diff options
author | Tamar Christina <tamar@zhox.com> | 2019-09-08 23:11:19 +0100 |
---|---|---|
committer | Tamar Christina <tamar@zhox.com> | 2019-10-20 16:21:10 +0100 |
commit | 4b431f334018eaef2cf36de3316025c68c922915 (patch) | |
tree | 7f85d5a1a6bc6d39e9a8c51fe33904ff61461a7e /utils/fs/fs.c | |
parent | c4c9904b324736dc5d190a91418e8d8f564d4104 (diff) | |
download | haskell-4b431f334018eaef2cf36de3316025c68c922915.tar.gz |
Windows: Update tarballs to GCC 9.2 and remove MAX_PATH limit.
Diffstat (limited to 'utils/fs/fs.c')
-rw-r--r-- | utils/fs/fs.c | 299 |
1 files changed, 266 insertions, 33 deletions
diff --git a/utils/fs/fs.c b/utils/fs/fs.c index 6644705ffe..4a761e5f9e 100644 --- a/utils/fs/fs.c +++ b/utils/fs/fs.c @@ -1,6 +1,6 @@ /* ----------------------------------------------------------------------------- * - * (c) Tamar Christina 2018 + * (c) Tamar Christina 2018-2019 * * Windows I/O routines for file opening. * @@ -24,16 +24,20 @@ #include <sys/stat.h> #include <sys/types.h> #include <share.h> +#include <errno.h> /* This function converts Windows paths between namespaces. More specifically It converts an explorer style path into a NT or Win32 namespace. - This has several caveats but they are caviats that are native to Windows and + This has several caveats but they are caveats that are native to Windows and not POSIX. See https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247.aspx. Anything else such as raw device paths we leave untouched. The main benefit of doing any of this is that we can break the MAX_PATH restriction and also - access raw handles that we couldn't before. */ -static wchar_t* __hs_create_device_name (const wchar_t* filename) { + access raw handles that we couldn't before. + + The resulting string is dynamically allocated and so callers are expected to + free this string. */ +wchar_t* FS(create_device_name) (const wchar_t* filename) { const wchar_t* win32_dev_namespace = L"\\\\.\\"; const wchar_t* win32_file_namespace = L"\\\\?\\"; const wchar_t* nt_device_namespace = L"\\Device\\"; @@ -57,13 +61,31 @@ static wchar_t* __hs_create_device_name (const wchar_t* filename) { result[i] = L'\\'; } + /* We need to expand dos short paths as well. */ + DWORD nResult = GetLongPathNameW (result, NULL, 0) + 1; + wchar_t* temp = NULL; + if (nResult > 1) + { + temp = _wcsdup (result); + free (result); + result = malloc (nResult * sizeof (wchar_t)); + if (GetLongPathNameW (temp, result, nResult) == 0) + { + result = memcpy (result, temp, wcslen (temp)); + goto cleanup; + } + free (temp); + } + /* Now resolve any . and .. in the path or subsequent API calls may fail since Win32 will no longer resolve them. */ - DWORD nResult = GetFullPathNameW (result, 0, NULL, NULL) + 1; - wchar_t *temp = _wcsdup (result); + nResult = GetFullPathNameW (result, 0, NULL, NULL) + 1; + temp = _wcsdup (result); + free (result); result = malloc (nResult * sizeof (wchar_t)); if (GetFullPathNameW (temp, nResult, result, NULL) == 0) { + result = memcpy (result, temp, wcslen (temp)); goto cleanup; } @@ -84,6 +106,7 @@ static wchar_t* __hs_create_device_name (const wchar_t* filename) { /* Create new string. */ int bLen = wcslen (result) + wcslen (ns) + 1; temp = _wcsdup (result); + free (result); result = malloc (bLen * sizeof (wchar_t)); if (swprintf (result, bLen, L"%ls%ls", ns, temp) <= 0) { @@ -100,23 +123,64 @@ cleanup: return NULL; } -#define HAS_FLAG(a,b) ((a & b) == b) +static int setErrNoFromWin32Error (void); +/* Sets errno to the right error value and returns -1 to indicate the failure. + This function should only be called when the creation of the fd actually + failed and you want to return -1 for the fd. */ +static +int setErrNoFromWin32Error () { + switch (GetLastError()) { + case ERROR_SUCCESS: + errno = 0; + break; + case ERROR_ACCESS_DENIED: + case ERROR_FILE_READ_ONLY: + errno = EACCES; + break; + case ERROR_FILE_NOT_FOUND: + case ERROR_PATH_NOT_FOUND: + errno = ENOENT; + break; + case ERROR_FILE_EXISTS: + errno = EEXIST; + break; + case ERROR_NOT_ENOUGH_MEMORY: + case ERROR_OUTOFMEMORY: + errno = ENOMEM; + break; + case ERROR_INVALID_HANDLE: + errno = EBADF; + break; + case ERROR_INVALID_FUNCTION: + errno = EFAULT; + break; + default: + errno = EINVAL; + break; + } + return -1; +} + + +#define HAS_FLAG(a,b) (((a) & (b)) == (b)) int FS(swopen) (const wchar_t* filename, int oflag, int shflag, int pmode) { /* Construct access mode. */ + /* https://docs.microsoft.com/en-us/windows/win32/fileio/file-access-rights-constants */ DWORD dwDesiredAccess = 0; if (HAS_FLAG (oflag, _O_RDONLY)) - dwDesiredAccess |= GENERIC_READ | FILE_READ_DATA | FILE_READ_ATTRIBUTES; + dwDesiredAccess |= GENERIC_READ; if (HAS_FLAG (oflag, _O_RDWR)) - dwDesiredAccess |= GENERIC_WRITE | GENERIC_READ | FILE_READ_DATA | - FILE_WRITE_DATA | FILE_READ_ATTRIBUTES | - FILE_WRITE_ATTRIBUTES; - if (HAS_FLAG (oflag, _O_WRONLY)) - dwDesiredAccess|= GENERIC_WRITE | FILE_WRITE_DATA | FILE_WRITE_ATTRIBUTES; + dwDesiredAccess |= GENERIC_WRITE | GENERIC_READ; + if (HAS_FLAG (oflag, _O_WRONLY)) + dwDesiredAccess |= GENERIC_WRITE; + if (HAS_FLAG (oflag, _O_APPEND)) + dwDesiredAccess |= FILE_APPEND_DATA; /* Construct shared mode. */ - DWORD dwShareMode = FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE; + /* https://docs.microsoft.com/en-us/windows/win32/fileio/file-attribute-constants */ + DWORD dwShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE; if (HAS_FLAG (shflag, _SH_DENYRW)) dwShareMode &= ~(FILE_SHARE_READ | FILE_SHARE_WRITE); if (HAS_FLAG (shflag, _SH_DENYWR)) @@ -138,21 +202,33 @@ int FS(swopen) (const wchar_t* filename, int oflag, int shflag, int pmode) } /* Create file disposition. */ - DWORD dwCreationDisposition = OPEN_EXISTING; - if (HAS_FLAG (oflag, _O_CREAT)) - dwCreationDisposition = OPEN_ALWAYS; + /* https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea */ + DWORD dwCreationDisposition = 0; if (HAS_FLAG (oflag, (_O_CREAT | _O_EXCL))) - dwCreationDisposition = CREATE_NEW; - if (HAS_FLAG (oflag, _O_TRUNC) && !HAS_FLAG (oflag, _O_CREAT)) - dwCreationDisposition = TRUNCATE_EXISTING; + dwCreationDisposition |= CREATE_NEW; + else if (HAS_FLAG (oflag, _O_TRUNC | _O_CREAT)) + dwCreationDisposition |= CREATE_ALWAYS; + else if (HAS_FLAG (oflag, _O_TRUNC) && !HAS_FLAG (oflag, O_RDONLY)) + dwCreationDisposition |= TRUNCATE_EXISTING; + else if (HAS_FLAG (oflag, _O_APPEND | _O_CREAT)) + dwCreationDisposition |= OPEN_ALWAYS; + else if (HAS_FLAG (oflag, _O_APPEND)) + dwCreationDisposition |= OPEN_EXISTING; + else if (HAS_FLAG (oflag, _O_CREAT)) + dwCreationDisposition |= OPEN_ALWAYS; + else + dwCreationDisposition |= OPEN_EXISTING; /* Set file access attributes. */ DWORD dwFlagsAndAttributes = FILE_ATTRIBUTE_NORMAL; if (HAS_FLAG (oflag, _O_RDONLY)) dwFlagsAndAttributes |= 0; /* No special attribute. */ - if (HAS_FLAG (oflag, (_O_CREAT | _O_TEMPORARY))) - dwFlagsAndAttributes |= FILE_FLAG_DELETE_ON_CLOSE; - if (HAS_FLAG (oflag, (_O_CREAT | _O_SHORT_LIVED))) + if (HAS_FLAG (oflag, _O_TEMPORARY)) + { + dwFlagsAndAttributes |= FILE_FLAG_DELETE_ON_CLOSE; + dwShareMode |= FILE_SHARE_DELETE; + } + if (HAS_FLAG (oflag, _O_SHORT_LIVED)) dwFlagsAndAttributes |= FILE_ATTRIBUTE_TEMPORARY; if (HAS_FLAG (oflag, _O_RANDOM)) dwFlagsAndAttributes |= FILE_FLAG_RANDOM_ACCESS; @@ -162,6 +238,11 @@ int FS(swopen) (const wchar_t* filename, int oflag, int shflag, int pmode) if (dwFlagsAndAttributes != FILE_ATTRIBUTE_NORMAL) dwFlagsAndAttributes &= ~FILE_ATTRIBUTE_NORMAL; + /* Ensure we have shared read for files which are opened read-only. */ + if (HAS_FLAG (dwCreationDisposition, OPEN_EXISTING) + && ((dwDesiredAccess & (GENERIC_WRITE|GENERIC_READ)) == GENERIC_READ)) + dwShareMode |= FILE_SHARE_READ; + /* Set security attributes. */ SECURITY_ATTRIBUTES securityAttributes; ZeroMemory (&securityAttributes, sizeof(SECURITY_ATTRIBUTES)); @@ -169,41 +250,39 @@ int FS(swopen) (const wchar_t* filename, int oflag, int shflag, int pmode) securityAttributes.lpSecurityDescriptor = NULL; securityAttributes.nLength = sizeof(SECURITY_ATTRIBUTES); - wchar_t* _filename = __hs_create_device_name (filename); + wchar_t* _filename = FS(create_device_name) (filename); if (!_filename) return -1; HANDLE hResult = CreateFileW (_filename, dwDesiredAccess, dwShareMode, &securityAttributes, dwCreationDisposition, dwFlagsAndAttributes, NULL); + free (_filename); if (INVALID_HANDLE_VALUE == hResult) - return -1; + return setErrNoFromWin32Error (); /* Now we have a Windows handle, we have to convert it to an FD and apply the remaining flags. */ const int flag_mask = _O_APPEND | _O_RDONLY | _O_TEXT | _O_WTEXT; int fd = _open_osfhandle ((intptr_t)hResult, oflag & flag_mask); if (-1 == fd) - return -1; + return setErrNoFromWin32Error (); /* Finally we can change the mode to the requested one. */ const int mode_mask = _O_TEXT | _O_BINARY | _O_U16TEXT | _O_U8TEXT | _O_WTEXT; if ((oflag & mode_mask) && (-1 == _setmode (fd, oflag & mode_mask))) - return -1; + return setErrNoFromWin32Error (); return fd; } -FILE *FS(fwopen) (const wchar_t* filename, const wchar_t* mode) +int FS(translate_mode) (const wchar_t* mode) { - int shflag = 0; - int pmode = 0; - int oflag = 0; - + int oflag = 0; int len = wcslen (mode); int i; - #define IS_EXT(X) ((i < (len - 1)) && mode[i] == X) + #define IS_EXT(X) ((i < (len - 1)) && mode[i+1] == X) for (i = 0; i < len; i++) { @@ -261,7 +340,19 @@ FILE *FS(fwopen) (const wchar_t* filename, const wchar_t* mode) } #undef IS_EXT + return oflag; +} + +FILE *FS(fwopen) (const wchar_t* filename, const wchar_t* mode) +{ + int shflag = 0; + int pmode = 0; + int oflag = FS(translate_mode) (mode); + int fd = FS(swopen) (filename, oflag, shflag, pmode); + if (fd < 0) + return NULL; + FILE* file = _wfdopen (fd, mode); return file; } @@ -281,8 +372,150 @@ FILE *FS(fopen) (const char* filename, const char* mode) FILE *result = FS(fwopen) (w_filename, w_mode); free (w_filename); free (w_mode); + + return result; +} + +int FS(sopen) (const char* filename, int oflag, int shflag, int pmode) +{ + size_t len = mbstowcs (NULL, filename, 0); + wchar_t *w_filename = malloc (sizeof (wchar_t) * (len + 1)); + mbstowcs (w_filename, filename, len); + w_filename[len] = L'\0'; + + int result = FS(swopen) (w_filename, oflag, shflag, pmode); + free (w_filename); + + return result; +} + +int FS(_stat) (const char *path, struct _stat *buffer) +{ + size_t len = mbstowcs (NULL, path, 0); + wchar_t *w_path = malloc (sizeof (wchar_t) * (len + 1)); + mbstowcs (w_path, path, len); + w_path[len] = L'\0'; + + int result = FS(_wstat) (w_path, buffer); + free (w_path); + return result; } + +int FS(_stat64) (const char *path, struct __stat64 *buffer) +{ + size_t len = mbstowcs (NULL, path, 0); + wchar_t *w_path = malloc (sizeof (wchar_t) * (len + 1)); + mbstowcs (w_path, path, len); + w_path[len] = L'\0'; + + int result = FS(_wstat64) (w_path, buffer); + free (w_path); + + return result; +} + +static __time64_t ftToPosix(FILETIME ft) +{ + /* takes the last modified date. */ + LARGE_INTEGER date, adjust; + date.HighPart = ft.dwHighDateTime; + date.LowPart = ft.dwLowDateTime; + + /* 100-nanoseconds = milliseconds * 10000. */ + /* A UNIX timestamp contains the number of seconds from Jan 1, 1970, while the + FILETIME documentation says: Contains a 64-bit value representing the + number of 100-nanosecond intervals since January 1, 1601 (UTC). + + Between Jan 1, 1601 and Jan 1, 1970 there are 11644473600 seconds */ + adjust.QuadPart = 11644473600000 * 10000; + + /* removes the diff between 1970 and 1601. */ + date.QuadPart -= adjust.QuadPart; + + /* converts back from 100-nanoseconds to seconds. */ + return (__time64_t)date.QuadPart / 10000000; +} + +int FS(_wstat) (const wchar_t *path, struct _stat *buffer) +{ + ZeroMemory (buffer, sizeof (struct _stat)); + wchar_t* _path = FS(create_device_name) (path); + if (!_path) + return -1; + + /* Construct shared mode. */ + DWORD dwShareMode = FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE; + DWORD dwDesiredAccess = FILE_READ_ATTRIBUTES; + DWORD dwFlagsAndAttributes = FILE_FLAG_BACKUP_SEMANTICS; + DWORD dwCreationDisposition = OPEN_EXISTING; + + SECURITY_ATTRIBUTES securityAttributes; + ZeroMemory (&securityAttributes, sizeof(SECURITY_ATTRIBUTES)); + securityAttributes.bInheritHandle = false; + securityAttributes.lpSecurityDescriptor = NULL; + securityAttributes.nLength = sizeof(SECURITY_ATTRIBUTES); + + HANDLE hResult + = CreateFileW (_path, dwDesiredAccess, dwShareMode, &securityAttributes, + dwCreationDisposition, dwFlagsAndAttributes, NULL); + + if (INVALID_HANDLE_VALUE == hResult) + { + free (_path); + return setErrNoFromWin32Error (); + } + + WIN32_FILE_ATTRIBUTE_DATA finfo; + ZeroMemory (&finfo, sizeof (WIN32_FILE_ATTRIBUTE_DATA)); + if(!GetFileAttributesExW (_path, GetFileExInfoStandard, &finfo)) + { + free (_path); + CloseHandle (hResult); + return setErrNoFromWin32Error (); + } + + unsigned short mode = _S_IREAD; + + if (finfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + mode |= (_S_IFDIR | _S_IEXEC); + else + { + mode |= _S_IFREG; + DWORD type; + if (GetBinaryTypeW (_path, &type)) + mode |= _S_IEXEC; + } + + if (!(finfo.dwFileAttributes & FILE_ATTRIBUTE_READONLY)) + mode |= _S_IWRITE; + + buffer->st_mode = mode; + buffer->st_nlink = 1; + buffer->st_size = ((uint64_t)finfo.nFileSizeHigh << 32) + finfo.nFileSizeLow; + buffer->st_atime = ftToPosix (finfo.ftLastAccessTime); + buffer->st_mtime = buffer->st_ctime = ftToPosix (finfo.ftLastWriteTime); + free (_path); + CloseHandle (hResult); + return 0; +} + +int FS(_wstat64) (const wchar_t *path, struct __stat64 *buffer) +{ + struct _stat buf; + ZeroMemory (buffer, sizeof (struct __stat64)); + + int result = FS(_wstat) (path, &buf); + + buffer->st_mode = buf.st_mode; + buffer->st_nlink = 1; + buffer->st_size = buf.st_size; + buffer->st_atime = buf.st_atime; + buffer->st_mtime = buf.st_mtime; + + return result; +} + #else FILE *FS(fopen) (const char* filename, const char* mode) { |