X-Git-Url: https://wimlib.net/git/?a=blobdiff_plain;f=src%2Freparse.c;h=0ae053b9cb06fa749d88dc8fb89edd5c6c88d82f;hb=33cc99f8a087d4232085234aee3f3134e15d5905;hp=1cbca56f146610f966df66a458346966e3c2c4a4;hpb=66abeba083ec882f4fac6a2d6a8ce3eef61be442;p=wimlib diff --git a/src/reparse.c b/src/reparse.c index 1cbca56f..0ae053b9 100644 --- a/src/reparse.c +++ b/src/reparse.c @@ -5,147 +5,37 @@ /* * Copyright (C) 2012, 2013 Eric Biggers * - * This file is part of wimlib, a library for working with WIM files. + * This file is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) any + * later version. * - * wimlib is free software; you can redistribute it and/or modify it under the - * terms of the GNU General Public License as published by the Free - * Software Foundation; either version 3 of the License, or (at your option) - * any later version. - * - * wimlib is distributed in the hope that it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - * A PARTICULAR PURPOSE. See the GNU General Public License for more + * This file is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. * - * You should have received a copy of the GNU General Public License - * along with wimlib; if not, see http://www.gnu.org/licenses/. + * You should have received a copy of the GNU Lesser General Public License + * along with this file; if not, see http://www.gnu.org/licenses/. */ #ifdef HAVE_CONFIG_H # include "config.h" #endif +#include + +#include "wimlib/alloca.h" #include "wimlib/assert.h" +#include "wimlib/blob_table.h" #include "wimlib/compiler.h" #include "wimlib/endianness.h" -#include "wimlib/dentry.h" #include "wimlib/encoding.h" #include "wimlib/error.h" -#include "wimlib/lookup_table.h" +#include "wimlib/inode.h" #include "wimlib/reparse.h" #include "wimlib/resource.h" -#ifdef __WIN32__ -# include "wimlib/win32.h" /* for win32_get_file_and_vol_ids() */ -#endif - -#ifdef HAVE_ALLOCA_H -# include -#endif -#include -#include - -/* On-disk format of a symbolic link (WIM_IO_REPARSE_TAG_SYMLINK) or junction - * point (WIM_IO_REPARSE_TAG_MOUNT_POINT) reparse data buffer. */ -struct reparse_buffer_disk { - le32 rptag; - le16 rpdatalen; - le16 rpreserved; - le16 substitute_name_offset; - le16 substitute_name_nbytes; - le16 print_name_offset; - le16 print_name_nbytes; - union { - struct { - le32 rpflags; - u8 data[REPARSE_POINT_MAX_SIZE - 20]; - } _packed_attribute symlink; - struct { - u8 data[REPARSE_POINT_MAX_SIZE - 16]; - } _packed_attribute junction; - }; -} _packed_attribute; - -static const utf16lechar volume_junction_prefix[11] = { - cpu_to_le16('\\'), - cpu_to_le16('\\'), - cpu_to_le16('?'), - cpu_to_le16('\\'), - cpu_to_le16('V'), - cpu_to_le16('o'), - cpu_to_le16('l'), - cpu_to_le16('u'), - cpu_to_le16('m'), - cpu_to_le16('e'), - cpu_to_le16('{'), -}; - -/* Parse the "substitute name" (link target) from a symbolic link or junction - * reparse point. - * - * Return value is: - * - * Non-negative integer: - * The name is an absolute symbolic link in one of several formats, - * and the return value is the number of UTF-16LE characters that need to - * be advanced to reach a simple "absolute" path starting with a backslash - * (i.e. skip over \??\ and/or drive letter) - * Negative integer: - * SUBST_NAME_IS_VOLUME_JUNCTION: - * The name is a volume junction. - * SUBST_NAME_IS_RELATIVE_LINK: - * The name is a relative symbolic link. - * SUBST_NAME_IS_UNKNOWN: - * The name does not appear to be a valid symbolic link, junction, - * or mount point. - */ -int -parse_substitute_name(const utf16lechar *substitute_name, - u16 substitute_name_nbytes, u32 rptag) -{ - u16 substitute_name_nchars = substitute_name_nbytes / 2; - - if (substitute_name_nchars >= 7 && - substitute_name[0] == cpu_to_le16('\\') && - substitute_name[1] == cpu_to_le16('?') && - substitute_name[2] == cpu_to_le16('?') && - substitute_name[3] == cpu_to_le16('\\') && - substitute_name[4] != cpu_to_le16('\0') && - substitute_name[5] == cpu_to_le16(':') && - substitute_name[6] == cpu_to_le16('\\')) - { - /* "Full" symlink or junction (\??\x:\ prefixed path) */ - return 6; - } else if (rptag == WIM_IO_REPARSE_TAG_MOUNT_POINT && - substitute_name_nchars >= 12 && - memcmp(substitute_name, volume_junction_prefix, - sizeof(volume_junction_prefix)) == 0 && - substitute_name[substitute_name_nchars - 1] == cpu_to_le16('\\')) - { - /* Volume junction. Can't really do anything with it. */ - return SUBST_NAME_IS_VOLUME_JUNCTION; - } else if (rptag == WIM_IO_REPARSE_TAG_SYMLINK && - substitute_name_nchars >= 3 && - substitute_name[0] != cpu_to_le16('\0') && - substitute_name[1] == cpu_to_le16(':') && - substitute_name[2] == cpu_to_le16('\\')) - { - /* "Absolute" symlink, with drive letter */ - return 2; - } else if (rptag == WIM_IO_REPARSE_TAG_SYMLINK && - substitute_name_nchars >= 1) - { - if (substitute_name[0] == cpu_to_le16('\\')) - /* "Absolute" symlink, without drive letter */ - return 0; - else - /* "Relative" symlink, without drive letter */ - return SUBST_NAME_IS_RELATIVE_LINK; - } else { - return SUBST_NAME_IS_UNKNOWN; - } -} - /* * Read the data from a symbolic link, junction, or mount point reparse point * buffer into a `struct reparse_data'. @@ -171,10 +61,10 @@ parse_reparse_data(const u8 * restrict rpbuf, u16 rpbuflen, rpdata->rptag == WIM_IO_REPARSE_TAG_MOUNT_POINT); rpdata->rpdatalen = le16_to_cpu(rpbuf_disk->rpdatalen); rpdata->rpreserved = le16_to_cpu(rpbuf_disk->rpreserved); - substitute_name_offset = le16_to_cpu(rpbuf_disk->substitute_name_offset); - rpdata->substitute_name_nbytes = le16_to_cpu(rpbuf_disk->substitute_name_nbytes); - print_name_offset = le16_to_cpu(rpbuf_disk->print_name_offset); - rpdata->print_name_nbytes = le16_to_cpu(rpbuf_disk->print_name_nbytes); + substitute_name_offset = le16_to_cpu(rpbuf_disk->symlink.substitute_name_offset); + rpdata->substitute_name_nbytes = le16_to_cpu(rpbuf_disk->symlink.substitute_name_nbytes); + print_name_offset = le16_to_cpu(rpbuf_disk->symlink.print_name_offset); + rpdata->print_name_nbytes = le16_to_cpu(rpbuf_disk->symlink.print_name_nbytes); if ((substitute_name_offset & 1) | (print_name_offset & 1) | (rpdata->substitute_name_nbytes & 1) | (rpdata->print_name_nbytes & 1)) @@ -222,37 +112,39 @@ make_reparse_buffer(const struct reparse_data * restrict rpdata, (struct reparse_buffer_disk*)rpbuf; u8 *data; - rpbuf_disk->rptag = cpu_to_le32(rpdata->rptag); - rpbuf_disk->rpreserved = cpu_to_le16(rpdata->rpreserved); - rpbuf_disk->substitute_name_offset = cpu_to_le16(0); - rpbuf_disk->substitute_name_nbytes = cpu_to_le16(rpdata->substitute_name_nbytes); - rpbuf_disk->print_name_offset = cpu_to_le16(rpdata->substitute_name_nbytes + 2); - rpbuf_disk->print_name_nbytes = cpu_to_le16(rpdata->print_name_nbytes); - - if (rpdata->rptag == WIM_IO_REPARSE_TAG_SYMLINK) { - rpbuf_disk->symlink.rpflags = cpu_to_le32(rpdata->rpflags); + if (rpdata->rptag == WIM_IO_REPARSE_TAG_SYMLINK) data = rpbuf_disk->symlink.data; - } else { + else data = rpbuf_disk->junction.data; - } - /* We null-terminate the substitute and print names, although this may - * not be strictly necessary. Note that the byte counts should not - * include the null terminators. */ - if (data + rpdata->substitute_name_nbytes + + if ((data - rpbuf) + rpdata->substitute_name_nbytes + rpdata->print_name_nbytes + - 2 * sizeof(utf16lechar) - rpbuf > REPARSE_POINT_MAX_SIZE) + 2 * sizeof(utf16lechar) > REPARSE_POINT_MAX_SIZE) { ERROR("Reparse data is too long!"); return WIMLIB_ERR_INVALID_REPARSE_DATA; } + + rpbuf_disk->rptag = cpu_to_le32(rpdata->rptag); + rpbuf_disk->rpreserved = cpu_to_le16(rpdata->rpreserved); + rpbuf_disk->symlink.substitute_name_offset = cpu_to_le16(0); + rpbuf_disk->symlink.substitute_name_nbytes = cpu_to_le16(rpdata->substitute_name_nbytes); + rpbuf_disk->symlink.print_name_offset = cpu_to_le16(rpdata->substitute_name_nbytes + 2); + rpbuf_disk->symlink.print_name_nbytes = cpu_to_le16(rpdata->print_name_nbytes); + + if (rpdata->rptag == WIM_IO_REPARSE_TAG_SYMLINK) + rpbuf_disk->symlink.rpflags = cpu_to_le32(rpdata->rpflags); + + /* We null-terminate the substitute and print names, although this may + * not be strictly necessary. Note that the byte counts should not + * include the null terminators. */ data = mempcpy(data, rpdata->substitute_name, rpdata->substitute_name_nbytes); *(utf16lechar*)data = cpu_to_le16(0); data += 2; data = mempcpy(data, rpdata->print_name, rpdata->print_name_nbytes); *(utf16lechar*)data = cpu_to_le16(0); data += 2; - rpbuf_disk->rpdatalen = cpu_to_le16(data - rpbuf - 8); + rpbuf_disk->rpdatalen = cpu_to_le16(data - rpbuf - REPARSE_DATA_OFFSET); *rpbuflen_ret = data - rpbuf; return 0; } @@ -265,41 +157,47 @@ make_reparse_buffer(const struct reparse_data * restrict rpdata, * * Note: in the WIM format, the first 8 bytes of the reparse point data buffer * are omitted, presumably because we already know the reparse tag from the - * dentry, and we already know the reparse tag length from the lookup table - * entry resource length. However, we reconstruct the first 8 bytes in the - * buffer returned by this function. + * dentry, and we already know the reparse tag length from the blob length. + * However, we reconstruct the first 8 bytes in the buffer returned by this + * function. */ -int +static int wim_inode_get_reparse_data(const struct wim_inode * restrict inode, u8 * restrict rpbuf, u16 * restrict rpbuflen_ret, - struct wim_lookup_table_entry *lte_override) + struct blob_descriptor *blob_override) { - struct wim_lookup_table_entry *lte; + struct blob_descriptor *blob; int ret; struct reparse_buffer_disk *rpbuf_disk; u16 rpdatalen; wimlib_assert(inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT); - if (!lte_override) { - lte = inode_unnamed_lte_resolved(inode); - if (!lte) { + if (blob_override) { + blob = blob_override; + } else { + struct wim_inode_stream *strm; + + strm = inode_get_unnamed_stream(inode, STREAM_TYPE_REPARSE_POINT); + if (strm) + blob = stream_blob_resolved(strm); + else + blob = NULL; + if (!blob) { ERROR("Reparse point has no reparse data!"); return WIMLIB_ERR_INVALID_REPARSE_DATA; } - } else { - lte = lte_override; } - if (wim_resource_size(lte) > REPARSE_POINT_MAX_SIZE - 8) { + if (blob->size > REPARSE_DATA_MAX_SIZE) { ERROR("Reparse data is too long!"); return WIMLIB_ERR_INVALID_REPARSE_DATA; } - rpdatalen = wim_resource_size(lte); + rpdatalen = blob->size; - /* Read the data from the WIM file */ - ret = read_full_resource_into_buf(lte, rpbuf + 8); + /* Read the reparse data from blob */ + ret = read_full_blob_into_buf(blob, rpbuf + REPARSE_DATA_OFFSET); if (ret) return ret; @@ -316,24 +214,131 @@ wim_inode_get_reparse_data(const struct wim_inode * restrict inode, * XXX this could be one of the unknown fields in the WIM dentry. */ rpbuf_disk->rpreserved = cpu_to_le16(0); - *rpbuflen_ret = rpdatalen + 8; + *rpbuflen_ret = rpdatalen + REPARSE_DATA_OFFSET; return 0; } /* UNIX version of getting and setting the data in reparse points */ -#if !defined(__WIN32__) +#ifndef __WIN32__ + +static const utf16lechar volume_junction_prefix[11] = { + cpu_to_le16('\\'), + cpu_to_le16('?'), + cpu_to_le16('?'), + cpu_to_le16('\\'), + cpu_to_le16('V'), + cpu_to_le16('o'), + cpu_to_le16('l'), + cpu_to_le16('u'), + cpu_to_le16('m'), + cpu_to_le16('e'), + cpu_to_le16('{'), +}; + +enum { + SUBST_NAME_IS_RELATIVE_LINK = -1, + SUBST_NAME_IS_VOLUME_JUNCTION = -2, + SUBST_NAME_IS_UNKNOWN = -3, +}; + +/* Parse the "substitute name" (link target) from a symbolic link or junction + * reparse point. + * + * Return value is: + * + * Non-negative integer: + * The name is an absolute symbolic link in one of several formats, + * and the return value is the number of UTF-16LE characters that need to + * be advanced to reach a simple "absolute" path starting with a backslash + * (i.e. skip over \??\ and/or drive letter) + * Negative integer: + * SUBST_NAME_IS_VOLUME_JUNCTION: + * The name is a volume junction. + * SUBST_NAME_IS_RELATIVE_LINK: + * The name is a relative symbolic link. + * SUBST_NAME_IS_UNKNOWN: + * The name does not appear to be a valid symbolic link, junction, + * or mount point. + */ +static int +parse_substitute_name(const utf16lechar *substitute_name, + u16 substitute_name_nbytes, u32 rptag) +{ + u16 substitute_name_nchars = substitute_name_nbytes / 2; + + if (substitute_name_nchars >= 7 && + substitute_name[0] == cpu_to_le16('\\') && + substitute_name[1] == cpu_to_le16('?') && + substitute_name[2] == cpu_to_le16('?') && + substitute_name[3] == cpu_to_le16('\\') && + substitute_name[4] != cpu_to_le16('\0') && + substitute_name[5] == cpu_to_le16(':') && + substitute_name[6] == cpu_to_le16('\\')) + { + /* "Full" symlink or junction (\??\x:\ prefixed path) */ + return 6; + } else if (rptag == WIM_IO_REPARSE_TAG_MOUNT_POINT && + substitute_name_nchars >= 12 && + memcmp(substitute_name, volume_junction_prefix, + sizeof(volume_junction_prefix)) == 0 && + substitute_name[substitute_name_nchars - 1] == cpu_to_le16('\\')) + { + /* Volume junction. Can't really do anything with it. */ + return SUBST_NAME_IS_VOLUME_JUNCTION; + } else if (rptag == WIM_IO_REPARSE_TAG_SYMLINK && + substitute_name_nchars >= 3 && + substitute_name[0] != cpu_to_le16('\0') && + substitute_name[1] == cpu_to_le16(':') && + substitute_name[2] == cpu_to_le16('\\')) + { + /* "Absolute" symlink, with drive letter */ + return 2; + } else if (rptag == WIM_IO_REPARSE_TAG_SYMLINK && + substitute_name_nchars >= 1) + { + if (substitute_name[0] == cpu_to_le16('\\')) + /* "Absolute" symlink, without drive letter */ + return 0; + else + /* "Relative" symlink, without drive letter */ + return SUBST_NAME_IS_RELATIVE_LINK; + } else { + return SUBST_NAME_IS_UNKNOWN; + } +} -/* Get the UNIX symlink target from a WIM inode. The inode may be either a - * "real" symlink (reparse tag WIM_IO_REPARSE_TAG_SYMLINK), or it may be a - * junction point (reparse tag WIM_IO_REPARSE_TAG_MOUNT_POINT). +/* + * Get the UNIX-style symlink target from the WIM inode for a reparse point. + * Specifically, this translates the target from UTF-16 to the current multibyte + * encoding, strips the drive prefix if present, and swaps backslashes and + * forward slashes. + * + * @inode + * The inode to read the symlink from. It must be a reparse point with + * tag WIM_IO_REPARSE_TAG_SYMLINK (a real symlink) or + * WIM_IO_REPARSE_TAG_MOUNT_POINT (a mount point or junction point). + * + * @buf + * Buffer into which to place the link target. + * + * @bufsize + * Available space in @buf, in bytes. + * + * @blob_override + * If not NULL, the blob from which to read the reparse data. Otherwise, + * the reparse data will be read from the reparse point stream of @inode. * - * This has similar semantics to the UNIX readlink() function, except the path - * argument is swapped out with the `struct wim_inode' for a reparse point, and - * on failure a negated error code is returned rather than -1 with errno set. */ + * If the entire symbolic link target was placed in the buffer, returns the + * number of bytes written. The resulting string is not null-terminated. If + * the symbolic link target was too large to be placed in the buffer, the first + * @bufsize bytes of it are placed in the buffer and + * -ENAMETOOLONG is returned. Otherwise, a negative errno value indicating + * another error is returned. + */ ssize_t wim_inode_readlink(const struct wim_inode * restrict inode, char * restrict buf, size_t bufsize, - struct wim_lookup_table_entry *lte_override) + struct blob_descriptor *blob_override) { int ret; struct reparse_buffer_disk rpbuf_disk _aligned_attribute(8); @@ -346,11 +351,11 @@ wim_inode_readlink(const struct wim_inode * restrict inode, wimlib_assert(inode_is_symlink(inode)); if (wim_inode_get_reparse_data(inode, (u8*)&rpbuf_disk, &rpbuflen, - lte_override)) + blob_override)) return -EIO; if (parse_reparse_data((const u8*)&rpbuf_disk, rpbuflen, &rpdata)) - return -EIO; + return -EINVAL; ret = utf16le_to_tstr(rpdata.substitute_name, rpdata.substitute_name_nbytes, @@ -379,9 +384,12 @@ wim_inode_readlink(const struct wim_inode * restrict inode, } out_translate_slashes: - for (size_t i = 0; i < link_target_len; i++) + for (size_t i = 0; i < link_target_len; i++) { if (translated_target[i] == '\\') translated_target[i] = '/'; + else if (translated_target[i] == '/') + translated_target[i] = '\\'; + } out_have_link: if (link_target_len > bufsize) { link_target_len = bufsize; @@ -395,10 +403,11 @@ out_free_link_target: return ret; } +/* Given a UNIX-style symbolic link target, create a Windows-style reparse point + * buffer and assign it to the specified inode. */ int -wim_inode_set_symlink(struct wim_inode *inode, - const char *target, - struct wim_lookup_table *lookup_table) +wim_inode_set_symlink(struct wim_inode *inode, const char *target, + struct blob_table *blob_table) { struct reparse_buffer_disk rpbuf_disk _aligned_attribute(8); @@ -416,11 +425,14 @@ wim_inode_set_symlink(struct wim_inode *inode, ret = tstr_to_utf16le(target, strlen(target), &name_utf16le, &name_utf16le_nbytes); if (ret) - return ret; + goto out; - for (size_t i = 0; i < name_utf16le_nbytes / 2; i++) + for (size_t i = 0; i < name_utf16le_nbytes / 2; i++) { if (name_utf16le[i] == cpu_to_le16('/')) name_utf16le[i] = cpu_to_le16('\\'); + else if (name_utf16le[i] == cpu_to_le16('\\')) + name_utf16le[i] = cpu_to_le16('/'); + } /* Compatability notes: * @@ -428,12 +440,9 @@ wim_inode_set_symlink(struct wim_inode *inode, * is a relative symbolic link. (Quite simple compared to the various * ways to provide Windows paths.) * - * To change a UNIX relative symbolic link to Windows format, we only - * need to translate it to UTF-16LE and replace forward slashes with - * backslashes. We do not make any attempt to handle filename character - * problems, such as a link target that itself contains backslashes on - * UNIX. Then, for these relative links, we set the reparse header - * @flags field to SYMBOLIC_LINK_RELATIVE. + * To change a UNIX relative symbolic link to Windows format, we need to + * translate it to UTF-16LE, swap forward slashes and backslashes, and + * set 'rpflags' to SYMBOLIC_LINK_RELATIVE. * * For UNIX absolute symbolic links, we must set the @flags field to 0. * Then, there are multiple options as to actually represent the @@ -492,105 +501,23 @@ wim_inode_set_symlink(struct wim_inode *inode, } ret = make_reparse_buffer(&rpdata, (u8*)&rpbuf_disk, &rpbuflen); - if (ret == 0) { - ret = inode_set_unnamed_stream(inode, - (u8*)&rpbuf_disk + 8, - rpbuflen - 8, - lookup_table); - } + if (ret) + goto out_free_name; + + ret = WIMLIB_ERR_NOMEM; + if (!inode_add_stream_with_data(inode, + STREAM_TYPE_REPARSE_POINT, + NO_STREAM_NAME, + (u8*)&rpbuf_disk + REPARSE_DATA_OFFSET, + rpbuflen - REPARSE_DATA_OFFSET, + blob_table)) + goto out_free_name; + + ret = 0; +out_free_name: FREE(name_utf16le); +out: return ret; } -#include - -static int -unix_get_ino_and_dev(const char *path, u64 *ino_ret, u64 *dev_ret) -{ - struct stat stbuf; - if (stat(path, &stbuf)) { - if (errno != ENOENT) - WARNING_WITH_ERRNO("Failed to stat \"%s\"", path); - /* Treat as a link pointing outside the capture root (it - * most likely is). */ - return WIMLIB_ERR_STAT; - } else { - *ino_ret = stbuf.st_ino; - *dev_ret = stbuf.st_dev; - return 0; - } -} - -#endif /* !defined(__WIN32__) */ - -/* is_rp_path_separator() - characters treated as path separators in absolute - * symbolic link targets */ - -#ifdef __WIN32__ -# define is_rp_path_separator(c) ((c) == L'\\' || (c) == L'/') -# define os_get_ino_and_dev win32_get_file_and_vol_ids -#else -# define is_rp_path_separator(c) ((c) == '/') -# define os_get_ino_and_dev unix_get_ino_and_dev -#endif - -/* Fix up absolute symbolic link targets--- mostly shared between UNIX and - * Windows */ -tchar * -capture_fixup_absolute_symlink(tchar *dest, - u64 capture_root_ino, u64 capture_root_dev) -{ - tchar *p = dest; - -#ifdef __WIN32__ - /* Skip drive letter */ - if (!is_rp_path_separator(*dest)) - p += 2; -#endif - - DEBUG("Fixing symlink or junction \"%"TS"\"", dest); - for (;;) { - tchar save; - int ret; - u64 ino; - u64 dev; - - while (is_rp_path_separator(*p)) - p++; - - save = *p; - *p = T('\0'); - ret = os_get_ino_and_dev(dest, &ino, &dev); - *p = save; - - if (ret) /* stat() failed before we got to the capture root--- - assume the link points outside it. */ - return NULL; - - if (ino == capture_root_ino && dev == capture_root_dev) { - /* Link points inside capture root. Return abbreviated - * path. */ - if (*p == T('\0')) - *(p - 1) = OS_PREFERRED_PATH_SEPARATOR; - while (p - 1 >= dest && is_rp_path_separator(*(p - 1))) - p--; - #ifdef __WIN32__ - if (!is_rp_path_separator(dest[0])) { - *--p = dest[1]; - *--p = dest[0]; - } - #endif - wimlib_assert(p >= dest); - return p; - } - - if (*p == T('\0')) { - /* Link points outside capture root. */ - return NULL; - } - - do { - p++; - } while (!is_rp_path_separator(*p) && *p != T('\0')); - } -} +#endif /* !__WIN32__ */