+ 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;