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.
This commit is contained in:
Руслан Ижбулатов 2018-08-24 09:16:46 +00:00
parent 19608e36d2
commit 62d387151d
7 changed files with 856 additions and 404 deletions

View File

@ -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

View File

@ -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);

View File

@ -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

View File

@ -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;
}

View File

@ -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,340 +263,157 @@ _g_win32_fill_statbuf_from_handle_info (const wchar_t *filename,
return 0;
}
static int
_g_win32_stat_utf16_no_trailing_slashes (const gunichar2 *filename,
int fd,
GWin32PrivateStat *buf,
gboolean for_symlink)
/* 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)
{
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 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;
if (fd < 0)
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))
{
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)
{
if (fd < 0)
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)
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)
{
memset (&finddata, 0, sizeof (finddata));
data = &((BYTE *) rep_buf->SymbolicLinkReparseBuffer.PathBuffer)[rep_buf->SymbolicLinkReparseBuffer.SubstituteNameOffset];
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);
}
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;
}
CloseHandle (file_handle);
to_copy = rep_buf->SymbolicLinkReparseBuffer.SubstituteNameLength;
}
/* 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)
else if (rep_buf->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT)
{
errno = w32_error_to_errno (error_code);
return -1;
}
data = &((BYTE *) rep_buf->MountPointReparseBuffer.PathBuffer)[rep_buf->MountPointReparseBuffer.SubstituteNameOffset];
/*
* 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);
to_copy = rep_buf->MountPointReparseBuffer.SubstituteNameLength;
}
else
result = _fstat64 (fd, &statbuf);
to_copy = 0;
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;
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_stat_utf8 (const gchar *filename,
GWin32PrivateStat *buf,
gboolean for_symlink)
_g_win32_readlink_utf16_raw (const gunichar2 *filename,
DWORD *reparse_tag,
gunichar2 *buf,
gsize buf_size,
gunichar2 **alloc_buf,
gboolean terminate)
{
wchar_t *wfilename;
int result;
gsize len;
if (filename == NULL)
{
errno = EINVAL;
return -1;
}
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 ();
@ -624,55 +446,59 @@ _g_win32_readlink_utf16_raw (const gunichar2 *filename,
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);
}
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 (const gunichar2 *filename,
gunichar2 *buf,
gsize buf_size)
_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 = _g_win32_readlink_utf16_raw (filename, buf, buf_size);
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;
@ -686,19 +512,257 @@ _g_win32_readlink_utf16 (const gunichar2 *filename,
/* 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
* 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
* 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);
_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,
GWin32PrivateStat *buf,
gboolean for_symlink)
{
struct __stat64 statbuf;
BY_HANDLE_FILE_INFORMATION handle_info;
FILE_STANDARD_INFO std_info;
gboolean is_symlink = FALSE;
wchar_t *filename_target = NULL;
DWORD immediate_attributes;
DWORD open_flags;
gboolean is_directory;
DWORD reparse_tag = 0;
DWORD error_code;
BOOL succeeded_so_far;
HANDLE file_handle;
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_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;
}
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.
* 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 (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)
{
CloseHandle (file_handle);
return -1;
}
CloseHandle (file_handle);
_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);
return 0;
}
/* 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;
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)
{
errno = w32_error_to_errno (error_code);
return -1;
}
is_symlink = (handle_info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) == FILE_ATTRIBUTE_REPARSE_POINT;
if (is_symlink &&
_g_win32_readlink_handle_raw (file_handle, &reparse_tag, NULL, 0, NULL, FALSE) < 0)
return -1;
if (_fstat64 (fd, &statbuf) != 0)
return -1;
_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,
gboolean for_symlink)
{
wchar_t *wfilename;
int result;
gsize len;
if (filename == NULL)
{
errno = EINVAL;
return -1;
}
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, 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)
{
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)
{
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_fd (fd, buf);
}
static gchar *
_g_win32_get_mode_alias (const gchar *mode)
{
@ -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)
if (result <= 0)
return result;
tmp = g_utf16_to_utf8 (buf_utf16,
result / sizeof (gunichar2),
NULL,
&tmp_len,
NULL);
g_free (buf_utf16);
if (tmp == NULL)
{
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);
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

View File

@ -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);

View File

@ -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);