mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2024-12-25 23:16:14 +01:00
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:
parent
19608e36d2
commit
62d387151d
@ -161,7 +161,7 @@ _g_local_file_info_create_fs_id (GLocalFileStat *statbuf)
|
|||||||
static gchar *
|
static gchar *
|
||||||
read_link (const gchar *full_name)
|
read_link (const gchar *full_name)
|
||||||
{
|
{
|
||||||
#if defined (HAVE_READLINK) || defined (G_OS_WIN32)
|
#if defined (HAVE_READLINK)
|
||||||
gchar *buffer;
|
gchar *buffer;
|
||||||
guint size;
|
guint size;
|
||||||
|
|
||||||
@ -172,11 +172,7 @@ read_link (const gchar *full_name)
|
|||||||
{
|
{
|
||||||
int read_size;
|
int read_size;
|
||||||
|
|
||||||
#ifndef G_OS_WIN32
|
|
||||||
read_size = readlink (full_name, buffer, size);
|
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)
|
if (read_size < 0)
|
||||||
{
|
{
|
||||||
g_free (buffer);
|
g_free (buffer);
|
||||||
@ -190,6 +186,17 @@ read_link (const gchar *full_name)
|
|||||||
size *= 2;
|
size *= 2;
|
||||||
buffer = g_realloc (buffer, size);
|
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
|
#else
|
||||||
return NULL;
|
return NULL;
|
||||||
#endif
|
#endif
|
||||||
|
@ -2090,7 +2090,7 @@ gchar *
|
|||||||
g_file_read_link (const gchar *filename,
|
g_file_read_link (const gchar *filename,
|
||||||
GError **error)
|
GError **error)
|
||||||
{
|
{
|
||||||
#if defined (HAVE_READLINK) || defined (G_OS_WIN32)
|
#if defined (HAVE_READLINK)
|
||||||
gchar *buffer;
|
gchar *buffer;
|
||||||
size_t size;
|
size_t size;
|
||||||
gssize read_size;
|
gssize read_size;
|
||||||
@ -2103,11 +2103,7 @@ g_file_read_link (const gchar *filename,
|
|||||||
|
|
||||||
while (TRUE)
|
while (TRUE)
|
||||||
{
|
{
|
||||||
#ifndef G_OS_WIN32
|
|
||||||
read_size = readlink (filename, buffer, size);
|
read_size = readlink (filename, buffer, size);
|
||||||
#else
|
|
||||||
read_size = g_win32_readlink_utf8 (filename, buffer, size);
|
|
||||||
#endif
|
|
||||||
if (read_size < 0)
|
if (read_size < 0)
|
||||||
{
|
{
|
||||||
int saved_errno = errno;
|
int saved_errno = errno;
|
||||||
@ -2128,6 +2124,27 @@ g_file_read_link (const gchar *filename,
|
|||||||
size *= 2;
|
size *= 2;
|
||||||
buffer = g_realloc (buffer, size);
|
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
|
#else
|
||||||
g_return_val_if_fail (filename != NULL, NULL);
|
g_return_val_if_fail (filename != NULL, NULL);
|
||||||
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
|
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
|
||||||
|
@ -67,18 +67,20 @@ typedef struct {
|
|||||||
|
|
||||||
/* See gstdio.c */
|
/* See gstdio.c */
|
||||||
#ifdef G_OS_WIN32
|
#ifdef G_OS_WIN32
|
||||||
int (* g_win32_stat_utf8) (const gchar *filename,
|
int (* g_win32_stat_utf8) (const gchar *filename,
|
||||||
GWin32PrivateStat *buf);
|
GWin32PrivateStat *buf);
|
||||||
|
|
||||||
int (* g_win32_lstat_utf8) (const gchar *filename,
|
int (* g_win32_lstat_utf8) (const gchar *filename,
|
||||||
GWin32PrivateStat *buf);
|
GWin32PrivateStat *buf);
|
||||||
|
|
||||||
int (* g_win32_readlink_utf8) (const gchar *filename,
|
int (* g_win32_readlink_utf8) (const gchar *filename,
|
||||||
gchar *buf,
|
gchar *buf,
|
||||||
gsize buf_size);
|
gsize buf_size,
|
||||||
|
gchar **alloc_buf,
|
||||||
|
gboolean terminate);
|
||||||
|
|
||||||
int (* g_win32_fstat) (int fd,
|
int (* g_win32_fstat) (int fd,
|
||||||
GWin32PrivateStat *buf);
|
GWin32PrivateStat *buf);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
@ -75,3 +75,92 @@ _g_win32_strip_extended_ntobjm_prefix (gunichar2 *str,
|
|||||||
|
|
||||||
return do_move;
|
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;
|
||||||
|
}
|
||||||
|
869
glib/gstdio.c
869
glib/gstdio.c
@ -129,6 +129,8 @@ w32_error_to_errno (DWORD error_code)
|
|||||||
* FT = UT * 10000000 + 116444736000000000.
|
* FT = UT * 10000000 + 116444736000000000.
|
||||||
* Therefore:
|
* Therefore:
|
||||||
* UT = (FT - 116444736000000000) / 10000000.
|
* UT = (FT - 116444736000000000) / 10000000.
|
||||||
|
* Converts FILETIME to unix epoch time in form
|
||||||
|
* of a signed 64-bit integer (can be negative).
|
||||||
*/
|
*/
|
||||||
static gint64
|
static gint64
|
||||||
_g_win32_filetime_to_unix_time (FILETIME *ft)
|
_g_win32_filetime_to_unix_time (FILETIME *ft)
|
||||||
@ -165,6 +167,9 @@ _g_win32_filetime_to_unix_time (FILETIME *ft)
|
|||||||
# endif
|
# endif
|
||||||
# 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
|
static int
|
||||||
_g_win32_fill_statbuf_from_handle_info (const wchar_t *filename,
|
_g_win32_fill_statbuf_from_handle_info (const wchar_t *filename,
|
||||||
const wchar_t *filename_target,
|
const wchar_t *filename_target,
|
||||||
@ -258,340 +263,157 @@ _g_win32_fill_statbuf_from_handle_info (const wchar_t *filename,
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
/* Fills our private stat-like structure using data from
|
||||||
_g_win32_stat_utf16_no_trailing_slashes (const gunichar2 *filename,
|
* a normal stat64 struct, BHFI, FSI and a reparse tag.
|
||||||
int fd,
|
*/
|
||||||
GWin32PrivateStat *buf,
|
static void
|
||||||
gboolean for_symlink)
|
_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;
|
DWORD error_code;
|
||||||
struct __stat64 statbuf;
|
DWORD returned_bytes = 0;
|
||||||
BY_HANDLE_FILE_INFORMATION handle_info;
|
BYTE *data;
|
||||||
FILE_STANDARD_INFO std_info;
|
gsize to_copy;
|
||||||
WIN32_FIND_DATAW finddata;
|
/* This is 16k. It's impossible to make DeviceIoControl() tell us
|
||||||
DWORD immediate_attributes;
|
* the required size. NtFsControlFile() does have such a feature,
|
||||||
gboolean is_symlink = FALSE;
|
* but for some reason it doesn't work with CreateFile()-returned handles.
|
||||||
gboolean is_directory;
|
* The only alternative is to repeatedly call DeviceIoControl()
|
||||||
DWORD open_flags;
|
* with bigger and bigger buffers, until it succeeds.
|
||||||
wchar_t *filename_target = NULL;
|
* We choose to sacrifice stack space for speed.
|
||||||
int result;
|
*/
|
||||||
|
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 ();
|
error_code = GetLastError ();
|
||||||
}
|
|
||||||
|
|
||||||
if (!succeeded_so_far)
|
|
||||||
{
|
|
||||||
if (fd < 0)
|
|
||||||
CloseHandle (file_handle);
|
|
||||||
errno = w32_error_to_errno (error_code);
|
errno = w32_error_to_errno (error_code);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* It's tempting to use GetFileInformationByHandleEx(FileAttributeTagInfo),
|
rep_buf = (REPARSE_DATA_BUFFER *) max_buffer;
|
||||||
* but it always reports that the ReparseTag is 0.
|
|
||||||
*/
|
if (reparse_tag != NULL)
|
||||||
if (fd < 0)
|
*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)
|
to_copy = rep_buf->SymbolicLinkReparseBuffer.SubstituteNameLength;
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
/* else if fd >= 0 the file_handle was obtained via _get_osfhandle()
|
else if (rep_buf->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT)
|
||||||
* and must not be closed, it is owned by fd.
|
|
||||||
*/
|
|
||||||
|
|
||||||
if (!succeeded_so_far)
|
|
||||||
{
|
{
|
||||||
errno = w32_error_to_errno (error_code);
|
data = &((BYTE *) rep_buf->MountPointReparseBuffer.PathBuffer)[rep_buf->MountPointReparseBuffer.SubstituteNameOffset];
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
to_copy = rep_buf->MountPointReparseBuffer.SubstituteNameLength;
|
||||||
* 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
|
else
|
||||||
result = _fstat64 (fd, &statbuf);
|
to_copy = 0;
|
||||||
|
|
||||||
if (result != 0)
|
return _g_win32_copy_and_maybe_terminate (data, to_copy, buf, buf_size, alloc_buf, terminate);
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 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
|
static int
|
||||||
_g_win32_stat_utf8 (const gchar *filename,
|
_g_win32_readlink_utf16_raw (const gunichar2 *filename,
|
||||||
GWin32PrivateStat *buf,
|
DWORD *reparse_tag,
|
||||||
gboolean for_symlink)
|
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;
|
HANDLE h;
|
||||||
DWORD attributes;
|
DWORD attributes;
|
||||||
REPARSE_DATA_BUFFER *rep_buf;
|
|
||||||
DWORD to_copy;
|
DWORD to_copy;
|
||||||
DWORD error_code;
|
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)
|
if ((attributes = GetFileAttributesW (filename)) == 0)
|
||||||
{
|
{
|
||||||
error_code = GetLastError ();
|
error_code = GetLastError ();
|
||||||
@ -624,55 +446,59 @@ _g_win32_readlink_utf16_raw (const gunichar2 *filename,
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!DeviceIoControl (h, FSCTL_GET_REPARSE_POINT, NULL, 0,
|
to_copy = _g_win32_readlink_handle_raw (h, reparse_tag, buf, buf_size, alloc_buf, terminate);
|
||||||
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);
|
CloseHandle (h);
|
||||||
|
|
||||||
return to_copy;
|
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
|
static int
|
||||||
_g_win32_readlink_utf16 (const gunichar2 *filename,
|
_g_win32_readlink_utf16_handle (const gunichar2 *filename,
|
||||||
gunichar2 *buf,
|
HANDLE file_handle,
|
||||||
gsize buf_size)
|
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;
|
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)
|
if (result <= 0)
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
@ -686,19 +512,257 @@ _g_win32_readlink_utf16 (const gunichar2 *filename,
|
|||||||
|
|
||||||
/* DeviceIoControl () tends to return filenames as NT Object Manager
|
/* DeviceIoControl () tends to return filenames as NT Object Manager
|
||||||
* names , i.e. "\\??\\C:\\foo\\bar".
|
* 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
|
* functions) is unprepared to deal with it. Unless it has no 'x:' drive
|
||||||
* letter part after the prefix, in which case we leave everything
|
* 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
|
* the prefix will allow it to be confused with relative links
|
||||||
* targeting "Volume{GUID}".
|
* targeting "Volume{GUID}".
|
||||||
*/
|
*/
|
||||||
string_size = result / sizeof (gunichar2);
|
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);
|
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 *
|
static gchar *
|
||||||
_g_win32_get_mode_alias (const gchar *mode)
|
_g_win32_get_mode_alias (const gchar *mode)
|
||||||
{
|
{
|
||||||
@ -719,13 +783,54 @@ _g_win32_get_mode_alias (const gchar *mode)
|
|||||||
return alias;
|
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
|
int
|
||||||
g_win32_readlink_utf8 (const gchar *filename,
|
g_win32_readlink_utf8 (const gchar *filename,
|
||||||
gchar *buf,
|
gchar *buf,
|
||||||
gsize buf_size)
|
gsize buf_size,
|
||||||
|
gchar **alloc_buf,
|
||||||
|
gboolean terminate)
|
||||||
{
|
{
|
||||||
wchar_t *wfilename;
|
wchar_t *wfilename;
|
||||||
int result;
|
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);
|
wfilename = g_utf8_to_utf16 (filename, -1, NULL, NULL, NULL);
|
||||||
|
|
||||||
@ -735,39 +840,41 @@ g_win32_readlink_utf8 (const gchar *filename,
|
|||||||
return -1;
|
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);
|
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;
|
errno = EINVAL;
|
||||||
gchar *tmp = g_utf16_to_utf8 ((const gunichar2 *) buf,
|
return -1;
|
||||||
result / sizeof (gunichar2),
|
|
||||||
NULL,
|
|
||||||
&tmp_len,
|
|
||||||
NULL);
|
|
||||||
|
|
||||||
if (tmp == NULL)
|
|
||||||
{
|
|
||||||
errno = EINVAL;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tmp_len > buf_size - 1)
|
|
||||||
tmp_len = buf_size - 1;
|
|
||||||
|
|
||||||
memcpy (buf, tmp, tmp_len);
|
|
||||||
/* readlink() doesn't NUL-terminate, but we do.
|
|
||||||
* To be compliant, however, we return the
|
|
||||||
* number of bytes without the NUL-terminator.
|
|
||||||
*/
|
|
||||||
buf[tmp_len] = '\0';
|
|
||||||
result = tmp_len;
|
|
||||||
g_free (tmp);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
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
|
#endif
|
||||||
|
@ -51,9 +51,11 @@ int g_win32_stat_utf8 (const gchar *filename,
|
|||||||
int g_win32_lstat_utf8 (const gchar *filename,
|
int g_win32_lstat_utf8 (const gchar *filename,
|
||||||
GWin32PrivateStat *buf);
|
GWin32PrivateStat *buf);
|
||||||
|
|
||||||
int g_win32_readlink_utf8 (const gchar *filename,
|
int g_win32_readlink_utf8 (const gchar *filename,
|
||||||
gchar *buf,
|
gchar *buf,
|
||||||
gsize buf_size);
|
gsize buf_size,
|
||||||
|
gchar **alloc_buf,
|
||||||
|
gboolean terminate);
|
||||||
|
|
||||||
int g_win32_fstat (int fd,
|
int g_win32_fstat (int fd,
|
||||||
GWin32PrivateStat *buf);
|
GWin32PrivateStat *buf);
|
||||||
|
@ -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
|
#endif
|
||||||
|
|
||||||
int
|
int
|
||||||
@ -1165,6 +1392,7 @@ main (int argc,
|
|||||||
|
|
||||||
#ifdef G_OS_WIN32
|
#ifdef G_OS_WIN32
|
||||||
g_test_add_func ("/fileutils/stdio-win32-pathstrip", test_win32_pathstrip);
|
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
|
#endif
|
||||||
g_test_add_func ("/fileutils/build-path", test_build_path);
|
g_test_add_func ("/fileutils/build-path", test_build_path);
|
||||||
g_test_add_func ("/fileutils/build-pathv", test_build_pathv);
|
g_test_add_func ("/fileutils/build-pathv", test_build_pathv);
|
||||||
|
Loading…
Reference in New Issue
Block a user