Skip to content

Commit

Permalink
Include info about system call errors in some exceptions from operati…
Browse files Browse the repository at this point in the history
…ng on named mutexes (#92603)

* Include info about system call errors in some exceptions from operating on named mutexes

- Added new PAL APIs for creating and opening mutexes that take a string buffer for system call error info. These are called with a stack-allocated buffer and upon error the system call errors are appended to the exception message.
- When there is a system call failure that leads to the PAL API failing, some info is appended to the error string, including the system call, relevant arguments, return value, and `errno`
- `chmod` on OSX seemingly can be interrupted by signals, fixed to retry. Also fixed a couple other small things.

Fixes #89090
  • Loading branch information
kouvel authored Sep 30, 2023
1 parent cc07299 commit a2818c5
Show file tree
Hide file tree
Showing 16 changed files with 784 additions and 136 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,7 @@
<Compile Include="$(BclSourcesRoot)\Internal\Runtime\InteropServices\InMemoryAssemblyLoader.PlatformNotSupported.cs" />
<Compile Include="$(BclSourcesRoot)\Interop\Unix\Interop.Libraries.cs" />
<Compile Include="$(BclSourcesRoot)\System\Threading\LowLevelLifoSemaphore.Unix.cs" />
<Compile Include="$(BclSourcesRoot)\System\Threading\Mutex.CoreCLR.Unix.cs" />
</ItemGroup>
<ItemGroup Condition="'$(TargetsWindows)' == 'true'">
<Compile Include="$(BclSourcesRoot)\Internal\Runtime\InteropServices\InMemoryAssemblyLoader.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.Win32.SafeHandles;

namespace System.Threading
{
/// <summary>
/// Synchronization primitive that can also be used for interprocess synchronization
/// </summary>
public sealed partial class Mutex : WaitHandle
{
private void CreateMutexCore(bool initiallyOwned, string? name, out bool createdNew)
{
SafeWaitHandle mutexHandle = CreateMutexCore(initiallyOwned, name, out int errorCode, out string? errorDetails);
if (mutexHandle.IsInvalid)
{
mutexHandle.SetHandleAsInvalid();
if (errorCode == Interop.Errors.ERROR_FILENAME_EXCED_RANGE)
// On Unix, length validation is done by CoreCLR's PAL after converting to utf-8
throw new ArgumentException(SR.Argument_WaitHandleNameTooLong, nameof(name));
if (errorCode == Interop.Errors.ERROR_INVALID_HANDLE)
throw new WaitHandleCannotBeOpenedException(SR.Format(SR.Threading_WaitHandleCannotBeOpenedException_InvalidHandle, name));

throw Win32Marshal.GetExceptionForWin32Error(errorCode, name, errorDetails);
}

createdNew = errorCode != Interop.Errors.ERROR_ALREADY_EXISTS;
SafeWaitHandle = mutexHandle;
}

private static OpenExistingResult OpenExistingWorker(string name, out Mutex? result)
{
ArgumentException.ThrowIfNullOrEmpty(name);

result = null;
// To allow users to view & edit the ACL's, call OpenMutex
// with parameters to allow us to view & edit the ACL. This will
// fail if we don't have permission to view or edit the ACL's.
// If that happens, ask for less permissions.
SafeWaitHandle myHandle = OpenMutexCore(name, out int errorCode, out string? errorDetails);

if (myHandle.IsInvalid)
{
myHandle.Dispose();

if (errorCode == Interop.Errors.ERROR_FILENAME_EXCED_RANGE)
{
// On Unix, length validation is done by CoreCLR's PAL after converting to utf-8
throw new ArgumentException(SR.Argument_WaitHandleNameTooLong, nameof(name));
}
if (Interop.Errors.ERROR_FILE_NOT_FOUND == errorCode || Interop.Errors.ERROR_INVALID_NAME == errorCode)
return OpenExistingResult.NameNotFound;
if (Interop.Errors.ERROR_PATH_NOT_FOUND == errorCode)
return OpenExistingResult.PathNotFound;
if (Interop.Errors.ERROR_INVALID_HANDLE == errorCode)
return OpenExistingResult.NameInvalid;

throw Win32Marshal.GetExceptionForWin32Error(errorCode, name, errorDetails);
}

result = new Mutex(myHandle);
return OpenExistingResult.Success;
}

// Note: To call ReleaseMutex, you must have an ACL granting you
// MUTEX_MODIFY_STATE rights (0x0001). The other interesting value
// in a Mutex's ACL is MUTEX_ALL_ACCESS (0x1F0001).
public void ReleaseMutex()
{
if (!Interop.Kernel32.ReleaseMutex(SafeWaitHandle))
{
throw new ApplicationException(SR.Arg_SynchronizationLockException);
}
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Unix-specific implementation

private const int SystemCallErrorsBufferSize = 256;

private static unsafe SafeWaitHandle CreateMutexCore(
bool initialOwner,
string? name,
out int errorCode,
out string? errorDetails)
{
byte* systemCallErrors = stackalloc byte[SystemCallErrorsBufferSize];
SafeWaitHandle mutexHandle = CreateMutex(initialOwner, name, systemCallErrors, SystemCallErrorsBufferSize);

// Get the error code even if the handle is valid, as it could be ERROR_ALREADY_EXISTS, indicating that the mutex
// already exists and was opened
errorCode = Marshal.GetLastPInvokeError();

errorDetails = mutexHandle.IsInvalid ? GetErrorDetails(systemCallErrors) : null;
return mutexHandle;
}

private static unsafe SafeWaitHandle OpenMutexCore(string name, out int errorCode, out string? errorDetails)
{
byte* systemCallErrors = stackalloc byte[SystemCallErrorsBufferSize];
SafeWaitHandle mutexHandle = OpenMutex(name, systemCallErrors, SystemCallErrorsBufferSize);
errorCode = mutexHandle.IsInvalid ? Marshal.GetLastPInvokeError() : Interop.Errors.ERROR_SUCCESS;
errorDetails = mutexHandle.IsInvalid ? GetErrorDetails(systemCallErrors) : null;
return mutexHandle;
}

private static unsafe string? GetErrorDetails(byte* systemCallErrors)
{
int systemCallErrorsLength =
new ReadOnlySpan<byte>(systemCallErrors, SystemCallErrorsBufferSize).IndexOf((byte)'\0');
if (systemCallErrorsLength > 0)
{
try
{
return
SR.Format(SR.Unix_SystemCallErrors, Encoding.UTF8.GetString(systemCallErrors, systemCallErrorsLength));
}
catch { } // avoid hiding the original error due to an error here
}

return null;
}

[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "PAL_CreateMutexW", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)]
private static unsafe partial SafeWaitHandle CreateMutex([MarshalAs(UnmanagedType.Bool)] bool initialOwner, string? name, byte* systemCallErrors, uint systemCallErrorsBufferSize);

[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "PAL_OpenMutexW", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)]
private static unsafe partial SafeWaitHandle OpenMutex(string name, byte* systemCallErrors, uint systemCallErrorsBufferSize);
}
}
17 changes: 17 additions & 0 deletions src/coreclr/pal/inc/pal.h
Original file line number Diff line number Diff line change
Expand Up @@ -1048,6 +1048,15 @@ CreateMutexExW(
IN DWORD dwFlags,
IN DWORD dwDesiredAccess);

PALIMPORT
HANDLE
PALAPI
PAL_CreateMutexW(
IN BOOL bInitialOwner,
IN LPCWSTR lpName,
IN LPSTR lpSystemCallErrors,
IN DWORD dwSystemCallErrorsBufferSize);

// CreateMutexExW: dwFlags
#define CREATE_MUTEX_INITIAL_OWNER ((DWORD)0x1)

Expand All @@ -1061,6 +1070,14 @@ OpenMutexW(
IN BOOL bInheritHandle,
IN LPCWSTR lpName);

PALIMPORT
HANDLE
PALAPI
PAL_OpenMutexW(
IN LPCWSTR lpName,
IN LPSTR lpSystemCallErrors,
IN DWORD dwSystemCallErrorsBufferSize);

#ifdef UNICODE
#define OpenMutex OpenMutexW
#endif
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/pal/src/configure.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ check_function_exists(semget HAS_SYSV_SEMAPHORES)
check_function_exists(pthread_mutex_init HAS_PTHREAD_MUTEXES)
check_function_exists(ttrace HAVE_TTRACE)
check_function_exists(pipe2 HAVE_PIPE2)
check_function_exists(strerrorname_np HAVE_STRERRORNAME_NP)

check_cxx_source_compiles("
#include <pthread_np.h>
Expand Down
16 changes: 9 additions & 7 deletions src/coreclr/pal/src/include/pal/mutex.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ namespace CorUnix

PAL_ERROR
InternalCreateMutex(
SharedMemorySystemCallErrors *errors,
CPalThread *pThread,
LPSECURITY_ATTRIBUTES lpMutexAttributes,
BOOL bInitialOwner,
Expand All @@ -47,6 +48,7 @@ namespace CorUnix

PAL_ERROR
InternalOpenMutex(
SharedMemorySystemCallErrors *errors,
CPalThread *pThread,
LPCSTR lpName,
HANDLE *phMutex
Expand Down Expand Up @@ -151,10 +153,10 @@ enum class MutexTryAcquireLockResult
class MutexHelpers
{
public:
static void InitializeProcessSharedRobustRecursiveMutex(pthread_mutex_t *mutex);
static void InitializeProcessSharedRobustRecursiveMutex(SharedMemorySystemCallErrors *errors, pthread_mutex_t *mutex);
static void DestroyMutex(pthread_mutex_t *mutex);

static MutexTryAcquireLockResult TryAcquireLock(pthread_mutex_t *mutex, DWORD timeoutMilliseconds);
static MutexTryAcquireLockResult TryAcquireLock(SharedMemorySystemCallErrors *errors, pthread_mutex_t *mutex, DWORD timeoutMilliseconds);
static void ReleaseLock(pthread_mutex_t *mutex);
};
#endif // NAMED_MUTEX_USE_PTHREAD_MUTEX
Expand All @@ -172,7 +174,7 @@ class NamedMutexSharedData
bool m_isAbandoned;

public:
NamedMutexSharedData();
NamedMutexSharedData(SharedMemorySystemCallErrors *errors);
~NamedMutexSharedData();

#if NAMED_MUTEX_USE_PTHREAD_MUTEX
Expand Down Expand Up @@ -214,10 +216,10 @@ class NamedMutexProcessData : public SharedMemoryProcessDataBase
bool m_hasRefFromLockOwnerThread;

public:
static SharedMemoryProcessDataHeader *CreateOrOpen(LPCSTR name, bool acquireLockIfCreated, bool *createdRef);
static SharedMemoryProcessDataHeader *Open(LPCSTR name);
static SharedMemoryProcessDataHeader *CreateOrOpen(SharedMemorySystemCallErrors *errors, LPCSTR name, bool acquireLockIfCreated, bool *createdRef);
static SharedMemoryProcessDataHeader *Open(SharedMemorySystemCallErrors *errors, LPCSTR name);
private:
static SharedMemoryProcessDataHeader *CreateOrOpen(LPCSTR name, bool createIfNotExist, bool acquireLockIfCreated, bool *createdRef);
static SharedMemoryProcessDataHeader *CreateOrOpen(SharedMemorySystemCallErrors *errors, LPCSTR name, bool createIfNotExist, bool acquireLockIfCreated, bool *createdRef);

public:
NamedMutexProcessData(
Expand Down Expand Up @@ -248,7 +250,7 @@ class NamedMutexProcessData : public SharedMemoryProcessDataBase
void SetNextInThreadOwnedNamedMutexList(NamedMutexProcessData *next);

public:
MutexTryAcquireLockResult TryAcquireLock(DWORD timeoutMilliseconds);
MutexTryAcquireLockResult TryAcquireLock(SharedMemorySystemCallErrors *errors, DWORD timeoutMilliseconds);
void ReleaseLock();
void Abandon();
private:
Expand Down
35 changes: 25 additions & 10 deletions src/coreclr/pal/src/include/pal/sharedmemory.h
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,19 @@ class SharedMemoryException
DWORD GetErrorCode() const;
};

class SharedMemorySystemCallErrors
{
private:
char *m_buffer;
int m_bufferSize;
int m_length;
bool m_isTracking;

public:
SharedMemorySystemCallErrors(char *buffer, int bufferSize);
void Append(LPCSTR format, ...);
};

class SharedMemoryHelpers
{
private:
Expand All @@ -106,20 +119,22 @@ class SharedMemoryHelpers
static void BuildSharedFilesPath(PathCharString& destination, const char *suffix, int suffixByteCount);
static bool AppendUInt32String(PathCharString& destination, UINT32 value);

static bool EnsureDirectoryExists(const char *path, bool isGlobalLockAcquired, bool createIfNotExist = true, bool isSystemDirectory = false);
static bool EnsureDirectoryExists(SharedMemorySystemCallErrors *errors, const char *path, bool isGlobalLockAcquired, bool createIfNotExist = true, bool isSystemDirectory = false);
private:
static int Open(LPCSTR path, int flags, mode_t mode = static_cast<mode_t>(0));
static int Open(SharedMemorySystemCallErrors *errors, LPCSTR path, int flags, mode_t mode = static_cast<mode_t>(0));
public:
static int OpenDirectory(LPCSTR path);
static int CreateOrOpenFile(LPCSTR path, bool createIfNotExist = true, bool *createdRef = nullptr);
static int OpenDirectory(SharedMemorySystemCallErrors *errors, LPCSTR path);
static int CreateOrOpenFile(SharedMemorySystemCallErrors *errors, LPCSTR path, bool createIfNotExist = true, bool *createdRef = nullptr);
static void CloseFile(int fileDescriptor);

static SIZE_T GetFileSize(int fileDescriptor);
static void SetFileSize(int fileDescriptor, SIZE_T byteCount);
static int ChangeMode(LPCSTR path, mode_t mode);

static SIZE_T GetFileSize(SharedMemorySystemCallErrors *errors, LPCSTR filePath, int fileDescriptor);
static void SetFileSize(SharedMemorySystemCallErrors *errors, LPCSTR filePath, int fileDescriptor, SIZE_T byteCount);

static void *MemoryMapFile(int fileDescriptor, SIZE_T byteCount);
static void *MemoryMapFile(SharedMemorySystemCallErrors *errors, LPCSTR filePath, int fileDescriptor, SIZE_T byteCount);

static bool TryAcquireFileLock(int fileDescriptor, int operation);
static bool TryAcquireFileLock(SharedMemorySystemCallErrors *errors, int fileDescriptor, int operation);
static void ReleaseFileLock(int fileDescriptor);

static void VerifyStringOperation(bool success);
Expand Down Expand Up @@ -207,7 +222,7 @@ class SharedMemoryProcessDataHeader
SharedMemoryProcessDataHeader *m_nextInProcessDataHeaderList;

public:
static SharedMemoryProcessDataHeader *CreateOrOpen(LPCSTR name, SharedMemorySharedDataHeader requiredSharedDataHeader, SIZE_T sharedDataByteCount, bool createIfNotExist, bool *createdRef);
static SharedMemoryProcessDataHeader *CreateOrOpen(SharedMemorySystemCallErrors *errors, LPCSTR name, SharedMemorySharedDataHeader requiredSharedDataHeader, SIZE_T sharedDataByteCount, bool createIfNotExist, bool *createdRef);

public:
static SharedMemoryProcessDataHeader *PalObject_GetProcessDataHeader(CorUnix::IPalObject *object);
Expand Down Expand Up @@ -260,7 +275,7 @@ class SharedMemoryManager
public:
static void AcquireCreationDeletionProcessLock();
static void ReleaseCreationDeletionProcessLock();
static void AcquireCreationDeletionFileLock();
static void AcquireCreationDeletionFileLock(SharedMemorySystemCallErrors *errors);
static void ReleaseCreationDeletionFileLock();

public:
Expand Down
2 changes: 2 additions & 0 deletions src/coreclr/pal/src/include/pal/utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -212,3 +212,5 @@ class StringHolder

};
#endif /* _PAL_UTILS_H_ */

const char *GetFriendlyErrorCodeString(int errorCode);
58 changes: 58 additions & 0 deletions src/coreclr/pal/src/misc/utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -366,3 +366,61 @@ BOOL IsRunningOnMojaveHardenedRuntime()
}

#endif // __APPLE__

const char *GetFriendlyErrorCodeString(int errorCode)
{
#if HAVE_STRERRORNAME_NP
const char *error = strerrorname_np(errorCode);
if (error != nullptr)
{
return error;
}
#else // !HAVE_STRERRORNAME_NP
switch (errorCode)
{
case EACCES: return "EACCES";
#if EAGAIN == EWOULDBLOCK
case EAGAIN: return "EAGAIN/EWOULDBLOCK";
#else
case EAGAIN: return "EAGAIN";
case EWOULDBLOCK: return "EWOULDBLOCK";
#endif
case EBADF: return "EBADF";
case EBUSY: return "EBUSY";
case EDQUOT: return "EDQUOT";
case EEXIST: return "EEXIST";
case EFAULT: return "EFAULT";
case EFBIG: return "EFBIG";
case EINVAL: return "EINVAL";
case EINTR: return "EINTR";
case EIO: return "EIO";
case EISDIR: return "EISDIR";
case ELOOP: return "ELOOP";
case EMFILE: return "EMFILE";
case EMLINK: return "EMLINK";
case ENAMETOOLONG: return "ENAMETOOLONG";
case ENFILE: return "ENFILE";
case ENODEV: return "ENODEV";
case ENOENT: return "ENOENT";
case ENOLCK: return "ENOLCK";
case ENOMEM: return "ENOMEM";
case ENOSPC: return "ENOSPC";
#if ENOTSUP == EOPNOTSUPP
case ENOTSUP: return "ENOTSUP/EOPNOTSUPP";
#else
case ENOTSUP: return "ENOTSUP";
case EOPNOTSUPP: return "EOPNOTSUPP";
#endif
case ENOTDIR: return "ENOTDIR";
case ENOTEMPTY: return "ENOTEMPTY";
case ENXIO: return "ENXIO";
case EOVERFLOW: return "EOVERFLOW";
case EPERM: return "EPERM";
case EROFS: return "EROFS";
case ETXTBSY: return "ETXTBSY";
case EXDEV: return "EXDEV";
}
#endif // HAVE_STRERRORNAME_NP

return strerror(errorCode);
}
Loading

0 comments on commit a2818c5

Please sign in to comment.