Merge branch 'win32-symlink-refactoring' into 'master'

Win32 symlink code refactoring

See merge request GNOME/glib!269
This commit is contained in:
Philip Withnall 2019-03-13 11:55:27 +00:00
commit df62731771
10 changed files with 880 additions and 413 deletions

View File

@ -76,6 +76,7 @@ typedef struct _GFileInfoClass GFileInfoClass;
* A key in the "standard" namespace for checking if the file is a symlink.
* Typically the actual type is something else, if we followed the symlink
* to get the type.
* On Windows NTFS mountpoints are considered to be symlinks as well.
* Corresponding #GFileAttributeType is %G_FILE_ATTRIBUTE_TYPE_BOOLEAN.
**/
#define G_FILE_ATTRIBUTE_STANDARD_IS_SYMLINK "standard::is-symlink" /* boolean */

View File

@ -361,6 +361,15 @@ typedef enum {
* @G_FILE_TYPE_MOUNTABLE: File is a mountable location.
*
* Indicates the file's on-disk type.
*
* On Windows systems a file will never have %G_FILE_TYPE_SYMBOLIC_LINK type;
* use #GFileInfo and %G_FILE_ATTRIBUTE_STANDARD_IS_SYMLINK to determine
* whether a file is a symlink or not. This is due to the fact that NTFS does
* not have a single filesystem object type for symbolic links - it has
* files that symlink to files, and directories that symlink to directories.
* #GFileType enumeration cannot precisely represent this important distinction,
* which is why all Windows symlinks will continue to be reported as
* %G_FILE_TYPE_REGULAR or %G_FILE_TYPE_DIRECTORY.
**/
typedef enum {
G_FILE_TYPE_UNKNOWN = 0,

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
@ -957,8 +964,8 @@ set_info_from_stat (GFileInfo *info,
else if (S_ISLNK (statbuf->st_mode))
file_type = G_FILE_TYPE_SYMBOLIC_LINK;
#elif defined (G_OS_WIN32)
if (statbuf->reparse_tag == IO_REPARSE_TAG_SYMLINK ||
statbuf->reparse_tag == IO_REPARSE_TAG_MOUNT_POINT)
else if (statbuf->reparse_tag == IO_REPARSE_TAG_SYMLINK ||
statbuf->reparse_tag == IO_REPARSE_TAG_MOUNT_POINT)
file_type = G_FILE_TYPE_SYMBOLIC_LINK;
#endif
@ -966,15 +973,17 @@ set_info_from_stat (GFileInfo *info,
g_file_info_set_size (info, statbuf->st_size);
_g_file_info_set_attribute_uint32_by_id (info, G_FILE_ATTRIBUTE_ID_UNIX_DEVICE, statbuf->st_dev);
_g_file_info_set_attribute_uint32_by_id (info, G_FILE_ATTRIBUTE_ID_UNIX_NLINK, statbuf->st_nlink);
#ifndef G_OS_WIN32
/* Pointless setting these on Windows even if they exist in the struct */
_g_file_info_set_attribute_uint64_by_id (info, G_FILE_ATTRIBUTE_ID_UNIX_INODE, statbuf->st_ino);
_g_file_info_set_attribute_uint32_by_id (info, G_FILE_ATTRIBUTE_ID_UNIX_NLINK, statbuf->st_nlink);
_g_file_info_set_attribute_uint32_by_id (info, G_FILE_ATTRIBUTE_ID_UNIX_UID, statbuf->st_uid);
_g_file_info_set_attribute_uint32_by_id (info, G_FILE_ATTRIBUTE_ID_UNIX_GID, statbuf->st_gid);
_g_file_info_set_attribute_uint32_by_id (info, G_FILE_ATTRIBUTE_ID_UNIX_RDEV, statbuf->st_rdev);
#endif
/* FIXME: st_mode is mostly pointless on Windows, too. Set the attribute or not? */
/* Mostly pointless on Windows.
* Still, it allows for S_ISREG/S_ISDIR and IWRITE (read-only) checks.
*/
_g_file_info_set_attribute_uint32_by_id (info, G_FILE_ATTRIBUTE_ID_UNIX_MODE, statbuf->st_mode);
#if defined (HAVE_STRUCT_STAT_ST_BLKSIZE)
_g_file_info_set_attribute_uint32_by_id (info, G_FILE_ATTRIBUTE_ID_UNIX_BLOCK_SIZE, statbuf->st_blksize);

View File

@ -291,10 +291,10 @@ test_internal_enhanced_stdio (void)
ft_programdata = g_file_info_get_file_type (fi_programdata);
ft_commondata = g_file_info_get_file_type (fi_commondata);
g_assert_cmpint (ft_allusers, ==, G_FILE_TYPE_SYMBOLIC_LINK);
g_assert_cmpint (ft_allusers, ==, G_FILE_TYPE_DIRECTORY);
g_assert_cmpint (ft_allusers_target, ==, G_FILE_TYPE_DIRECTORY);
g_assert_cmpint (ft_programdata, ==, G_FILE_TYPE_DIRECTORY);
g_assert_cmpint (ft_commondata, ==, G_FILE_TYPE_SYMBOLIC_LINK);
g_assert_cmpint (ft_commondata, ==, G_FILE_TYPE_DIRECTORY);
allusers_is_symlink = g_file_info_get_attribute_boolean (fi_allusers, G_FILE_ATTRIBUTE_STANDARD_IS_SYMLINK);
allusers_reparse_tag = g_file_info_get_attribute_uint32 (fi_allusers, G_FILE_ATTRIBUTE_DOS_REPARSE_POINT_TAG);

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

@ -61,18 +61,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

@ -889,6 +889,7 @@ test_stdio_wrappers (void)
struct utimbuf ut;
GError *error = NULL;
GStatBuf path_statbuf, cwd_statbuf;
time_t now;
/* The permissions tests here dont work when running as root. */
#ifdef G_OS_UNIX
@ -958,14 +959,16 @@ test_stdio_wrappers (void)
g_assert_cmpint (ret, ==, 0);
#endif
ut.actime = ut.modtime = (time_t)0;
now = time (NULL);
ut.actime = ut.modtime = now;
ret = g_utime ("test-create", &ut);
g_assert_cmpint (ret, ==, 0);
ret = g_lstat ("test-create", &buf);
g_assert_cmpint (ret, ==, 0);
g_assert_cmpint (buf.st_atime, ==, (time_t)0);
g_assert_cmpint (buf.st_mtime, ==, (time_t)0);
g_assert_cmpint (buf.st_atime, ==, now);
g_assert_cmpint (buf.st_mtime, ==, now);
g_chdir ("..");
g_remove ("mkdir-test/test-create");
@ -1153,6 +1156,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
@ -1166,6 +1396,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);