[glib: 3/5] W32: significant symlink code changes
- From: Philip Withnall <pwithnall src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [glib: 3/5] W32: significant symlink code changes
- Date: Wed, 13 Mar 2019 11:56:00 +0000 (UTC)
commit 62d387151dfd7c3f807370844d9a936d6d42699c
Author: Руслан Ижбулатов <lrn1986 gmail com>
Date: Fri Aug 24 09:16:46 2018 +0000
W32: significant symlink code changes
Put the core readlink() code into a separate
_g_win32_readlink_handle_raw() function that takes a file handle,
can optionally ensure NUL-terminatedness of its output
(for cases where we need a NUL-terminator and do *not* need
to get the exact contents of the symlink as it is stored in FS)
and can either fill a caller-provided buffer *or* allocate
its own buffer, and can also read the reparse tag.
Put the rest of readlink() code into separate
functions that do UTF-16<->UTF-8, strip inconvenient prefix
and open/close the symlink file handle as needed.
Split _g_win32_stat_utf16_no_trailing_slashes() into
two functions - the one that takes a filename and the one
that takes a file descriptor. The part of these functions
that would have been duplicate is now split into the
_g_win32_fill_privatestat() funcion.
Add more comments explaining what each function does.
Only g_win32_readlink_utf8(), which is callable from outside
via private function interface, gets a real doc-comment,
the rest get normal, non-doc comments.
Change all callers to use the new version of the private
g_win32_readlink_utf8() function, which can now NUL-terminate
and allocate on demand - no need to call it in a loop.
Also, the new code should correctly get reparse tag when the
caller does fstat() on a symlink. Do note that this requires
the caller to get a FD for the symlink, not the target. Figuring
out how to do that is up to the caller.
Since symlink info (target path and reparse tag) are now always
read directly, via DeviceIoControl(), we don't need to use
FindFirstFileW() anymore.
gio/glocalfileinfo.c | 19 +-
glib/gfileutils.c | 27 +-
glib/glib-private.h | 20 +-
glib/gstdio-private.c | 89 ++++++
glib/gstdio.c | 805 ++++++++++++++++++++++++++++---------------------
glib/gstdioprivate.h | 8 +-
glib/tests/fileutils.c | 228 ++++++++++++++
7 files changed, 824 insertions(+), 372 deletions(-)
---
diff --git a/gio/glocalfileinfo.c b/gio/glocalfileinfo.c
index 1387dc077..ca2e9234b 100644
--- a/gio/glocalfileinfo.c
+++ b/gio/glocalfileinfo.c
@@ -161,7 +161,7 @@ _g_local_file_info_create_fs_id (GLocalFileStat *statbuf)
static gchar *
read_link (const gchar *full_name)
{
-#if defined (HAVE_READLINK) || defined (G_OS_WIN32)
+#if defined (HAVE_READLINK)
gchar *buffer;
guint size;
@@ -171,12 +171,8 @@ read_link (const gchar *full_name)
while (1)
{
int read_size;
-
-#ifndef G_OS_WIN32
+
read_size = readlink (full_name, buffer, size);
-#else
- read_size = GLIB_PRIVATE_CALL (g_win32_readlink_utf8) (full_name, buffer, size);
-#endif
if (read_size < 0)
{
g_free (buffer);
@@ -190,6 +186,17 @@ read_link (const gchar *full_name)
size *= 2;
buffer = g_realloc (buffer, size);
}
+#elif defined (G_OS_WIN32)
+ gchar *buffer;
+ int read_size;
+
+ read_size = GLIB_PRIVATE_CALL (g_win32_readlink_utf8) (full_name, NULL, 0, &buffer, TRUE);
+ if (read_size < 0)
+ return NULL;
+ else if (read_size == 0)
+ return strdup ("");
+ else
+ return buffer;
#else
return NULL;
#endif
diff --git a/glib/gfileutils.c b/glib/gfileutils.c
index 1e7a771a9..b39d3d478 100644
--- a/glib/gfileutils.c
+++ b/glib/gfileutils.c
@@ -2090,7 +2090,7 @@ gchar *
g_file_read_link (const gchar *filename,
GError **error)
{
-#if defined (HAVE_READLINK) || defined (G_OS_WIN32)
+#if defined (HAVE_READLINK)
gchar *buffer;
size_t size;
gssize read_size;
@@ -2103,11 +2103,7 @@ g_file_read_link (const gchar *filename,
while (TRUE)
{
-#ifndef G_OS_WIN32
read_size = readlink (filename, buffer, size);
-#else
- read_size = g_win32_readlink_utf8 (filename, buffer, size);
-#endif
if (read_size < 0)
{
int saved_errno = errno;
@@ -2128,6 +2124,27 @@ g_file_read_link (const gchar *filename,
size *= 2;
buffer = g_realloc (buffer, size);
}
+#elif defined (G_OS_WIN32)
+ gchar *buffer;
+ gssize read_size;
+
+ g_return_val_if_fail (filename != NULL, NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ read_size = g_win32_readlink_utf8 (filename, NULL, 0, &buffer, TRUE);
+ if (read_size < 0)
+ {
+ int saved_errno = errno;
+ set_file_error (error,
+ filename,
+ _("Failed to read the symbolic link “%s”: %s"),
+ saved_errno);
+ return NULL;
+ }
+ else if (read_size == 0)
+ return strdup ("");
+ else
+ return buffer;
#else
g_return_val_if_fail (filename != NULL, NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
diff --git a/glib/glib-private.h b/glib/glib-private.h
index 31acd9386..facdbb26b 100644
--- a/glib/glib-private.h
+++ b/glib/glib-private.h
@@ -67,18 +67,20 @@ typedef struct {
/* See gstdio.c */
#ifdef G_OS_WIN32
- int (* g_win32_stat_utf8) (const gchar *filename,
- GWin32PrivateStat *buf);
+ int (* g_win32_stat_utf8) (const gchar *filename,
+ GWin32PrivateStat *buf);
- int (* g_win32_lstat_utf8) (const gchar *filename,
- GWin32PrivateStat *buf);
+ int (* g_win32_lstat_utf8) (const gchar *filename,
+ GWin32PrivateStat *buf);
- int (* g_win32_readlink_utf8) (const gchar *filename,
- gchar *buf,
- gsize buf_size);
+ int (* g_win32_readlink_utf8) (const gchar *filename,
+ gchar *buf,
+ gsize buf_size,
+ gchar **alloc_buf,
+ gboolean terminate);
- int (* g_win32_fstat) (int fd,
- GWin32PrivateStat *buf);
+ int (* g_win32_fstat) (int fd,
+ GWin32PrivateStat *buf);
#endif
diff --git a/glib/gstdio-private.c b/glib/gstdio-private.c
index 5eaaf09c5..6d86ce48c 100644
--- a/glib/gstdio-private.c
+++ b/glib/gstdio-private.c
@@ -75,3 +75,92 @@ _g_win32_strip_extended_ntobjm_prefix (gunichar2 *str,
return do_move;
}
+
+static int
+_g_win32_copy_and_maybe_terminate (const guchar *data,
+ gsize in_to_copy,
+ gunichar2 *buf,
+ gsize buf_size,
+ gunichar2 **alloc_buf,
+ gboolean terminate)
+{
+ gsize to_copy = in_to_copy;
+ /* Number of bytes we can use to add extra zeroes for NUL-termination.
+ * 0 means that we can destroy up to 2 bytes of data,
+ * 1 means that we can destroy up to 1 byte of data,
+ * 2 means that we do not perform destructive NUL-termination
+ */
+ gsize extra_bytes = terminate ? 2 : 0;
+ char *buf_in_chars;
+
+ if (to_copy == 0)
+ return 0;
+
+ /* 2 bytes is sizeof (wchar_t), for an extra NUL-terminator. */
+ if (buf)
+ {
+ if (to_copy >= buf_size)
+ {
+ extra_bytes = 0;
+ to_copy = buf_size;
+ }
+ else if (to_copy > buf_size - 2)
+ {
+ extra_bytes = 1;
+ }
+
+ memcpy (buf, data, to_copy);
+ }
+ else
+ {
+ /* Note that SubstituteNameLength is USHORT, so to_copy + 2, being
+ * gsize, never overflows.
+ */
+ *alloc_buf = g_malloc (to_copy + extra_bytes);
+ memcpy (*alloc_buf, data, to_copy);
+ }
+
+ if (!terminate)
+ return to_copy;
+
+ if (buf)
+ buf_in_chars = (char *) buf;
+ else
+ buf_in_chars = (char *) *alloc_buf;
+
+ if (to_copy >= 2 && buf_in_chars[to_copy - 2] == 0 &&
+ buf_in_chars[to_copy - 1] == 0)
+ {
+ /* Fully NUL-terminated, do nothing */
+ }
+ else if ((to_copy == 1 || buf_in_chars[to_copy - 2] != 0) &&
+ buf_in_chars[to_copy - 1] == 0)
+ {
+ /* Have one zero, try to add another one */
+ if (extra_bytes > 0)
+ {
+ /* Append trailing zero */
+ buf_in_chars[to_copy] = 0;
+ /* Be precise about the number of bytes we return */
+ to_copy += 1;
+ }
+ else if (to_copy >= 2)
+ {
+ /* No space for appending, destroy one byte */
+ buf_in_chars[to_copy - 2] = 0;
+ }
+ /* else there's no space at all (to_copy == 1), do nothing */
+ }
+ else if (extra_bytes > 0 || to_copy >= 2)
+ {
+ buf_in_chars[to_copy - 2 + extra_bytes] = 0;
+ buf_in_chars[to_copy - 1 + extra_bytes] = 0;
+ to_copy += extra_bytes;
+ }
+ else /* extra_bytes == 0 && to_copy == 1 */
+ {
+ buf_in_chars[0] = 0;
+ }
+
+ return to_copy;
+}
diff --git a/glib/gstdio.c b/glib/gstdio.c
index 4243fd5a7..51a1ddbe9 100644
--- a/glib/gstdio.c
+++ b/glib/gstdio.c
@@ -129,6 +129,8 @@ w32_error_to_errno (DWORD error_code)
* FT = UT * 10000000 + 116444736000000000.
* Therefore:
* UT = (FT - 116444736000000000) / 10000000.
+ * Converts FILETIME to unix epoch time in form
+ * of a signed 64-bit integer (can be negative).
*/
static gint64
_g_win32_filetime_to_unix_time (FILETIME *ft)
@@ -165,6 +167,9 @@ _g_win32_filetime_to_unix_time (FILETIME *ft)
# endif
# endif
+/* Uses filename and BHFI to fill a stat64 structure.
+ * Tries to reproduce the behaviour and quirks of MS C runtime stat().
+ */
static int
_g_win32_fill_statbuf_from_handle_info (const wchar_t *filename,
const wchar_t *filename_target,
@@ -258,67 +263,322 @@ _g_win32_fill_statbuf_from_handle_info (const wchar_t *filename,
return 0;
}
+/* Fills our private stat-like structure using data from
+ * a normal stat64 struct, BHFI, FSI and a reparse tag.
+ */
+static void
+_g_win32_fill_privatestat (const struct __stat64 *statbuf,
+ const BY_HANDLE_FILE_INFORMATION *handle_info,
+ const FILE_STANDARD_INFO *std_info,
+ DWORD reparse_tag,
+ GWin32PrivateStat *buf)
+{
+ 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;
+ 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;
+
+ buf->reparse_tag = reparse_tag;
+
+ buf->st_ctime = statbuf->st_ctime;
+ buf->st_atime = statbuf->st_atime;
+ buf->st_mtime = statbuf->st_mtime;
+}
+
+/* Read the link data from a symlink/mountpoint represented
+ * by the handle. Also reads reparse tag.
+ * @reparse_tag receives the tag. Can be %NULL if @buf or @alloc_buf
+ * is non-NULL.
+ * @buf receives the link data. Can be %NULL if reparse_tag is non-%NULL.
+ * Mutually-exclusive with @alloc_buf.
+ * @buf_size is the size of the @buf, in bytes.
+ * @alloc_buf points to a location where internally-allocated buffer
+ * pointer will be written. That buffer receives the
+ * link data. Mutually-exclusive with @buf.
+ * @terminate ensures that the buffer is NUL-terminated if
+ * it isn't already. Note that this can erase useful
+ * data if @buf is provided and @buf_size is too small.
+ * Specifically, with @buf_size <= 2 the buffer will
+ * receive an empty string, even if there is some
+ * data in the reparse point.
+ * The contents of @buf or @alloc_buf are presented as-is - could
+ * be non-NUL-terminated (unless @terminate is %TRUE) or even malformed.
+ * Returns the number of bytes (!) placed into @buf or @alloc_buf,
+ * including NUL-terminator (if any).
+ *
+ * Returned value of 0 means that there's no recognizable data in the
+ * reparse point. @alloc_buf will not be allocated in that case,
+ * and @buf will be left unmodified.
+ *
+ * If @buf and @alloc_buf are %NULL, returns 0 to indicate success.
+ * Returns -1 to indicate an error, sets errno.
+ */
+static int
+_g_win32_readlink_handle_raw (HANDLE h,
+ DWORD *reparse_tag,
+ gunichar2 *buf,
+ gsize buf_size,
+ gunichar2 **alloc_buf,
+ gboolean terminate)
+{
+ DWORD error_code;
+ DWORD returned_bytes = 0;
+ BYTE *data;
+ gsize to_copy;
+ /* This is 16k. It's impossible to make DeviceIoControl() tell us
+ * the required size. NtFsControlFile() does have such a feature,
+ * but for some reason it doesn't work with CreateFile()-returned handles.
+ * The only alternative is to repeatedly call DeviceIoControl()
+ * with bigger and bigger buffers, until it succeeds.
+ * We choose to sacrifice stack space for speed.
+ */
+ BYTE max_buffer[sizeof (REPARSE_DATA_BUFFER) + MAXIMUM_REPARSE_DATA_BUFFER_SIZE] = {0,};
+ DWORD max_buffer_size = sizeof (REPARSE_DATA_BUFFER) + MAXIMUM_REPARSE_DATA_BUFFER_SIZE;
+ REPARSE_DATA_BUFFER *rep_buf;
+
+ g_return_val_if_fail ((buf != NULL || alloc_buf != NULL || reparse_tag != NULL) &&
+ (buf == NULL || alloc_buf == NULL),
+ -1);
+
+ if (!DeviceIoControl (h, FSCTL_GET_REPARSE_POINT, NULL, 0,
+ max_buffer,
+ max_buffer_size,
+ &returned_bytes, NULL))
+ {
+ error_code = GetLastError ();
+ errno = w32_error_to_errno (error_code);
+ return -1;
+ }
+
+ rep_buf = (REPARSE_DATA_BUFFER *) max_buffer;
+
+ if (reparse_tag != NULL)
+ *reparse_tag = rep_buf->ReparseTag;
+
+ if (buf == NULL && alloc_buf == NULL)
+ return 0;
+
+ if (rep_buf->ReparseTag == IO_REPARSE_TAG_SYMLINK)
+ {
+ data = &((BYTE *)
rep_buf->SymbolicLinkReparseBuffer.PathBuffer)[rep_buf->SymbolicLinkReparseBuffer.SubstituteNameOffset];
+
+ to_copy = rep_buf->SymbolicLinkReparseBuffer.SubstituteNameLength;
+ }
+ else if (rep_buf->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT)
+ {
+ data = &((BYTE *)
rep_buf->MountPointReparseBuffer.PathBuffer)[rep_buf->MountPointReparseBuffer.SubstituteNameOffset];
+
+ to_copy = rep_buf->MountPointReparseBuffer.SubstituteNameLength;
+ }
+ else
+ to_copy = 0;
+
+ return _g_win32_copy_and_maybe_terminate (data, to_copy, buf, buf_size, alloc_buf, terminate);
+}
+
+/* Read the link data from a symlink/mountpoint represented
+ * by the @filename.
+ * @filename is the name of the file.
+ * @reparse_tag receives the tag. Can be %NULL if @buf or @alloc_buf
+ * is non-%NULL.
+ * @buf receives the link data. Mutually-exclusive with @alloc_buf.
+ * @buf_size is the size of the @buf, in bytes.
+ * @alloc_buf points to a location where internally-allocated buffer
+ * pointer will be written. That buffer receives the
+ * link data. Mutually-exclusive with @buf.
+ * @terminate ensures that the buffer is NUL-terminated if
+ * it isn't already
+ * The contents of @buf or @alloc_buf are presented as-is - could
+ * be non-NUL-terminated (unless @terminate is TRUE) or even malformed.
+ * Returns the number of bytes (!) placed into @buf or @alloc_buf.
+ * Returned value of 0 means that there's no recognizable data in the
+ * reparse point. @alloc_buf will not be allocated in that case,
+ * and @buf will be left unmodified.
+ * If @buf and @alloc_buf are %NULL, returns 0 to indicate success.
+ * Returns -1 to indicate an error, sets errno.
+ */
+static int
+_g_win32_readlink_utf16_raw (const gunichar2 *filename,
+ DWORD *reparse_tag,
+ gunichar2 *buf,
+ gsize buf_size,
+ gunichar2 **alloc_buf,
+ gboolean terminate)
+{
+ HANDLE h;
+ DWORD attributes;
+ DWORD to_copy;
+ DWORD error_code;
+
+ 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_EA,
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
+ 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;
+ }
+
+ to_copy = _g_win32_readlink_handle_raw (h, reparse_tag, buf, buf_size, alloc_buf, terminate);
+
+ CloseHandle (h);
+
+ return to_copy;
+}
+
+/* Read the link data from a symlink/mountpoint represented
+ * by a UTF-16 filename or a file handle.
+ * @filename is the name of the file. Mutually-exclusive with @file_handle.
+ * @file_handle is the handle of the file. Mutually-exclusive with @filename.
+ * @reparse_tag receives the tag. Can be %NULL if @buf or @alloc_buf
+ * is non-%NULL.
+ * @buf receives the link data. Mutually-exclusive with @alloc_buf.
+ * @buf_size is the size of the @buf, in bytes.
+ * @alloc_buf points to a location where internally-allocated buffer
+ * pointer will be written. That buffer receives the
+ * link data. Mutually-exclusive with @buf.
+ * @terminate ensures that the buffer is NUL-terminated if
+ * it isn't already
+ * The contents of @buf or @alloc_buf are adjusted
+ * (extended or nt object manager prefix is stripped),
+ * but otherwise they are presented as-is - could be non-NUL-terminated
+ * (unless @terminate is TRUE) or even malformed.
+ * Returns the number of bytes (!) placed into @buf or @alloc_buf.
+ * Returned value of 0 means that there's no recognizable data in the
+ * reparse point. @alloc_buf will not be allocated in that case,
+ * and @buf will be left unmodified.
+ * Returns -1 to indicate an error, sets errno.
+ */
+static int
+_g_win32_readlink_utf16_handle (const gunichar2 *filename,
+ HANDLE file_handle,
+ DWORD *reparse_tag,
+ gunichar2 *buf,
+ gsize buf_size,
+ gunichar2 **alloc_buf,
+ gboolean terminate)
+{
+ int result;
+ gsize string_size;
+
+ g_return_val_if_fail ((buf != NULL || alloc_buf != NULL || reparse_tag != NULL) &&
+ (filename != NULL || file_handle != NULL) &&
+ (buf == NULL || alloc_buf == NULL) &&
+ (filename == NULL || file_handle == NULL),
+ -1);
+
+ if (filename)
+ result = _g_win32_readlink_utf16_raw (filename, reparse_tag, buf, buf_size, alloc_buf, terminate);
+ else
+ result = _g_win32_readlink_handle_raw (file_handle, reparse_tag, buf, buf_size, alloc_buf, terminate);
+
+ 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. Unless it has no 'x:' drive
+ * letter part after the prefix, in which case we leave everything
+ * as-is, because the path could be "\\??\\Volume{GUID}" - stripping
+ * the prefix will allow it to be confused with relative links
+ * targeting "Volume{GUID}".
+ */
+ string_size = result / sizeof (gunichar2);
+ _g_win32_strip_extended_ntobjm_prefix (buf ? buf : *alloc_buf, &string_size);
+
+ return string_size * sizeof (gunichar2);
+}
+
+/* Works like stat() or lstat(), depending on the value of @for_symlink,
+ * but accepts filename in UTF-16 and fills our custom stat structure.
+ * The @filename must not have trailing slashes.
+ */
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;
+ DWORD immediate_attributes;
+ DWORD open_flags;
+ gboolean is_directory;
+ DWORD reparse_tag = 0;
+ DWORD error_code;
+ BOOL succeeded_so_far;
+ HANDLE file_handle;
- if (fd < 0)
- {
- immediate_attributes = GetFileAttributesW (filename);
+ immediate_attributes = GetFileAttributesW (filename);
- if (immediate_attributes == INVALID_FILE_ATTRIBUTES)
- {
- error_code = GetLastError ();
- errno = w32_error_to_errno (error_code);
+ if (immediate_attributes == INVALID_FILE_ATTRIBUTES)
+ {
+ error_code = GetLastError ();
+ errno = w32_error_to_errno (error_code);
- return -1;
- }
+ return -1;
+ }
- is_symlink = (immediate_attributes & FILE_ATTRIBUTE_REPARSE_POINT) == FILE_ATTRIBUTE_REPARSE_POINT;
- is_directory = (immediate_attributes & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY;
+ 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;
+ open_flags = FILE_ATTRIBUTE_NORMAL;
- if (for_symlink && is_symlink)
- open_flags |= FILE_FLAG_OPEN_REPARSE_POINT;
+ if (for_symlink && is_symlink)
+ open_flags |= FILE_FLAG_OPEN_REPARSE_POINT;
- if (is_directory)
- open_flags |= FILE_FLAG_BACKUP_SEMANTICS;
+ 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);
+ file_handle = CreateFileW (filename, FILE_READ_ATTRIBUTES | FILE_READ_EA,
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
+ 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
+ if (file_handle == INVALID_HANDLE_VALUE)
{
- file_handle = (HANDLE) _get_osfhandle (fd);
-
- if (file_handle == INVALID_HANDLE_VALUE)
- return -1;
+ error_code = GetLastError ();
+ errno = w32_error_to_errno (error_code);
+ return -1;
}
succeeded_so_far = GetFileInformationByHandle (file_handle,
@@ -336,182 +596,104 @@ _g_win32_stat_utf16_no_trailing_slashes (const gunichar2 *filename,
if (!succeeded_so_far)
{
- if (fd < 0)
- CloseHandle (file_handle);
+ 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.
+ * We already have a handle open for symlink, use that.
+ * For the target we have to specify a filename, and the function
+ * will open another handle internally.
*/
- if (fd < 0)
+ if (is_symlink &&
+ _g_win32_readlink_utf16_handle (for_symlink ? NULL : filename,
+ for_symlink ? file_handle : NULL,
+ &reparse_tag,
+ NULL, 0,
+ for_symlink ? NULL : &filename_target,
+ TRUE) < 0)
{
- memset (&finddata, 0, sizeof (finddata));
+ CloseHandle (file_handle);
+ return -1;
+ }
- if (handle_info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
- {
- 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);
- }
+ CloseHandle (file_handle);
- if (is_symlink && !for_symlink)
- {
- /* If filename is a symlink, but we need the target.
- * To get information about the target we need to resolve
- * the symlink first.
- */
- 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)
- {
- /* Pretend that new_len doesn't count the terminating NUL char,
- * and ask for a bit more space than is needed, and allocate even more.
- */
- filename_target_len = new_len + 3;
- filename_target = g_malloc ((filename_target_len + 1) * 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);
- }
- else if (new_len == 0)
- {
- g_clear_pointer (&filename_target, g_free);
- }
- /* GetFinalPathNameByHandle() is documented to return extended paths,
- * strip the extended prefix, if it is followed by a drive letter
- * and a colon. Otherwise keep it (the path could be
- * \\\\?\\Volume{GUID}\\ - it's only usable in extended form).
- */
- else if (new_len > 0)
- {
- gsize len = new_len;
-
- /* Account for NUL-terminator maybe not being counted.
- * This is why we overallocated earlier.
- */
- if (filename_target[len] != L'\0')
- {
- len++;
- filename_target[len] = L'\0';
- }
-
- _g_win32_strip_extended_ntobjm_prefix (filename_target, &len);
- new_len = len;
- }
-
- }
-
- if (new_len == 0)
- succeeded_so_far = FALSE;
- }
+ _g_win32_fill_statbuf_from_handle_info (filename,
+ filename_target,
+ &handle_info,
+ &statbuf);
+ g_free (filename_target);
+ _g_win32_fill_privatestat (&statbuf,
+ &handle_info,
+ &std_info,
+ reparse_tag,
+ buf);
- 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.
- */
+ return 0;
+}
- if (!succeeded_so_far)
- {
- errno = w32_error_to_errno (error_code);
- return -1;
- }
+/* Works like fstat(), but fills our custom stat structure. */
+static int
+_g_win32_stat_fd (int fd,
+ GWin32PrivateStat *buf)
+{
+ HANDLE file_handle;
+ gboolean succeeded_so_far;
+ DWORD error_code;
+ struct __stat64 statbuf;
+ BY_HANDLE_FILE_INFORMATION handle_info;
+ FILE_STANDARD_INFO std_info;
+ DWORD reparse_tag = 0;
+ gboolean is_symlink = FALSE;
- /*
- * We can't use _wstat64() here, because with UCRT it now gives
- * information about the target, even if we want information about
- * the link itself (unlike MSVCRT, which gave information about
- * the link, and if we needed information about the target we were
- * able to resolve it by ourselves prior to calling _wstat64()).
- */
- if (fd < 0)
- result = _g_win32_fill_statbuf_from_handle_info (filename,
- filename_target,
- &handle_info,
- &statbuf);
- else
- result = _fstat64 (fd, &statbuf);
+ file_handle = (HANDLE) _get_osfhandle (fd);
- if (result != 0)
- {
- int errsv = errno;
+ if (file_handle == INVALID_HANDLE_VALUE)
+ return -1;
- g_free (filename_target);
- errno = errsv;
+ 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)
+ {
+ errno = w32_error_to_errno (error_code);
return -1;
}
- g_free (filename_target);
+ is_symlink = (handle_info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) == FILE_ATTRIBUTE_REPARSE_POINT;
- 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 (is_symlink &&
+ _g_win32_readlink_handle_raw (file_handle, &reparse_tag, NULL, 0, NULL, FALSE) < 0)
+ return -1;
- if (fd < 0 && buf->attributes & FILE_ATTRIBUTE_REPARSE_POINT)
- buf->reparse_tag = finddata.dwReserved0;
- else
- buf->reparse_tag = 0;
+ if (_fstat64 (fd, &statbuf) != 0)
+ return -1;
- buf->st_ctime = statbuf.st_ctime;
- buf->st_atime = statbuf.st_atime;
- buf->st_mtime = statbuf.st_mtime;
+ _g_win32_fill_privatestat (&statbuf,
+ &handle_info,
+ &std_info,
+ reparse_tag,
+ buf);
return 0;
}
+/* Works like stat() or lstat(), depending on the value of @for_symlink,
+ * but accepts filename in UTF-8 and fills our custom stat structure.
+ */
static int
_g_win32_stat_utf8 (const gchar *filename,
GWin32PrivateStat *buf,
@@ -544,13 +726,16 @@ _g_win32_stat_utf8 (const gchar *filename,
return -1;
}
- result = _g_win32_stat_utf16_no_trailing_slashes (wfilename, -1, buf, for_symlink);
+ result = _g_win32_stat_utf16_no_trailing_slashes (wfilename, buf, for_symlink);
g_free (wfilename);
return result;
}
+/* Works like stat(), but accepts filename in UTF-8
+ * and fills our custom stat structure.
+ */
int
g_win32_stat_utf8 (const gchar *filename,
GWin32PrivateStat *buf)
@@ -558,6 +743,9 @@ g_win32_stat_utf8 (const gchar *filename,
return _g_win32_stat_utf8 (filename, buf, FALSE);
}
+/* Works like lstat(), but accepts filename in UTF-8
+ * and fills our custom stat structure.
+ */
int
g_win32_lstat_utf8 (const gchar *filename,
GWin32PrivateStat *buf)
@@ -565,138 +753,14 @@ g_win32_lstat_utf8 (const gchar *filename,
return _g_win32_stat_utf8 (filename, buf, TRUE);
}
+/* Works like fstat(), but accepts filename in UTF-8
+ * and fills our custom stat structure.
+ */
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_EA,
- FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
- 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)
-{
- int result = _g_win32_readlink_utf16_raw (filename, buf, buf_size);
- gsize string_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. Unless it has no 'x:' drive
- * letter part after the prefix, in which case we leave everything
- * as-is, because the path could be "\??\Volume{GUID}" - stripping
- * the prefix will allow it to be confused with relative links
- * targeting "Volume{GUID}".
- */
- string_size = result / sizeof (gunichar2);
- _g_win32_strip_extended_ntobjm_prefix (buf, &string_size);
-
- return string_size * sizeof (gunichar2);
+ return _g_win32_stat_fd (fd, buf);
}
static gchar *
@@ -719,13 +783,54 @@ _g_win32_get_mode_alias (const gchar *mode)
return alias;
}
+/**
+ * g_win32_readlink_utf8:
+ * @filename: (type filename): a pathname in UTF-8
+ * @buf: (array length=buf_size) : a buffer to receive the reparse point
+ * target path. Mutually-exclusive
+ * with @alloc_buf.
+ * @buf_size: size of the @buf, in bytes
+ * @alloc_buf: points to a location where internally-allocated buffer
+ * pointer will be written. That buffer receives the
+ * link data. Mutually-exclusive with @buf.
+ * @terminate: ensures that the buffer is NUL-terminated if
+ * it isn't already. If %FALSE, the returned string
+ * might not be NUL-terminated (depends entirely on
+ * what the contents of the filesystem are).
+ *
+ * Tries to read the reparse point indicated by @filename, filling
+ * @buf or @alloc_buf with the path that the reparse point redirects to.
+ * The path will be UTF-8-encoded, and an extended path prefix
+ * or a NT object manager prefix will be removed from it, if
+ * possible, but otherwise the path is returned as-is. Specifically,
+ * it could be a "\\\\Volume{GUID}\\" path. It also might use
+ * backslashes as path separators.
+ *
+ * Returns: -1 on error (sets errno), 0 if there's no (recognizable)
+ * path in the reparse point (@alloc_buf will not be allocated in that case,
+ * and @buf will be left unmodified),
+ * or the number of bytes placed into @buf otherwise,
+ * including NUL-terminator (if present or if @terminate is TRUE).
+ * The buffer returned via @alloc_buf should be freed with g_free().
+ *
+ * Since: 2.60
+ */
int
-g_win32_readlink_utf8 (const gchar *filename,
- gchar *buf,
- gsize buf_size)
+g_win32_readlink_utf8 (const gchar *filename,
+ gchar *buf,
+ gsize buf_size,
+ gchar **alloc_buf,
+ gboolean terminate)
{
wchar_t *wfilename;
int result;
+ wchar_t *buf_utf16;
+ glong tmp_len;
+ gchar *tmp;
+
+ g_return_val_if_fail ((buf != NULL || alloc_buf != NULL) &&
+ (buf == NULL || alloc_buf == NULL),
+ -1);
wfilename = g_utf8_to_utf16 (filename, -1, NULL, NULL, NULL);
@@ -735,39 +840,41 @@ g_win32_readlink_utf8 (const gchar *filename,
return -1;
}
- result = _g_win32_readlink_utf16 (wfilename, (gunichar2 *) buf, buf_size);
+ result = _g_win32_readlink_utf16_handle (wfilename, NULL, NULL,
+ NULL, 0, &buf_utf16, terminate);
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 (result <= 0)
+ return result;
- if (tmp_len > buf_size - 1)
- tmp_len = buf_size - 1;
+ tmp = g_utf16_to_utf8 (buf_utf16,
+ result / sizeof (gunichar2),
+ NULL,
+ &tmp_len,
+ NULL);
- 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);
+ g_free (buf_utf16);
+
+ if (tmp == NULL)
+ {
+ errno = EINVAL;
+ return -1;
}
- return result;
+ if (alloc_buf)
+ {
+ *alloc_buf = tmp;
+ return tmp_len;
+ }
+
+ if (tmp_len > buf_size)
+ tmp_len = buf_size;
+
+ memcpy (buf, tmp, tmp_len);
+ g_free (tmp);
+
+ return tmp_len;
}
#endif
diff --git a/glib/gstdioprivate.h b/glib/gstdioprivate.h
index a100abb5e..71565388a 100644
--- a/glib/gstdioprivate.h
+++ b/glib/gstdioprivate.h
@@ -51,9 +51,11 @@ int g_win32_stat_utf8 (const gchar *filename,
int g_win32_lstat_utf8 (const gchar *filename,
GWin32PrivateStat *buf);
-int g_win32_readlink_utf8 (const gchar *filename,
- gchar *buf,
- gsize buf_size);
+int g_win32_readlink_utf8 (const gchar *filename,
+ gchar *buf,
+ gsize buf_size,
+ gchar **alloc_buf,
+ gboolean terminate);
int g_win32_fstat (int fd,
GWin32PrivateStat *buf);
diff --git a/glib/tests/fileutils.c b/glib/tests/fileutils.c
index 3ef6615d0..adc735d79 100644
--- a/glib/tests/fileutils.c
+++ b/glib/tests/fileutils.c
@@ -1152,6 +1152,233 @@ test_win32_pathstrip (void)
}
}
+#define g_assert_memcmp(m1, cmp, m2, memlen, m1hex, m2hex, testcase_num) \
+G_STMT_START { \
+ if (memcmp (m1, m2, memlen) cmp 0); else \
+ g_assertion_message_cmpstr (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \
+ #m1hex " " #cmp " " #m2hex, m1hex, #cmp, m2hex); \
+} G_STMT_END
+
+static gchar *
+to_hex (const guchar *buf,
+ gsize len)
+{
+ gsize i;
+ GString *s = g_string_new (NULL);
+ if (len > 0)
+ g_string_append_printf (s, "%02x", buf[0]);
+ for (i = 1; i < len; i++)
+ g_string_append_printf (s, " %02x", buf[i]);
+ return g_string_free (s, FALSE);
+}
+
+static void
+test_win32_zero_terminate_symlink (void)
+{
+ gsize i;
+#define TESTCASE(data, len_mod, use_buf, buf_size, terminate, reported_len, returned_string) \
+ { (const guchar *) data, wcslen (data) * 2 + len_mod, use_buf, buf_size, terminate, reported_len, (guchar
*) returned_string},
+
+ struct
+ {
+ const guchar *data;
+ gsize data_size;
+ gboolean use_buf;
+ gsize buf_size;
+ gboolean terminate;
+ int reported_len;
+ const guchar *returned_string;
+ } testcases[] = {
+ TESTCASE (L"foobar", +2, TRUE, 12 + 4, FALSE, 12 + 2, "f\0o\0o\0b\0a\0r\0\0\0")
+ TESTCASE (L"foobar", +2, TRUE, 12 + 3, FALSE, 12 + 2, "f\0o\0o\0b\0a\0r\0\0\0")
+ TESTCASE (L"foobar", +2, TRUE, 12 + 2, FALSE, 12 + 2, "f\0o\0o\0b\0a\0r\0\0\0")
+ TESTCASE (L"foobar", +2, TRUE, 12 + 1, FALSE, 12 + 1, "f\0o\0o\0b\0a\0r\0\0")
+ TESTCASE (L"foobar", +2, TRUE, 12 + 0, FALSE, 12 + 0, "f\0o\0o\0b\0a\0r\0")
+ TESTCASE (L"foobar", +2, TRUE, 12 - 1, FALSE, 12 - 1, "f\0o\0o\0b\0a\0r")
+ TESTCASE (L"foobar", +2, TRUE, 12 - 2, FALSE, 12 - 2, "f\0o\0o\0b\0a\0")
+ TESTCASE (L"foobar", +2, TRUE, 12 - 3, FALSE, 12 - 3, "f\0o\0o\0b\0a")
+ TESTCASE (L"foobar", +1, TRUE, 12 + 4, FALSE, 12 + 1, "f\0o\0o\0b\0a\0r\0\0")
+ TESTCASE (L"foobar", +1, TRUE, 12 + 3, FALSE, 12 + 1, "f\0o\0o\0b\0a\0r\0\0")
+ TESTCASE (L"foobar", +1, TRUE, 12 + 2, FALSE, 12 + 1, "f\0o\0o\0b\0a\0r\0\0")
+ TESTCASE (L"foobar", +1, TRUE, 12 + 1, FALSE, 12 + 1, "f\0o\0o\0b\0a\0r\0\0")
+ TESTCASE (L"foobar", +1, TRUE, 12 + 0, FALSE, 12 + 0, "f\0o\0o\0b\0a\0r\0")
+ TESTCASE (L"foobar", +1, TRUE, 12 - 1, FALSE, 12 - 1, "f\0o\0o\0b\0a\0r")
+ TESTCASE (L"foobar", +1, TRUE, 12 - 2, FALSE, 12 - 2, "f\0o\0o\0b\0a\0")
+ TESTCASE (L"foobar", +1, TRUE, 12 - 3, FALSE, 12 - 3, "f\0o\0o\0b\0a")
+ TESTCASE (L"foobar", +0, TRUE, 12 + 4, FALSE, 12 + 0, "f\0o\0o\0b\0a\0r\0")
+ TESTCASE (L"foobar", +0, TRUE, 12 + 3, FALSE, 12 + 0, "f\0o\0o\0b\0a\0r\0")
+ TESTCASE (L"foobar", +0, TRUE, 12 + 2, FALSE, 12 + 0, "f\0o\0o\0b\0a\0r\0")
+ TESTCASE (L"foobar", +0, TRUE, 12 + 1, FALSE, 12 + 0, "f\0o\0o\0b\0a\0r\0")
+ TESTCASE (L"foobar", +0, TRUE, 12 + 0, FALSE, 12 + 0, "f\0o\0o\0b\0a\0r\0")
+ TESTCASE (L"foobar", +0, TRUE, 12 - 1, FALSE, 12 - 1, "f\0o\0o\0b\0a\0r")
+ TESTCASE (L"foobar", +0, TRUE, 12 - 2, FALSE, 12 - 2, "f\0o\0o\0b\0a\0")
+ TESTCASE (L"foobar", +0, TRUE, 12 - 3, FALSE, 12 - 3, "f\0o\0o\0b\0a")
+ TESTCASE (L"foobar", -1, TRUE, 12 + 3, FALSE, 12 - 1, "f\0o\0o\0b\0a\0r")
+ TESTCASE (L"foobar", -1, TRUE, 12 + 2, FALSE, 12 - 1, "f\0o\0o\0b\0a\0r")
+ TESTCASE (L"foobar", -1, TRUE, 12 + 1, FALSE, 12 - 1, "f\0o\0o\0b\0a\0r")
+ TESTCASE (L"foobar", -1, TRUE, 12 + 0, FALSE, 12 - 1, "f\0o\0o\0b\0a\0r")
+ TESTCASE (L"foobar", -1, TRUE, 12 - 1, FALSE, 12 - 1, "f\0o\0o\0b\0a\0r")
+ TESTCASE (L"foobar", -1, TRUE, 12 - 2, FALSE, 12 - 2, "f\0o\0o\0b\0a\0")
+ TESTCASE (L"foobar", -1, TRUE, 12 - 3, FALSE, 12 - 3, "f\0o\0o\0b\0a")
+ TESTCASE (L"foobar", -1, TRUE, 12 - 4, FALSE, 12 - 4, "f\0o\0o\0b\0")
+ TESTCASE (L"foobar", -2, TRUE, 12 + 2, FALSE, 12 - 2, "f\0o\0o\0b\0a\0")
+ TESTCASE (L"foobar", -2, TRUE, 12 + 1, FALSE, 12 - 2, "f\0o\0o\0b\0a\0")
+ TESTCASE (L"foobar", -2, TRUE, 12 + 0, FALSE, 12 - 2, "f\0o\0o\0b\0a\0")
+ TESTCASE (L"foobar", -2, TRUE, 12 - 1, FALSE, 12 - 2, "f\0o\0o\0b\0a\0")
+ TESTCASE (L"foobar", -2, TRUE, 12 - 2, FALSE, 12 - 2, "f\0o\0o\0b\0a\0")
+ TESTCASE (L"foobar", -2, TRUE, 12 - 3, FALSE, 12 - 3, "f\0o\0o\0b\0a")
+ TESTCASE (L"foobar", -2, TRUE, 12 - 4, FALSE, 12 - 4, "f\0o\0o\0b\0")
+ TESTCASE (L"foobar", -2, TRUE, 12 - 5, FALSE, 12 - 5, "f\0o\0o\0b")
+ TESTCASE (L"foobar", +2, TRUE, 12 + 4, TRUE, 12 + 2, "f\0o\0o\0b\0a\0r\0\0\0")
+ TESTCASE (L"foobar", +2, TRUE, 12 + 3, TRUE, 12 + 2, "f\0o\0o\0b\0a\0r\0\0\0")
+ TESTCASE (L"foobar", +2, TRUE, 12 + 2, TRUE, 12 + 2, "f\0o\0o\0b\0a\0r\0\0\0")
+ TESTCASE (L"foobar", +2, TRUE, 12 + 1, TRUE, 12 + 1, "f\0o\0o\0b\0a\0r\0\0")
+ TESTCASE (L"foobar", +2, TRUE, 12 + 0, TRUE, 12 + 0, "f\0o\0o\0b\0a\0\0\0")
+ TESTCASE (L"foobar", +2, TRUE, 12 - 1, TRUE, 12 - 1, "f\0o\0o\0b\0a\0\0")
+ TESTCASE (L"foobar", +2, TRUE, 12 - 2, TRUE, 12 - 2, "f\0o\0o\0b\0\0\0")
+ TESTCASE (L"foobar", +2, TRUE, 12 - 3, TRUE, 12 - 3, "f\0o\0o\0b\0\0")
+ TESTCASE (L"foobar", +1, TRUE, 12 + 4, TRUE, 12 + 1, "f\0o\0o\0b\0a\0r\0\0")
+ TESTCASE (L"foobar", +1, TRUE, 12 + 3, TRUE, 12 + 1, "f\0o\0o\0b\0a\0r\0\0")
+ TESTCASE (L"foobar", +1, TRUE, 12 + 2, TRUE, 12 + 1, "f\0o\0o\0b\0a\0r\0\0")
+ TESTCASE (L"foobar", +1, TRUE, 12 + 1, TRUE, 12 + 1, "f\0o\0o\0b\0a\0r\0\0")
+ TESTCASE (L"foobar", +1, TRUE, 12 + 0, TRUE, 12 + 0, "f\0o\0o\0b\0a\0\0\0")
+ TESTCASE (L"foobar", +1, TRUE, 12 - 1, TRUE, 12 - 1, "f\0o\0o\0b\0a\0\0")
+ TESTCASE (L"foobar", +1, TRUE, 12 - 2, TRUE, 12 - 2, "f\0o\0o\0b\0\0\0")
+ TESTCASE (L"foobar", +1, TRUE, 12 - 3, TRUE, 12 - 3, "f\0o\0o\0b\0\0")
+ TESTCASE (L"foobar", +0, TRUE, 12 + 4, TRUE, 12 + 1, "f\0o\0o\0b\0a\0r\0\0")
+ TESTCASE (L"foobar", +0, TRUE, 12 + 3, TRUE, 12 + 1, "f\0o\0o\0b\0a\0r\0\0")
+ TESTCASE (L"foobar", +0, TRUE, 12 + 2, TRUE, 12 + 1, "f\0o\0o\0b\0a\0r\0\0")
+ TESTCASE (L"foobar", +0, TRUE, 12 + 1, TRUE, 12 + 1, "f\0o\0o\0b\0a\0r\0\0")
+ TESTCASE (L"foobar", +0, TRUE, 12 + 0, TRUE, 12 + 0, "f\0o\0o\0b\0a\0\0\0")
+ TESTCASE (L"foobar", +0, TRUE, 12 - 1, TRUE, 12 - 1, "f\0o\0o\0b\0a\0\0")
+ TESTCASE (L"foobar", +0, TRUE, 12 - 2, TRUE, 12 - 2, "f\0o\0o\0b\0\0\0")
+ TESTCASE (L"foobar", +0, TRUE, 12 - 3, TRUE, 12 - 3, "f\0o\0o\0b\0\0")
+ TESTCASE (L"foobar", -1, TRUE, 12 + 3, TRUE, 12 + 1, "f\0o\0o\0b\0a\0r\0\0")
+ TESTCASE (L"foobar", -1, TRUE, 12 + 2, TRUE, 12 + 1, "f\0o\0o\0b\0a\0r\0\0")
+ TESTCASE (L"foobar", -1, TRUE, 12 + 1, TRUE, 12 + 1, "f\0o\0o\0b\0a\0r\0\0")
+ TESTCASE (L"foobar", -1, TRUE, 12 + 0, TRUE, 12 + 0, "f\0o\0o\0b\0a\0\0\0")
+ TESTCASE (L"foobar", -1, TRUE, 12 - 1, TRUE, 12 - 1, "f\0o\0o\0b\0a\0\0")
+ TESTCASE (L"foobar", -1, TRUE, 12 - 2, TRUE, 12 - 2, "f\0o\0o\0b\0\0\0")
+ TESTCASE (L"foobar", -1, TRUE, 12 - 3, TRUE, 12 - 3, "f\0o\0o\0b\0\0")
+ TESTCASE (L"foobar", -1, TRUE, 12 - 4, TRUE, 12 - 4, "f\0o\0o\0\0\0")
+ TESTCASE (L"foobar", -2, TRUE, 12 + 2, TRUE, 12 - 1, "f\0o\0o\0b\0a\0\0")
+ TESTCASE (L"foobar", -2, TRUE, 12 + 1, TRUE, 12 - 1, "f\0o\0o\0b\0a\0\0")
+ TESTCASE (L"foobar", -2, TRUE, 12 + 0, TRUE, 12 - 1, "f\0o\0o\0b\0a\0\0")
+ TESTCASE (L"foobar", -2, TRUE, 12 - 1, TRUE, 12 - 1, "f\0o\0o\0b\0a\0\0")
+ TESTCASE (L"foobar", -2, TRUE, 12 - 2, TRUE, 12 - 2, "f\0o\0o\0b\0\0\0")
+ TESTCASE (L"foobar", -2, TRUE, 12 - 3, TRUE, 12 - 3, "f\0o\0o\0b\0\0")
+ TESTCASE (L"foobar", -2, TRUE, 12 - 4, TRUE, 12 - 4, "f\0o\0o\0\0\0")
+ TESTCASE (L"foobar", -2, TRUE, 12 - 5, TRUE, 12 - 5, "f\0o\0o\0\0")
+ TESTCASE (L"foobar", +2, FALSE, 0, FALSE, 12 + 2, "f\0o\0o\0b\0a\0r\0\0\0")
+ TESTCASE (L"foobar", +1, FALSE, 0, FALSE, 12 + 1, "f\0o\0o\0b\0a\0r\0\0")
+ TESTCASE (L"foobar", +0, FALSE, 0, FALSE, 12 + 0, "f\0o\0o\0b\0a\0r\0")
+ TESTCASE (L"foobar", -1, FALSE, 0, FALSE, 12 - 1, "f\0o\0o\0b\0a\0r")
+ TESTCASE (L"foobar", -2, FALSE, 0, FALSE, 12 - 2, "f\0o\0o\0b\0a\0")
+ TESTCASE (L"foobar", +2, FALSE, 0, TRUE, 12 + 2, "f\0o\0o\0b\0a\0r\0\0\0")
+ TESTCASE (L"foobar", +1, FALSE, 0, TRUE, 12 + 1, "f\0o\0o\0b\0a\0r\0\0")
+ TESTCASE (L"foobar", +0, FALSE, 0, TRUE, 12 + 1, "f\0o\0o\0b\0a\0r\0\0")
+ TESTCASE (L"foobar", -1, FALSE, 0, TRUE, 12 + 1, "f\0o\0o\0b\0a\0r\0\0")
+ TESTCASE (L"foobar", -2, FALSE, 0, TRUE, 12 - 1, "f\0o\0o\0b\0a\0\0")
+ TESTCASE (L"x", +2, TRUE, 2 + 4, FALSE, 2 + 2, "x\0\0\0")
+ TESTCASE (L"x", +2, TRUE, 2 + 3, FALSE, 2 + 2, "x\0\0\0")
+ TESTCASE (L"x", +2, TRUE, 2 + 2, FALSE, 2 + 2, "x\0\0\0")
+ TESTCASE (L"x", +2, TRUE, 2 + 1, FALSE, 2 + 1, "x\0\0")
+ TESTCASE (L"x", +2, TRUE, 2 + 0, FALSE, 2 + 0, "x\0")
+ TESTCASE (L"x", +2, TRUE, 2 - 1, FALSE, 2 - 1, "x")
+ TESTCASE (L"x", +2, TRUE, 2 - 2, FALSE, 2 - 2, "")
+ TESTCASE (L"x", +1, TRUE, 2 + 3, FALSE, 2 + 1, "x\0\0")
+ TESTCASE (L"x", +1, TRUE, 2 + 2, FALSE, 2 + 1, "x\0\0")
+ TESTCASE (L"x", +1, TRUE, 2 + 1, FALSE, 2 + 1, "x\0\0")
+ TESTCASE (L"x", +1, TRUE, 2 + 0, FALSE, 2 + 0, "x\0")
+ TESTCASE (L"x", +1, TRUE, 2 - 1, FALSE, 2 - 1, "x")
+ TESTCASE (L"x", +1, TRUE, 2 - 2, FALSE, 2 - 2, "")
+ TESTCASE (L"x", +0, TRUE, 2 + 2, FALSE, 2 + 0, "x\0")
+ TESTCASE (L"x", +0, TRUE, 2 + 1, FALSE, 2 + 0, "x\0")
+ TESTCASE (L"x", +0, TRUE, 2 + 0, FALSE, 2 + 0, "x\0")
+ TESTCASE (L"x", +0, TRUE, 2 - 1, FALSE, 2 - 1, "x")
+ TESTCASE (L"x", +0, TRUE, 2 - 2, FALSE, 2 - 2, "")
+ TESTCASE (L"x", -1, TRUE, 2 + 1, FALSE, 2 - 1, "x")
+ TESTCASE (L"x", -1, TRUE, 2 + 0, FALSE, 2 - 1, "x")
+ TESTCASE (L"x", -1, TRUE, 2 - 1, FALSE, 2 - 1, "x")
+ TESTCASE (L"x", -1, TRUE, 2 - 2, FALSE, 2 - 2, "")
+ TESTCASE (L"x", -2, TRUE, 2 + 0, FALSE, 2 - 2, "")
+ TESTCASE (L"x", -2, TRUE, 2 - 1, FALSE, 2 - 2, "")
+ TESTCASE (L"x", -2, TRUE, 2 - 2, FALSE, 2 - 2, "")
+ TESTCASE (L"x", +2, TRUE, 2 + 4, TRUE, 2 + 2, "x\0\0\0")
+ TESTCASE (L"x", +2, TRUE, 2 + 3, TRUE, 2 + 2, "x\0\0\0")
+ TESTCASE (L"x", +2, TRUE, 2 + 2, TRUE, 2 + 2, "x\0\0\0")
+ TESTCASE (L"x", +2, TRUE, 2 + 1, TRUE, 2 + 1, "x\0\0")
+ TESTCASE (L"x", +2, TRUE, 2 + 0, TRUE, 2 + 0, "\0\0")
+ TESTCASE (L"x", +2, TRUE, 2 - 1, TRUE, 2 - 1, "\0")
+ TESTCASE (L"x", +2, TRUE, 2 - 2, TRUE, 2 - 2, "")
+ TESTCASE (L"x", +1, TRUE, 2 + 3, TRUE, 2 + 1, "x\0\0")
+ TESTCASE (L"x", +1, TRUE, 2 + 2, TRUE, 2 + 1, "x\0\0")
+ TESTCASE (L"x", +1, TRUE, 2 + 1, TRUE, 2 + 1, "x\0\0")
+ TESTCASE (L"x", +1, TRUE, 2 + 0, TRUE, 2 + 0, "\0\0")
+ TESTCASE (L"x", +1, TRUE, 2 - 1, TRUE, 2 - 1, "\0")
+ TESTCASE (L"x", +1, TRUE, 2 - 2, TRUE, 2 - 2, "")
+ TESTCASE (L"x", +0, TRUE, 2 + 2, TRUE, 2 + 1, "x\0\0")
+ TESTCASE (L"x", +0, TRUE, 2 + 1, TRUE, 2 + 1, "x\0\0")
+ TESTCASE (L"x", +0, TRUE, 2 + 0, TRUE, 2 + 0, "\0\0")
+ TESTCASE (L"x", +0, TRUE, 2 - 1, TRUE, 2 - 1, "\0")
+ TESTCASE (L"x", +0, TRUE, 2 - 2, TRUE, 2 - 2, "")
+ TESTCASE (L"x", -1, TRUE, 2 + 1, TRUE, 2 + 1, "x\0\0")
+ TESTCASE (L"x", -1, TRUE, 2 + 0, TRUE, 2 + 0, "\0\0")
+ TESTCASE (L"x", -1, TRUE, 2 - 1, TRUE, 2 - 1, "\0")
+ TESTCASE (L"x", -1, TRUE, 2 - 2, TRUE, 2 - 2, "")
+ TESTCASE (L"x", -2, TRUE, 2 + 0, TRUE, 2 - 2, "")
+ TESTCASE (L"x", -2, TRUE, 2 - 1, TRUE, 2 - 2, "")
+ TESTCASE (L"x", -2, TRUE, 2 - 2, TRUE, 2 - 2, "")
+ TESTCASE (L"x", +2, FALSE, 0, FALSE, 2 + 2, "x\0\0\0")
+ TESTCASE (L"x", +1, FALSE, 0, FALSE, 2 + 1, "x\0\0")
+ TESTCASE (L"x", +0, FALSE, 0, FALSE, 2 + 0, "x\0")
+ TESTCASE (L"x", -1, FALSE, 0, FALSE, 2 - 1, "x")
+ TESTCASE (L"x", -2, FALSE, 0, FALSE, 2 - 2, "")
+ TESTCASE (L"x", +2, FALSE, 0, TRUE, 2 + 2, "x\0\0\0")
+ TESTCASE (L"x", +1, FALSE, 0, TRUE, 2 + 1, "x\0\0")
+ TESTCASE (L"x", +0, FALSE, 0, TRUE, 2 + 1, "x\0\0")
+ TESTCASE (L"x", -1, FALSE, 0, TRUE, 2 + 1, "x\0\0")
+ TESTCASE (L"x", -2, FALSE, 0, TRUE, 2 - 2, "")
+ { 0, },
+ };
+#undef TESTCASE
+
+ for (i = 0; testcases[i].data != NULL; i++)
+ {
+ gunichar2 *buf;
+ int result;
+ gchar *buf_hex, *expected_hex;
+ if (testcases[i].use_buf)
+ buf = g_malloc0 (testcases[i].buf_size + 1); /* +1 to ensure it succeeds with buf_size == 0 */
+ else
+ buf = NULL;
+ result = _g_win32_copy_and_maybe_terminate (testcases[i].data,
+ testcases[i].data_size,
+ testcases[i].use_buf ? buf : NULL,
+ testcases[i].buf_size,
+ testcases[i].use_buf ? NULL : &buf,
+ testcases[i].terminate);
+ if (testcases[i].reported_len != result)
+ g_error ("Test %" G_GSIZE_FORMAT " failed, result %d != %d", i, result, testcases[i].reported_len);
+ if (buf == NULL && testcases[i].buf_size != 0)
+ g_error ("Test %" G_GSIZE_FORMAT " failed, buf == NULL", i);
+ g_assert_cmpint (testcases[i].reported_len, ==, result);
+ if ((testcases[i].use_buf && testcases[i].buf_size != 0) ||
+ (!testcases[i].use_buf && testcases[i].reported_len != 0))
+ {
+ g_assert_nonnull (buf);
+ buf_hex = to_hex ((const guchar *) buf, result);
+ expected_hex = to_hex (testcases[i].returned_string, testcases[i].reported_len);
+ if (memcmp (buf, testcases[i].returned_string, result) != 0)
+ g_error ("Test %" G_GSIZE_FORMAT " failed:\n%s !=\n%s", i, buf_hex, expected_hex);
+ g_assert_memcmp (buf, ==, testcases[i].returned_string, testcases[i].reported_len, buf_hex,
expected_hex, testcases[i].line);
+ g_free (buf_hex);
+ g_free (expected_hex);
+ }
+ g_free (buf);
+ }
+}
+
#endif
int
@@ -1165,6 +1392,7 @@ main (int argc,
#ifdef G_OS_WIN32
g_test_add_func ("/fileutils/stdio-win32-pathstrip", test_win32_pathstrip);
+ g_test_add_func ("/fileutils/stdio-win32-zero-terminate-symlink", test_win32_zero_terminate_symlink);
#endif
g_test_add_func ("/fileutils/build-path", test_build_path);
g_test_add_func ("/fileutils/build-pathv", test_build_pathv);
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]