+ const char *path;
+ TOKEN_PRIVILEGES privs;
+ int restore_privs = 0;
+ HANDLE sh;
+ ssize_t retval;
+
+ if (name == NULL)
+ {
+ errno = EFAULT;
+ return -1;
+ }
+ if (!*name)
+ {
+ errno = ENOENT;
+ return -1;
+ }
+
+ path = map_w32_filename (name, NULL);
+
+ if (strlen (path) > MAX_PATH)
+ {
+ errno = ENAMETOOLONG;
+ return -1;
+ }
+
+ errno = 0;
+ if (is_windows_9x () == TRUE
+ || (volume_info.flags & FILE_SUPPORTS_REPARSE_POINTS) == 0
+ || !is_symlink (path))
+ {
+ if (!errno)
+ errno = EINVAL; /* not a symlink */
+ return -1;
+ }
+
+ /* Done with simple tests, now we're in for some _real_ work. */
+ if (enable_privilege (SE_BACKUP_NAME, TRUE, &privs))
+ restore_privs = 1;
+ /* Implementation note: From here and onward, don't return early,
+ since that will fail to restore the original set of privileges of
+ the calling thread. */
+
+ retval = -1; /* not too optimistic, are we? */
+
+ /* Note: In the next call to CreateFile, we use zero as the 2nd
+ argument because, when the symlink is a hidden/system file,
+ e.g. 'C:\Users\All Users', GENERIC_READ fails with
+ ERROR_ACCESS_DENIED. Zero seems to work just fine, both for file
+ and directory symlinks. */
+ sh = CreateFile (path, 0, 0, NULL, OPEN_EXISTING,
+ FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS,
+ NULL);
+ if (sh != INVALID_HANDLE_VALUE)
+ {
+ BYTE reparse_buf[MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
+ REPARSE_DATA_BUFFER *reparse_data = (REPARSE_DATA_BUFFER *)&reparse_buf[0];
+ DWORD retbytes;
+
+ if (!DeviceIoControl (sh, FSCTL_GET_REPARSE_POINT, NULL, 0,
+ reparse_buf, MAXIMUM_REPARSE_DATA_BUFFER_SIZE,
+ &retbytes, NULL))
+ errno = EIO;
+ else if (reparse_data->ReparseTag != IO_REPARSE_TAG_SYMLINK)
+ errno = EINVAL;
+ else
+ {
+ /* Copy the link target name, in wide characters, fro
+ reparse_data, then convert it to multibyte encoding in
+ the current locale's codepage. */
+ WCHAR *lwname;
+ BYTE lname[MAX_PATH];
+ USHORT lname_len;
+ USHORT lwname_len =
+ reparse_data->SymbolicLinkReparseBuffer.PrintNameLength;
+ WCHAR *lwname_src =
+ reparse_data->SymbolicLinkReparseBuffer.PathBuffer
+ + reparse_data->SymbolicLinkReparseBuffer.PrintNameOffset/sizeof(WCHAR);
+
+ /* According to MSDN, PrintNameLength does not include the
+ terminating null character. */
+ lwname = alloca ((lwname_len + 1) * sizeof(WCHAR));
+ memcpy (lwname, lwname_src, lwname_len);
+ lwname[lwname_len/sizeof(WCHAR)] = 0; /* null-terminate */
+
+ /* FIXME: Should we use the current file-name coding system
+ instead of the fixed value of the ANSI codepage? */
+ lname_len = WideCharToMultiByte (w32_ansi_code_page, 0, lwname, -1,
+ lname, MAX_PATH, NULL, NULL);
+ if (!lname_len)
+ {
+ /* WideCharToMultiByte failed. */
+ DWORD w32err1 = GetLastError ();
+
+ switch (w32err1)
+ {
+ case ERROR_INSUFFICIENT_BUFFER:
+ errno = ENAMETOOLONG;
+ break;
+ case ERROR_INVALID_PARAMETER:
+ errno = EFAULT;
+ break;
+ case ERROR_NO_UNICODE_TRANSLATION:
+ errno = ENOENT;
+ break;
+ default:
+ errno = EINVAL;
+ break;
+ }
+ }
+ else
+ {
+ size_t size_to_copy = buf_size;
+ BYTE *p = lname;
+ BYTE *pend = p + lname_len;
+
+ /* Normalize like dostounix_filename does, but we don't
+ want to assume that lname is null-terminated. */
+ if (*p && p[1] == ':' && *p >= 'A' && *p <= 'Z')
+ *p += 'a' - 'A';
+ while (p <= pend)
+ {
+ if (*p == '\\')
+ *p = '/';
+ ++p;
+ }
+ /* Testing for null-terminated LNAME is paranoia:
+ WideCharToMultiByte should always return a
+ null-terminated string when its 4th argument is -1
+ and its 3rd argument is null-terminated (which they
+ are, see above). */
+ if (lname[lname_len - 1] == '\0')
+ lname_len--;
+ if (lname_len <= buf_size)
+ size_to_copy = lname_len;
+ strncpy (buf, lname, size_to_copy);
+ /* Success! */
+ retval = size_to_copy;
+ }
+ }
+ CloseHandle (sh);
+ }
+ else
+ {
+ /* CreateFile failed. */
+ DWORD w32err2 = GetLastError ();
+
+ switch (w32err2)
+ {
+ case ERROR_FILE_NOT_FOUND:
+ case ERROR_PATH_NOT_FOUND:
+ errno = ENOENT;
+ break;
+ case ERROR_ACCESS_DENIED:
+ case ERROR_TOO_MANY_OPEN_FILES:
+ errno = EACCES;
+ break;
+ default:
+ errno = EPERM;
+ break;
+ }
+ }
+ if (restore_privs)
+ {
+ restore_privilege (&privs);
+ revert_to_self ();
+ }
+
+ return retval;
+}
+
+/* If FILE is a symlink, return its target (stored in a static
+ buffer); otherwise return FILE.
+
+ This function repeatedly resolves symlinks in the last component of
+ a chain of symlink file names, as in foo -> bar -> baz -> ...,
+ until it arrives at a file whose last component is not a symlink,
+ or some error occurs. It returns the target of the last
+ successfully resolved symlink in the chain. If it succeeds to
+ resolve even a single symlink, the value returned is an absolute
+ file name with backslashes (result of GetFullPathName). By
+ contrast, if the original FILE is returned, it is unaltered.
+
+ Note: This function can set errno even if it succeeds.
+
+ Implementation note: we only resolve the last portion ("basename")
+ of the argument FILE and of each following file in the chain,
+ disregarding any possible symlinks in its leading directories.
+ This is because Windows system calls and library functions
+ transparently resolve symlinks in leading directories and return
+ correct information, as long as the basename is not a symlink. */
+static char *
+chase_symlinks (const char *file)
+{
+ static char target[MAX_PATH];
+ char link[MAX_PATH];
+ ssize_t res, link_len;
+ int loop_count = 0;
+
+ if (is_windows_9x () == TRUE || !is_symlink (file))
+ return (char *)file;
+
+ if ((link_len = GetFullPathName (file, MAX_PATH, link, NULL)) == 0)
+ return (char *)file;
+
+ target[0] = '\0';
+ do {
+
+ /* Remove trailing slashes, as we want to resolve the last
+ non-trivial part of the link name. */
+ while (link_len > 3 && IS_DIRECTORY_SEP (link[link_len-1]))
+ link[link_len--] = '\0';
+
+ res = readlink (link, target, MAX_PATH);
+ if (res > 0)
+ {
+ target[res] = '\0';
+ if (!(IS_DEVICE_SEP (target[1])
+ || (IS_DIRECTORY_SEP (target[0]) && IS_DIRECTORY_SEP (target[1]))))
+ {
+ /* Target is relative. Append it to the directory part of
+ the symlink, then copy the result back to target. */
+ char *p = link + link_len;
+
+ while (p > link && !IS_ANY_SEP (p[-1]))
+ p--;
+ strcpy (p, target);
+ strcpy (target, link);
+ }
+ /* Resolve any "." and ".." to get a fully-qualified file name
+ in link[] again. */
+ link_len = GetFullPathName (target, MAX_PATH, link, NULL);
+ }
+ } while (res > 0 && link_len > 0 && ++loop_count <= 100);
+
+ if (loop_count > 100)
+ errno = ELOOP;
+
+ if (target[0] == '\0') /* not a single call to readlink succeeded */
+ return (char *)file;
+ return target;