diff options
author | Руслан Ижбулатов <lrn1986@gmail.com> | 2017-09-29 10:14:41 +0000 |
---|---|---|
committer | Philip Withnall <withnall@endlessm.com> | 2017-11-01 12:46:38 +0000 |
commit | 53bd6a359f2c48e7729f89902097c892c8aa6fea (patch) | |
tree | a706f1f487cb7302bb59471e81e51125dae6f245 /glib/gstdio.c | |
parent | c74ab4a1db1e92a717f92d4c4b86e28661a966e7 (diff) | |
download | glib-53bd6a359f2c48e7729f89902097c892c8aa6fea.tar.gz |
W32: Add a stat() implementation for private use
This commit adds new W32-only functions to gstdio.c,
and a new header file, gstdioprivate.h.
These functions are:
g_win32_stat_utf8()
g_win32_lstat_utf8()
g_win32_fstat()
and they fill a private structure, GWin32PrivateStat,
which has all the fields that normal stat has, as well as some
extras.
These functions are then used throughout glib and gio to get better
data about the system. Specifically:
* Full, 64-bit size, guaranteed (g_stat() is forced to use 32-bit st_size)
* Full, 64-bit file identifier (st_ino is 0 when normal stat() is used, and still is)
* W32 File attributes (which stat() doesn't report); in particular, this allows
symlinks to be correctly identified
* Full, 64-bit time, guaranteed (g_stat() uses 32-bit st_*time on 32-bit Windows)
* Allocated file size (as a W32 replacement for the missing st_blocks)
st_mode remains unchanged (thus, no S_ISLNK), so when these are given back to
glib users (via g_stat(), for example, which is now implemented by calling g_win32_stat_utf8),
this field does not contain anything unexpected.
g_lstat() now calls g_win32_lstat_utf8(), which works on symlinks the way it's supposed to.
Also adds the g_win32_readlink_utf8() function, which behaves like readlink()
(including its inability to return 0-terminated strings and inability to say how large
the output buffer should be; these limitations are purely for compatibility with
existing glib code).
Thus, symlink support should now be much better, although far from being complete.
A new W32-only test in gio/tests/file.c highlights the following features:
* allocated size
* 64-bit time
* unique file IDs
https://bugzilla.gnome.org/show_bug.cgi?id=788180
Diffstat (limited to 'glib/gstdio.c')
-rw-r--r-- | glib/gstdio.c | 588 |
1 files changed, 564 insertions, 24 deletions
diff --git a/glib/gstdio.c b/glib/gstdio.c index 1c2587022..9ce36f63c 100644 --- a/glib/gstdio.c +++ b/glib/gstdio.c @@ -42,7 +42,7 @@ #endif #include "gstdio.h" - +#include "gstdioprivate.h" #if !defined (G_OS_UNIX) && !defined (G_OS_WIN32) #error Please port this to your operating system @@ -53,6 +53,538 @@ #define _wstat _wstat32 #endif +#if defined (G_OS_WIN32) + +/* We can't include Windows DDK and Windows SDK simultaneously, + * so let's copy this here from MinGW-w64 DDK. + * The structure is ultimately documented here: + * https://msdn.microsoft.com/en-us/library/ff552012(v=vs.85).aspx + */ +typedef struct _REPARSE_DATA_BUFFER +{ + ULONG ReparseTag; + USHORT ReparseDataLength; + USHORT Reserved; + union + { + struct + { + USHORT SubstituteNameOffset; + USHORT SubstituteNameLength; + USHORT PrintNameOffset; + USHORT PrintNameLength; + ULONG Flags; + WCHAR PathBuffer[1]; + } SymbolicLinkReparseBuffer; + struct + { + USHORT SubstituteNameOffset; + USHORT SubstituteNameLength; + USHORT PrintNameOffset; + USHORT PrintNameLength; + WCHAR PathBuffer[1]; + } MountPointReparseBuffer; + struct + { + UCHAR DataBuffer[1]; + } GenericReparseBuffer; + }; +} REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER; + +static int +w32_error_to_errno (DWORD error_code) +{ + switch (error_code) + { + case ERROR_ACCESS_DENIED: + return EACCES; + break; + case ERROR_INVALID_HANDLE: + return EBADF; + break; + case ERROR_INVALID_FUNCTION: + return EFAULT; + break; + case ERROR_FILE_NOT_FOUND: + return ENOENT; + break; + case ERROR_PATH_NOT_FOUND: + return ENOENT; /* or ELOOP, or ENAMETOOLONG */ + break; + case ERROR_NOT_ENOUGH_MEMORY: + case ERROR_OUTOFMEMORY: + return ENOMEM; + break; + default: + return EIO; + break; + } +} + +static int +_g_win32_stat_utf16_no_trailing_slashes (const gunichar2 *filename, + int fd, + GWin32PrivateStat *buf, + gboolean for_symlink) +{ + HANDLE file_handle; + gboolean succeeded_so_far; + DWORD error_code; + struct __stat64 statbuf; + BY_HANDLE_FILE_INFORMATION handle_info; + FILE_STANDARD_INFO std_info; + WIN32_FIND_DATAW finddata; + DWORD immediate_attributes; + gboolean is_symlink = FALSE; + gboolean is_directory; + DWORD open_flags; + wchar_t *filename_target = NULL; + int result; + + if (fd < 0) + { + immediate_attributes = GetFileAttributesW (filename); + + if (immediate_attributes == INVALID_FILE_ATTRIBUTES) + { + error_code = GetLastError (); + errno = w32_error_to_errno (error_code); + + return -1; + } + + is_symlink = (immediate_attributes & FILE_ATTRIBUTE_REPARSE_POINT) == FILE_ATTRIBUTE_REPARSE_POINT; + is_directory = (immediate_attributes & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY; + + open_flags = FILE_ATTRIBUTE_NORMAL; + + if (for_symlink && is_symlink) + open_flags |= FILE_FLAG_OPEN_REPARSE_POINT; + + if (is_directory) + open_flags |= FILE_FLAG_BACKUP_SEMANTICS; + + file_handle = CreateFileW (filename, FILE_READ_ATTRIBUTES, + FILE_SHARE_READ, NULL, OPEN_EXISTING, + open_flags, + NULL); + + if (file_handle == INVALID_HANDLE_VALUE) + { + error_code = GetLastError (); + errno = w32_error_to_errno (error_code); + return -1; + } + } + else + { + file_handle = (HANDLE) _get_osfhandle (fd); + + if (file_handle == INVALID_HANDLE_VALUE) + return -1; + } + + succeeded_so_far = GetFileInformationByHandle (file_handle, + &handle_info); + error_code = GetLastError (); + + if (succeeded_so_far) + { + succeeded_so_far = GetFileInformationByHandleEx (file_handle, + FileStandardInfo, + &std_info, + sizeof (std_info)); + error_code = GetLastError (); + } + + if (!succeeded_so_far) + { + CloseHandle (file_handle); + errno = w32_error_to_errno (error_code); + return -1; + } + + /* It's tempting to use GetFileInformationByHandleEx(FileAttributeTagInfo), + * but it always reports that the ReparseTag is 0. + */ + if (fd < 0) + { + HANDLE tmp = FindFirstFileW (filename, + &finddata); + + if (tmp == INVALID_HANDLE_VALUE) + { + error_code = GetLastError (); + errno = w32_error_to_errno (error_code); + CloseHandle (file_handle); + return -1; + } + + FindClose (tmp); + + if (is_symlink && !for_symlink) + { + /* If filename is a symlink, _wstat64 obtains information about + * the symlink (except that st_size will be 0). + * To get information about the target we need to resolve + * the symlink first. And we need _wstat64() to get st_dev, + * it's a bother to try finding it ourselves. + */ + DWORD filename_target_len; + DWORD new_len; + + /* Just in case, give it a real memory location instead of NULL */ + new_len = GetFinalPathNameByHandleW (file_handle, + (wchar_t *) &filename_target_len, + 0, + FILE_NAME_NORMALIZED); + +#define SANE_LIMIT 1024 * 10 + if (new_len >= SANE_LIMIT) +#undef SANE_LIMIT + { + new_len = 0; + error_code = ERROR_BUFFER_OVERFLOW; + } + else if (new_len == 0) + { + error_code = GetLastError (); + } + + if (new_len > 0) + { + const wchar_t *extended_prefix = L"\\\\?\\"; + const gsize extended_prefix_len = wcslen (extended_prefix); + const gsize extended_prefix_len_bytes = sizeof (wchar_t) * extended_prefix_len; + + /* Pretend that new_len doesn't count the terminating NUL char, + * and ask for a bit more space than is needed. + */ + filename_target_len = new_len + 5; + filename_target = g_malloc (filename_target_len * sizeof (wchar_t)); + + new_len = GetFinalPathNameByHandleW (file_handle, + filename_target, + filename_target_len, + FILE_NAME_NORMALIZED); + + /* filename_target_len is already larger than needed, + * new_len should be smaller than that, even if the size + * is off by 1 for some reason. + */ + if (new_len >= filename_target_len - 1) + { + new_len = 0; + error_code = ERROR_BUFFER_OVERFLOW; + g_clear_pointer (&filename_target, g_free); + } + /* GetFinalPathNameByHandle() is documented to return extended paths, + * strip the extended prefix. + */ + else if (new_len > extended_prefix_len && + memcmp (filename_target, extended_prefix, extended_prefix_len_bytes) == 0) + { + new_len -= extended_prefix_len; + memmove (filename_target, + filename_target + extended_prefix_len, + (new_len + 1) * sizeof (wchar_t)); + } + } + + if (new_len == 0) + succeeded_so_far = FALSE; + } + + CloseHandle (file_handle); + } + /* else if fd >= 0 the file_handle was obtained via _get_osfhandle() + * and must not be closed, it is owned by fd. + */ + + if (!succeeded_so_far) + { + errno = w32_error_to_errno (error_code); + return -1; + } + + if (fd < 0) + result = _wstat64 (filename_target != NULL ? filename_target : filename, &statbuf); + else + result = _fstat64 (fd, &statbuf); + + if (result != 0) + { + int errsv = errno; + + g_free (filename_target); + errno = errsv; + + return -1; + } + + g_free (filename_target); + + buf->st_dev = statbuf.st_dev; + buf->st_mode = statbuf.st_mode; + buf->volume_serial = handle_info.dwVolumeSerialNumber; + buf->file_index = (((guint64) handle_info.nFileIndexHigh) << 32) | handle_info.nFileIndexLow; + /* Note that immediate_attributes is for the symlink + * (if it's a symlink), while handle_info contains info + * about the symlink or the target, depending on the flags + * we used earlier. + */ + buf->attributes = handle_info.dwFileAttributes; + buf->st_nlink = handle_info.nNumberOfLinks; + buf->st_size = (((guint64) handle_info.nFileSizeHigh) << 32) | handle_info.nFileSizeLow; + buf->allocated_size = std_info.AllocationSize.QuadPart; + + if (fd < 0 && buf->attributes & FILE_ATTRIBUTE_REPARSE_POINT) + buf->reparse_tag = finddata.dwReserved0; + else + buf->reparse_tag = 0; + + buf->st_ctime = statbuf.st_ctime; + buf->st_atime = statbuf.st_atime; + buf->st_mtime = statbuf.st_mtime; + + return 0; +} + +static int +_g_win32_stat_utf8 (const gchar *filename, + GWin32PrivateStat *buf, + gboolean for_symlink) +{ + wchar_t *wfilename; + int result; + gsize len; + + len = strlen (filename); + + while (len > 0 && G_IS_DIR_SEPARATOR (filename[len - 1])) + len--; + + if (len <= 0 || + (g_path_is_absolute (filename) && len <= g_path_skip_root (filename) - filename)) + len = strlen (filename); + + wfilename = g_utf8_to_utf16 (filename, len, NULL, NULL, NULL); + + if (wfilename == NULL) + { + errno = EINVAL; + return -1; + } + + result = _g_win32_stat_utf16_no_trailing_slashes (wfilename, -1, buf, for_symlink); + + g_free (wfilename); + + return result; +} + +int +g_win32_stat_utf8 (const gchar *filename, + GWin32PrivateStat *buf) +{ + return _g_win32_stat_utf8 (filename, buf, FALSE); +} + +int +g_win32_lstat_utf8 (const gchar *filename, + GWin32PrivateStat *buf) +{ + return _g_win32_stat_utf8 (filename, buf, TRUE); +} + +int +g_win32_fstat (int fd, + GWin32PrivateStat *buf) +{ + return _g_win32_stat_utf16_no_trailing_slashes (NULL, fd, buf, FALSE); +} + +static int +_g_win32_readlink_utf16_raw (const gunichar2 *filename, + gunichar2 *buf, + gsize buf_size) +{ + DWORD returned_bytes; + BYTE returned_data[MAXIMUM_REPARSE_DATA_BUFFER_SIZE]; /* This is 16k, by the way */ + HANDLE h; + DWORD attributes; + REPARSE_DATA_BUFFER *rep_buf; + DWORD to_copy; + DWORD error_code; + + if (buf_size > G_MAXSIZE / sizeof (wchar_t)) + { + /* "buf_size * sizeof (wchar_t)" overflows */ + errno = EFAULT; + return -1; + } + + if ((attributes = GetFileAttributesW (filename)) == 0) + { + error_code = GetLastError (); + errno = w32_error_to_errno (error_code); + return -1; + } + + if ((attributes & FILE_ATTRIBUTE_REPARSE_POINT) == 0) + { + errno = EINVAL; + return -1; + } + + /* To read symlink target we need to open the file as a reparse + * point and use DeviceIoControl() on it. + */ + h = CreateFileW (filename, + FILE_READ_ATTRIBUTES | SYNCHRONIZE | GENERIC_READ, + FILE_SHARE_READ, NULL, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL + | FILE_FLAG_OPEN_REPARSE_POINT + | (attributes & FILE_ATTRIBUTE_DIRECTORY ? FILE_FLAG_BACKUP_SEMANTICS : 0), + NULL); + + if (h == INVALID_HANDLE_VALUE) + { + error_code = GetLastError (); + errno = w32_error_to_errno (error_code); + return -1; + } + + if (!DeviceIoControl (h, FSCTL_GET_REPARSE_POINT, NULL, 0, + returned_data, MAXIMUM_REPARSE_DATA_BUFFER_SIZE, + &returned_bytes, NULL)) + { + error_code = GetLastError (); + errno = w32_error_to_errno (error_code); + CloseHandle (h); + return -1; + } + + rep_buf = (REPARSE_DATA_BUFFER *) returned_data; + to_copy = 0; + + if (rep_buf->ReparseTag == IO_REPARSE_TAG_SYMLINK) + { + to_copy = rep_buf->SymbolicLinkReparseBuffer.SubstituteNameLength; + + if (to_copy > buf_size * sizeof (wchar_t)) + to_copy = buf_size * sizeof (wchar_t); + + memcpy (buf, + &((BYTE *) rep_buf->SymbolicLinkReparseBuffer.PathBuffer)[rep_buf->SymbolicLinkReparseBuffer.SubstituteNameOffset], + to_copy); + } + else if (rep_buf->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT) + { + to_copy = rep_buf->MountPointReparseBuffer.SubstituteNameLength; + + if (to_copy > buf_size * sizeof (wchar_t)) + to_copy = buf_size * sizeof (wchar_t); + + memcpy (buf, + &((BYTE *) rep_buf->MountPointReparseBuffer.PathBuffer)[rep_buf->MountPointReparseBuffer.SubstituteNameOffset], + to_copy); + } + + CloseHandle (h); + + return to_copy; +} + +static int +_g_win32_readlink_utf16 (const gunichar2 *filename, + gunichar2 *buf, + gsize buf_size) +{ + const wchar_t *ntobjm_prefix = L"\\??\\"; + const gsize ntobjm_prefix_len_unichar2 = wcslen (ntobjm_prefix); + const gsize ntobjm_prefix_len_bytes = sizeof (gunichar2) * ntobjm_prefix_len_unichar2; + int result = _g_win32_readlink_utf16_raw (filename, buf, buf_size); + + if (result <= 0) + return result; + + /* Ensure that output is a multiple of sizeof (gunichar2), + * cutting any trailing partial gunichar2, if present. + */ + result -= result % sizeof (gunichar2); + + if (result <= 0) + return result; + + /* DeviceIoControl () tends to return filenames as NT Object Manager + * names , i.e. "\\??\\C:\\foo\\bar". + * Remove the leading 4-byte \??\ prefix, as glib (as well as many W32 API + * functions) is unprepared to deal with it. + */ + if (result > ntobjm_prefix_len_bytes && + memcmp (buf, ntobjm_prefix, ntobjm_prefix_len_bytes) == 0) + { + result -= ntobjm_prefix_len_bytes; + memmove (buf, buf + ntobjm_prefix_len_unichar2, result); + } + + return result; +} + +int +g_win32_readlink_utf8 (const gchar *filename, + gchar *buf, + gsize buf_size) +{ + wchar_t *wfilename; + int result; + + wfilename = g_utf8_to_utf16 (filename, -1, NULL, NULL, NULL); + + if (wfilename == NULL) + { + errno = EINVAL; + return -1; + } + + result = _g_win32_readlink_utf16 (wfilename, (gunichar2 *) buf, buf_size); + + g_free (wfilename); + + if (result > 0) + { + glong tmp_len; + gchar *tmp = g_utf16_to_utf8 ((const gunichar2 *) buf, + result / sizeof (gunichar2), + NULL, + &tmp_len, + NULL); + + if (tmp == NULL) + { + errno = EINVAL; + return -1; + } + + if (tmp_len > buf_size - 1) + tmp_len = buf_size - 1; + + memcpy (buf, tmp, tmp_len); + /* readlink() doesn't NUL-terminate, but we do. + * To be compliant, however, we return the + * number of bytes without the NUL-terminator. + */ + buf[tmp_len] = '\0'; + result = tmp_len; + g_free (tmp); + } + + return result; +} + +#endif + /** * g_access: * @filename: (type filename): a pathname in the GLib file name encoding @@ -482,30 +1014,21 @@ g_stat (const gchar *filename, GStatBuf *buf) { #ifdef G_OS_WIN32 - wchar_t *wfilename = g_utf8_to_utf16 (filename, -1, NULL, NULL, NULL); - int retval; - int save_errno; - int len; - - if (wfilename == NULL) - { - errno = EINVAL; - return -1; - } - - len = wcslen (wfilename); - while (len > 0 && G_IS_DIR_SEPARATOR (wfilename[len-1])) - len--; - if (len > 0 && - (!g_path_is_absolute (filename) || len > g_path_skip_root (filename) - filename)) - wfilename[len] = '\0'; - - retval = _wstat (wfilename, buf); - save_errno = errno; + GWin32PrivateStat w32_buf; + int retval = g_win32_stat_utf8 (filename, &w32_buf); + + buf->st_dev = w32_buf.st_dev; + buf->st_ino = w32_buf.st_ino; + buf->st_mode = w32_buf.st_mode; + buf->st_nlink = w32_buf.st_nlink; + buf->st_uid = w32_buf.st_uid; + buf->st_gid = w32_buf.st_gid; + buf->st_rdev = w32_buf.st_dev; + buf->st_size = w32_buf.st_size; + buf->st_atime = w32_buf.st_atime; + buf->st_mtime = w32_buf.st_mtime; + buf->st_ctime = w32_buf.st_ctime; - g_free (wfilename); - - errno = save_errno; return retval; #else return stat (filename, buf); @@ -539,6 +1062,23 @@ g_lstat (const gchar *filename, #ifdef HAVE_LSTAT /* This can't be Win32, so don't do the widechar dance. */ return lstat (filename, buf); +#elif defined (G_OS_WIN32) + GWin32PrivateStat w32_buf; + int retval = g_win32_lstat_utf8 (filename, &w32_buf); + + buf->st_dev = w32_buf.st_dev; + buf->st_ino = w32_buf.st_ino; + buf->st_mode = w32_buf.st_mode; + buf->st_nlink = w32_buf.st_nlink; + buf->st_uid = w32_buf.st_uid; + buf->st_gid = w32_buf.st_gid; + buf->st_rdev = w32_buf.st_dev; + buf->st_size = w32_buf.st_size; + buf->st_atime = w32_buf.st_atime; + buf->st_mtime = w32_buf.st_mtime; + buf->st_ctime = w32_buf.st_ctime; + + return retval; #else return g_stat (filename, buf); #endif |