OSDN Git Service

gas/opcodes: blackfin: move dsp mac func defines to common header
[pf3gnuchains/sourceware.git] / winsup / cygwin / fhandler_disk_file.cc
index 2baabae..cc2c237 100644 (file)
@@ -1,7 +1,7 @@
 /* fhandler_disk_file.cc
 
    Copyright 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004,
-   2005, 2006, 2007, 2008, 2009 Red Hat, Inc.
+   2005, 2006, 2007, 2008, 2009, 2010, 2011 Red Hat, Inc.
 
 This file is part of Cygwin.
 
@@ -23,7 +23,6 @@ details. */
 #include "pinfo.h"
 #include "ntdll.h"
 #include "tls_pbuf.h"
-#include "nfs.h"
 #include "pwdgrp.h"
 #include <winioctl.h>
 
@@ -118,7 +117,7 @@ public:
            {
              found[__DIR_PROC] = true;
              if (retname)
-               RtlInitUnicodeString (retname, L"proc");
+               *retname = ro_u_proc;
              return 2;
            }
          if (!found[__DIR_CYGDRIVE])
@@ -148,12 +147,18 @@ path_conv::isgood_inode (__ino64_t ino) const
   return hasgood_inode () && (ino > UINT32_MAX || !isremote () || fs_is_nfs ());
 }
 
-static inline bool
-is_volume_mountpoint (POBJECT_ATTRIBUTES attr)
+/* Check reparse point for type.  IO_REPARSE_TAG_MOUNT_POINT types are
+   either volume mount points, which are treated as directories, or they
+   are directory mount points, which are treated as symlinks.
+   IO_REPARSE_TAG_SYMLINK types are always symlinks.  We don't know
+   anything about other reparse points, so they are treated as unknown.  */
+static inline int
+readdir_check_reparse_point (POBJECT_ATTRIBUTES attr)
 {
-  bool ret = false;
+  DWORD ret = DT_UNKNOWN;
   IO_STATUS_BLOCK io;
   HANDLE reph;
+  UNICODE_STRING subst;
 
   if (NT_SUCCESS (NtOpenFile (&reph, READ_CONTROL, attr, &io,
                              FILE_SHARE_VALID_FLAGS,
@@ -164,28 +169,44 @@ is_volume_mountpoint (POBJECT_ATTRIBUTES attr)
                  alloca (MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
       if (NT_SUCCESS (NtFsControlFile (reph, NULL, NULL, NULL,
                      &io, FSCTL_GET_REPARSE_POINT, NULL, 0,
-                     (LPVOID) rp, MAXIMUM_REPARSE_DATA_BUFFER_SIZE))
-         && rp->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT
-         && rp->SymbolicLinkReparseBuffer.PrintNameLength == 0)
-       ret = true;
-      NtClose (reph);
+                     (LPVOID) rp, MAXIMUM_REPARSE_DATA_BUFFER_SIZE)))
+       {
+         if (rp->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT)
+           {
+             RtlInitCountedUnicodeString (&subst, 
+                 (WCHAR *)((char *)rp->MountPointReparseBuffer.PathBuffer
+                           + rp->MountPointReparseBuffer.SubstituteNameOffset),
+                 rp->MountPointReparseBuffer.SubstituteNameLength);
+             /* Only volume mountpoints are treated as directories. */
+             if (RtlEqualUnicodePathPrefix (&subst, &ro_u_volume, TRUE))
+               ret = DT_DIR;
+             else
+               ret = DT_LNK;
+           }
+         else if (rp->ReparseTag == IO_REPARSE_TAG_SYMLINK)
+           ret = DT_LNK;
+         NtClose (reph);
+       }
     }
   return ret;
 }
 
-static inline __ino64_t
-get_ino_by_handle (path_conv &pc, HANDLE hdl)
+inline __ino64_t
+path_conv::get_ino_by_handle (HANDLE hdl)
 {
   IO_STATUS_BLOCK io;
   FILE_INTERNAL_INFORMATION fai;
 
   if (NT_SUCCESS (NtQueryInformationFile (hdl, &io, &fai, sizeof fai,
                                          FileInternalInformation))
-      && pc.isgood_inode (fai.FileId.QuadPart))
+      && isgood_inode (fai.FileId.QuadPart))
     return fai.FileId.QuadPart;
   return 0;
 }
 
+#if 0
+/* This function is obsolete.  We're keeping it in so we don't forget
+   that we already did all that at one point. */
 unsigned __stdcall
 path_conv::ndisk_links (DWORD nNumberOfLinks)
 {
@@ -206,11 +227,11 @@ path_conv::ndisk_links (DWORD nNumberOfLinks)
 
   unsigned count = 0;
   bool first = true;
-  PFILE_DIRECTORY_INFORMATION fdibuf = (PFILE_DIRECTORY_INFORMATION)
+  PFILE_BOTH_DIRECTORY_INFORMATION fdibuf = (PFILE_BOTH_DIRECTORY_INFORMATION)
                                       alloca (65536);
   __DIR_mounts *dir = new __DIR_mounts (normalized_path);
   while (NT_SUCCESS (NtQueryDirectoryFile (fh, NULL, NULL, NULL, &io, fdibuf,
-                                          65536, FileDirectoryInformation,
+                                          65536, FileBothDirectoryInformation,
                                           FALSE, NULL, first)))
     {
       if (first)
@@ -222,9 +243,9 @@ path_conv::ndisk_links (DWORD nNumberOfLinks)
          if (fdibuf->FileNameLength != 2 || fdibuf->FileName[0] != L'.')
            count = 2;
        }
-      for (PFILE_DIRECTORY_INFORMATION pfdi = fdibuf;
+      for (PFILE_BOTH_DIRECTORY_INFORMATION pfdi = fdibuf;
           pfdi;
-          pfdi = (PFILE_DIRECTORY_INFORMATION)
+          pfdi = (PFILE_BOTH_DIRECTORY_INFORMATION)
                  (pfdi->NextEntryOffset ? (PBYTE) pfdi + pfdi->NextEntryOffset
                                         : NULL))
        {
@@ -263,6 +284,7 @@ path_conv::ndisk_links (DWORD nNumberOfLinks)
   delete dir;
   return count;
 }
+#endif
 
 /* For files on NFS shares, we request an EA of type NfsV3Attributes.
    This returns the content of a struct fattr3 as defined in RFC 1813.
@@ -271,114 +293,100 @@ path_conv::ndisk_links (DWORD nNumberOfLinks)
 int __stdcall
 fhandler_base::fstat_by_nfs_ea (struct __stat64 *buf)
 {
-  NTSTATUS status;
-  IO_STATUS_BLOCK io;
-  struct {
-    FILE_FULL_EA_INFORMATION ffei;
-    char buf[sizeof (NFS_V3_ATTR) + sizeof (fattr3)];
-  } ffei_buf;
-  struct {
-     FILE_GET_EA_INFORMATION fgei;
-     char buf[sizeof (NFS_V3_ATTR)];
-   } fgei_buf;
-
-  fgei_buf.fgei.NextEntryOffset = 0;
-  fgei_buf.fgei.EaNameLength = sizeof (NFS_V3_ATTR) - 1;
-  stpcpy (fgei_buf.fgei.EaName, NFS_V3_ATTR);
-  status = NtQueryEaFile (get_handle (), &io,
-                         &ffei_buf.ffei, sizeof ffei_buf, TRUE,
-                         &fgei_buf.fgei, sizeof fgei_buf, NULL, TRUE);
-  if (NT_SUCCESS (status))
-    {
-      fattr3 *nfs_attr = (fattr3 *) (ffei_buf.ffei.EaName
-                                    + ffei_buf.ffei.EaNameLength + 1);
-      buf->st_dev = nfs_attr->fsid;
-      buf->st_ino = nfs_attr->fileid;
-      buf->st_mode = (nfs_attr->mode & 0xfff)
-                    | nfs_type_mapping[nfs_attr->type & 7];
-      buf->st_nlink = nfs_attr->nlink;
-      /* FIXME: How to convert UNIX uid/gid to Windows SIDs? */
+  fattr3 *nfs_attr = pc.nfsattr ();
+
+  if (get_io_handle ())
+    {
+      /* NFS stumbles over its own caching.  If you write to the file,
+        a subsequent fstat does not return the actual size of the file,
+        but the size at the time the handle has been opened.  Unless
+        access through another handle invalidates the caching within the
+        NFS client. */
+      if (get_access () & GENERIC_WRITE)
+       FlushFileBuffers (get_io_handle ());
+      nfs_fetch_fattr3 (get_io_handle (), nfs_attr);
+    }
+  buf->st_dev = nfs_attr->fsid;
+  buf->st_ino = nfs_attr->fileid;
+  buf->st_mode = (nfs_attr->mode & 0xfff)
+                | nfs_type_mapping[nfs_attr->type & 7];
+  buf->st_nlink = nfs_attr->nlink;
+  /* FIXME: How to convert UNIX uid/gid to Windows SIDs? */
 #if 0
-      buf->st_uid = nfs_attr->uid;
-      buf->st_gid = nfs_attr->gid;
+  buf->st_uid = nfs_attr->uid;
+  buf->st_gid = nfs_attr->gid;
 #else
-      buf->st_uid = myself->uid;
-      buf->st_gid = myself->gid;
+  buf->st_uid = myself->uid;
+  buf->st_gid = myself->gid;
 #endif
-      buf->st_rdev = makedev (nfs_attr->rdev.specdata1,
-                             nfs_attr->rdev.specdata2);
-      buf->st_size = nfs_attr->size;
-      buf->st_blksize = PREFERRED_IO_BLKSIZE;
-      buf->st_blocks = nfs_attr->used / 512;
-      buf->st_atim = nfs_attr->atime;
-      buf->st_mtim = nfs_attr->mtime;
-      buf->st_ctim = nfs_attr->ctime;
-      return 0;
-    }
-  debug_printf ("%p = NtQueryEaFile(%S)", status, pc.get_nt_native_path ());
-  return -1;
+  buf->st_rdev = makedev (nfs_attr->rdev.specdata1,
+                         nfs_attr->rdev.specdata2);
+  buf->st_size = nfs_attr->size;
+  buf->st_blksize = PREFERRED_IO_BLKSIZE;
+  buf->st_blocks = (nfs_attr->used + S_BLKSIZE - 1) / S_BLKSIZE;
+  buf->st_atim = nfs_attr->atime;
+  buf->st_mtim = nfs_attr->mtime;
+  buf->st_ctim = nfs_attr->ctime;
+  return 0;
 }
 
 int __stdcall
 fhandler_base::fstat_by_handle (struct __stat64 *buf)
 {
-  NTSTATUS status;
-  IO_STATUS_BLOCK io;
-
-  if (pc.fs_is_nfs ())
-    return fstat_by_nfs_ea (buf);
-
   /* Don't use FileAllInformation info class.  It returns a pathname rather
      than a filename, so it needs a really big buffer for no good reason
      since we don't need the name anyway.  So we just call the three info
      classes necessary to get all information required by stat(2). */
-  FILE_BASIC_INFORMATION fbi;
   FILE_STANDARD_INFORMATION fsi;
   FILE_INTERNAL_INFORMATION fii;
 
-  status = NtQueryInformationFile (get_handle (), &io, &fbi, sizeof fbi,
-                                  FileBasicInformation);
-  if (!NT_SUCCESS (status))
-    {
-      debug_printf ("%p = NtQueryInformationFile(%S, FileBasicInformation)",
-                   status, pc.get_nt_native_path ());
-      return -1;
-    }
-  status = NtQueryInformationFile (get_handle (), &io, &fsi, sizeof fsi,
-                                  FileStandardInformation);
-  if (!NT_SUCCESS (status))
+  HANDLE h = get_stat_handle ();
+  NTSTATUS status = 0;
+  IO_STATUS_BLOCK io;
+
+  /* If the file has been opened for other purposes than stat, we can't rely
+     on the information stored in pc.fnoi.  So we overwrite them here. */
+  if (get_io_handle ())
     {
-      debug_printf ("%p = NtQueryInformationFile(%S, FileStandardInformation)",
-                   status, pc.get_nt_native_path ());
-      return -1;
-    }
-  status = NtQueryInformationFile (get_handle (), &io, &fii, sizeof fii,
-                                  FileInternalInformation);
-  if (!NT_SUCCESS (status))
+      PFILE_NETWORK_OPEN_INFORMATION pfnoi = pc.fnoi ();
+      status = NtQueryInformationFile (h, &io, pfnoi, sizeof *pfnoi,
+                                      FileNetworkOpenInformation);
+      if (!NT_SUCCESS (status))
+       {
+        debug_printf ("%p = NtQueryInformationFile(%S, "
+                      "FileNetworkOpenInformation)",
+                      status, pc.get_nt_native_path ());
+        return -1;
+       }
+    }
+  if (!pc.hasgood_inode ())
+    fsi.NumberOfLinks = 1;
+  else
     {
-      debug_printf ("%p = NtQueryInformationFile(%S, FileInternalInformation)",
-                   status, pc.get_nt_native_path ());
-      return -1;
+      status = NtQueryInformationFile (h, &io, &fsi, sizeof fsi,
+                                      FileStandardInformation);
+      if (!NT_SUCCESS (status))
+       {
+         debug_printf ("%p = NtQueryInformationFile(%S, "
+                       "FileStandardInformation)",
+                       status, pc.get_nt_native_path ());
+         return -1;
+       }
+      if (!ino)
+       {
+         status = NtQueryInformationFile (h, &io, &fii, sizeof fii,
+                                          FileInternalInformation);
+         if (!NT_SUCCESS (status))
+           {
+             debug_printf ("%p = NtQueryInformationFile(%S, "
+                           "FileInternalInformation)",
+                           status, pc.get_nt_native_path ());
+             return -1;
+           }
+         ino = fii.FileId.QuadPart;
+       }
     }
-  /* If the change time is 0, it's a file system which doesn't
-     support a change timestamp.  In that case use the LastWriteTime
-     entry, as in other calls to fstat_helper. */
-  if (pc.is_rep_symlink ())
-    fbi.FileAttributes &= ~FILE_ATTRIBUTE_DIRECTORY;
-  pc.file_attributes (fbi.FileAttributes);
-  return fstat_helper (buf,
-                  fbi.ChangeTime.QuadPart
-                  ? *(FILETIME *) (void *) &fbi.ChangeTime
-                  : *(FILETIME *) (void *) &fbi.LastWriteTime,
-                  *(FILETIME *) (void *) &fbi.LastAccessTime,
-                  *(FILETIME *) (void *) &fbi.LastWriteTime,
-                  *(FILETIME *) (void *) &fbi.CreationTime,
-                  get_dev (),
-                  fsi.EndOfFile.QuadPart,
-                  fsi.AllocationSize.QuadPart,
-                  fii.FileId.QuadPart,
-                  fsi.NumberOfLinks,
-                  fbi.FileAttributes);
+  return fstat_helper (buf, fsi.NumberOfLinks);
 }
 
 int __stdcall
@@ -394,75 +402,36 @@ fhandler_base::fstat_by_name (struct __stat64 *buf)
     FILE_ID_BOTH_DIR_INFORMATION fdi;
     WCHAR buf[NAME_MAX + 1];
   } fdi_buf;
-  LARGE_INTEGER FileId;
 
-  RtlSplitUnicodePath (pc.get_nt_native_path (), &dirname, &basename);
-  InitializeObjectAttributes (&attr, &dirname, pc.objcaseinsensitive (),
-                             NULL, NULL);
-  if (!NT_SUCCESS (status = NtOpenFile (&dir, SYNCHRONIZE | FILE_LIST_DIRECTORY,
-                                      &attr, &io, FILE_SHARE_VALID_FLAGS,
-                                      FILE_SYNCHRONOUS_IO_NONALERT
-                                      | FILE_OPEN_FOR_BACKUP_INTENT
-                                      | FILE_DIRECTORY_FILE)))
-    {
-      debug_printf ("%p = NtOpenFile(%S)", status, pc.get_nt_native_path ());
-      goto too_bad;
-    }
-  if (wincap.has_fileid_dirinfo () && !pc.has_buggy_fileid_dirinfo ()
-      && NT_SUCCESS (status = NtQueryDirectoryFile (dir, NULL, NULL, NULL, &io,
-                                                &fdi_buf.fdi, sizeof fdi_buf,
-                                                FileIdBothDirectoryInformation,
-                                                TRUE, &basename, TRUE)))
-    FileId = fdi_buf.fdi.FileId;
-  else if (NT_SUCCESS (status = NtQueryDirectoryFile (dir, NULL, NULL, NULL,
-                                                &io, &fdi_buf.fdi,
-                                                sizeof fdi_buf,
-                                                FileDirectoryInformation,
-                                                TRUE, &basename, TRUE)))
-    FileId.QuadPart = 0; /* get_ino is called in fstat_helper. */
-  if (!NT_SUCCESS (status))
+  if (!ino && pc.hasgood_inode ()
+      && wincap.has_fileid_dirinfo () && !pc.has_buggy_fileid_dirinfo ())
     {
-      debug_printf ("%p = NtQueryDirectoryFile(%S)", status,
-                   pc.get_nt_native_path ());
-      NtClose (dir);
-      goto too_bad;
-    }
-  NtClose (dir);
-  /* If the change time is 0, it's a file system which doesn't
-     support a change timestamp.  In that case use the LastWriteTime
-     entry, as in other calls to fstat_helper. */
-  if (pc.is_rep_symlink ())
-    fdi_buf.fdi.FileAttributes &= ~FILE_ATTRIBUTE_DIRECTORY;
-  pc.file_attributes (fdi_buf.fdi.FileAttributes);
-  return fstat_helper (buf,
-                      fdi_buf.fdi.ChangeTime.QuadPart ?
-                      *(FILETIME *) (void *) &fdi_buf.fdi.ChangeTime :
-                      *(FILETIME *) (void *) &fdi_buf.fdi.LastWriteTime,
-                      *(FILETIME *) (void *) &fdi_buf.fdi.LastAccessTime,
-                      *(FILETIME *) (void *) &fdi_buf.fdi.LastWriteTime,
-                      *(FILETIME *) (void *) &fdi_buf.fdi.CreationTime,
-                      pc.fs_serial_number (),
-                      fdi_buf.fdi.EndOfFile.QuadPart,
-                      fdi_buf.fdi.AllocationSize.QuadPart,
-                      FileId.QuadPart,
-                      1,
-                      fdi_buf.fdi.FileAttributes);
-
-too_bad:
-  LARGE_INTEGER ft;
-  /* Arbitrary value: 2006-12-01 */
-  RtlSecondsSince1970ToTime (1164931200L, &ft);
-  return fstat_helper (buf,
-                      *(FILETIME *) (void *) &ft,
-                      *(FILETIME *) (void *) &ft,
-                      *(FILETIME *) (void *) &ft,
-                      *(FILETIME *) (void *) &ft,
-                      0,
-                      0ULL,
-                      -1LL,
-                      0ULL,
-                      1,
-                      pc.file_attributes ());
+      RtlSplitUnicodePath (pc.get_nt_native_path (), &dirname, &basename);
+      InitializeObjectAttributes (&attr, &dirname, pc.objcaseinsensitive (),
+                                 NULL, NULL);
+      status = NtOpenFile (&dir, SYNCHRONIZE | FILE_LIST_DIRECTORY,
+                          &attr, &io, FILE_SHARE_VALID_FLAGS,
+                          FILE_SYNCHRONOUS_IO_NONALERT
+                          | FILE_OPEN_FOR_BACKUP_INTENT
+                          | FILE_DIRECTORY_FILE);
+      if (!NT_SUCCESS (status))
+       debug_printf ("%p = NtOpenFile(%S)", status,
+                     pc.get_nt_native_path ());
+      else
+       {
+         status = NtQueryDirectoryFile (dir, NULL, NULL, NULL, &io,
+                                        &fdi_buf.fdi, sizeof fdi_buf,
+                                        FileIdBothDirectoryInformation,
+                                        TRUE, &basename, TRUE);
+         NtClose (dir);
+         if (!NT_SUCCESS (status))
+           debug_printf ("%p = NtQueryDirectoryFile(%S)", status,
+                         pc.get_nt_native_path ());
+         else
+           ino = fdi_buf.fdi.FileId.QuadPart;
+       }
+    }
+  return fstat_helper (buf, 1);
 }
 
 int __stdcall
@@ -472,23 +441,31 @@ fhandler_base::fstat_fs (struct __stat64 *buf)
   int oret;
   int open_flags = O_RDONLY | O_BINARY;
 
-  if (get_handle ())
+  if (get_stat_handle ())
     {
       if (!nohandle () && !is_fs_special ())
-       res = fstat_by_handle (buf);
+       res = pc.fs_is_nfs () ? fstat_by_nfs_ea (buf) : fstat_by_handle (buf);
       if (res)
        res = fstat_by_name (buf);
       return res;
     }
-  query_open (query_read_attributes);
+  /* First try to open with generic read access.  This allows to read the file
+     in fstat_helper (when checking for executability) without having to
+     re-open it.  Opening a file can take a lot of time on network drives
+     so we try to avoid that. */
   oret = open_fs (open_flags, 0);
+  if (!oret)
+    {
+      query_open (query_read_attributes);
+      oret = open_fs (open_flags, 0);
+    }
   if (oret)
     {
       /* We now have a valid handle, regardless of the "nohandle" state.
         Since fhandler_base::open only calls CloseHandle if !nohandle,
         we have to set it to false before calling close and restore
         the state afterwards. */
-      res = fstat_by_handle (buf);
+      res = pc.fs_is_nfs () ? fstat_by_nfs_ea (buf) : fstat_by_handle (buf);
       bool no_handle = nohandle ();
       nohandle (false);
       close_fs ();
@@ -501,44 +478,36 @@ fhandler_base::fstat_fs (struct __stat64 *buf)
   return res;
 }
 
-/* The ftChangeTime is taken from the NTFS ChangeTime entry, if reading
-   the file information using NtQueryInformationFile succeeded.  If not,
-   it's faked using the LastWriteTime entry from GetFileInformationByHandle
-   or FindFirstFile.  We're deliberatly not using the creation time anymore
-   to simplify interaction with native Windows applications which choke on
-   creation times >= access or write times.
-
-   Note that the dwFileAttributes member of the file information evaluated
-   in the calling function is used here, not the pc.fileattr member, since
-   the latter might be old and not reflect the actual state of the file. */
 int __stdcall
 fhandler_base::fstat_helper (struct __stat64 *buf,
-                            FILETIME ftChangeTime,
-                            FILETIME ftLastAccessTime,
-                            FILETIME ftLastWriteTime,
-                            FILETIME ftCreationTime,
-                            DWORD dwVolumeSerialNumber,
-                            ULONGLONG nFileSize,
-                            LONGLONG nAllocSize,
-                            ULONGLONG nFileIndex,
-                            DWORD nNumberOfLinks,
-                            DWORD dwFileAttributes)
+                            DWORD nNumberOfLinks)
 {
   IO_STATUS_BLOCK st;
   FILE_COMPRESSION_INFORMATION fci;
-
-  to_timestruc_t (&ftLastAccessTime, &buf->st_atim);
-  to_timestruc_t (&ftLastWriteTime, &buf->st_mtim);
-  to_timestruc_t (&ftChangeTime, &buf->st_ctim);
-  to_timestruc_t (&ftCreationTime, &buf->st_birthtim);
-  buf->st_dev = dwVolumeSerialNumber;
-  buf->st_size = (_off64_t) nFileSize;
-  /* The number of links to a directory includes the
-     number of subdirectories in the directory, since all
-     those subdirectories point to it.
-     This is too slow on remote drives, so we do without it.
-     Setting the count to 2 confuses `find (1)' command. So
-     let's try it with `1' as link count. */
+  HANDLE h = get_stat_handle ();
+  PFILE_NETWORK_OPEN_INFORMATION pfnoi = pc.fnoi ();
+  ULONG attributes = pc.file_attributes ();
+
+  to_timestruc_t ((PFILETIME) &pfnoi->LastAccessTime, &buf->st_atim);
+  to_timestruc_t ((PFILETIME) &pfnoi->LastWriteTime, &buf->st_mtim);
+  /* If the ChangeTime is 0, the underlying FS doesn't support this timestamp
+     (FAT for instance).  If so, it's faked using LastWriteTime. */
+  to_timestruc_t (pfnoi->ChangeTime.QuadPart ? (PFILETIME) &pfnoi->ChangeTime
+                                           : (PFILETIME) &pfnoi->LastWriteTime,
+                 &buf->st_ctim);
+  to_timestruc_t ((PFILETIME) &pfnoi->CreationTime, &buf->st_birthtim);
+  buf->st_rdev = buf->st_dev = get_dev ();
+  /* CV 2011-01-13: Observations on the Cygwin mailing list point to an
+     interesting behaviour in some Windows versions.  Apparently the size of
+     a directory is computed at the time the directory is first scanned.  This
+     can result in two subsequent NtQueryInformationFile calls to return size
+     0 in the first call and size > 0 in the second call.  This in turn can
+     affect applications like newer tar.
+     FIXME: Is the allocation size affected as well? */
+  buf->st_size = pc.isdir () ? 0 : (_off64_t) pfnoi->EndOfFile.QuadPart;
+  /* The number of links to a directory includes the number of subdirectories
+     in the directory, since all those subdirectories point to it.  However,
+     this is painfully slow, so we do without it. */
 #if 0
   buf->st_nlink = pc.ndisk_links (nNumberOfLinks);
 #else
@@ -546,33 +515,34 @@ fhandler_base::fstat_helper (struct __stat64 *buf,
 #endif
 
   /* Enforce namehash as inode number on untrusted file systems. */
-  if (pc.isgood_inode (nFileIndex))
-    buf->st_ino = (__ino64_t) nFileIndex;
+  if (ino && pc.isgood_inode (ino))
+    buf->st_ino = (__ino64_t) ino;
   else
     buf->st_ino = get_ino ();
 
   buf->st_blksize = PREFERRED_IO_BLKSIZE;
 
-  if (nAllocSize >= 0LL)
+  if (pfnoi->AllocationSize.QuadPart >= 0LL)
     /* A successful NtQueryInformationFile returns the allocation size
        correctly for compressed and sparse files as well. */
-    buf->st_blocks = (nAllocSize + S_BLKSIZE - 1) / S_BLKSIZE;
-  else if (::has_attribute (dwFileAttributes, FILE_ATTRIBUTE_COMPRESSED
-                                             | FILE_ATTRIBUTE_SPARSE_FILE)
-          && get_handle () && !is_fs_special ()
-          && !NtQueryInformationFile (get_handle (), &st, (PVOID) &fci,
-                                     sizeof fci, FileCompressionInformation))
+    buf->st_blocks = (pfnoi->AllocationSize.QuadPart + S_BLKSIZE - 1)
+                    / S_BLKSIZE;
+  else if (::has_attribute (attributes, FILE_ATTRIBUTE_COMPRESSED
+                                       | FILE_ATTRIBUTE_SPARSE_FILE)
+          && h && !is_fs_special ()
+          && !NtQueryInformationFile (h, &st, (PVOID) &fci, sizeof fci,
+                                      FileCompressionInformation))
     /* Otherwise we request the actual amount of bytes allocated for
        compressed and sparsed files. */
     buf->st_blocks = (fci.CompressedFileSize.QuadPart + S_BLKSIZE - 1)
                     / S_BLKSIZE;
   else
     /* Otherwise compute no. of blocks from file size. */
-    buf->st_blocks  = (buf->st_size + S_BLKSIZE - 1) / S_BLKSIZE;
+    buf->st_blocks = (buf->st_size + S_BLKSIZE - 1) / S_BLKSIZE;
 
   buf->st_mode = 0;
-  /* Using a side effect: get_file_attibutes checks for
-     directory. This is used, to set S_ISVTX, if needed.  */
+  /* Using a side effect: get_file_attributes checks for directory.
+     This is used, to set S_ISVTX, if needed.  */
   if (pc.isdir ())
     buf->st_mode = S_IFDIR;
   else if (pc.issymlink ())
@@ -580,19 +550,18 @@ fhandler_base::fstat_helper (struct __stat64 *buf,
       buf->st_size = pc.get_symlink_length ();
       /* symlinks are everything for everyone! */
       buf->st_mode = S_IFLNK | S_IRWXU | S_IRWXG | S_IRWXO;
-      get_file_attribute (get_handle (), pc, NULL,
+      get_file_attribute (h, pc, NULL,
                          &buf->st_uid, &buf->st_gid);
       goto done;
     }
   else if (pc.issocket ())
     buf->st_mode = S_IFSOCK;
 
-  if (!get_file_attribute (is_fs_special () && !pc.issocket ()
-                          ? NULL : get_handle (), pc,
+  if (!get_file_attribute (is_fs_special () && !pc.issocket () ? NULL : h, pc,
                           &buf->st_mode, &buf->st_uid, &buf->st_gid))
     {
       /* If read-only attribute is set, modify ntsec return value */
-      if (::has_attribute (dwFileAttributes, FILE_ATTRIBUTE_READONLY)
+      if (::has_attribute (attributes, FILE_ATTRIBUTE_READONLY)
          && !pc.isdir () && !pc.issymlink ())
        buf->st_mode &= ~(S_IWUSR | S_IWGRP | S_IWOTH);
 
@@ -602,7 +571,7 @@ fhandler_base::fstat_helper (struct __stat64 *buf,
        buf->st_mode |= S_IFREG;
       else
        {
-         buf->st_dev = dev ();
+         buf->st_dev = buf->st_rdev = dev ();
          buf->st_mode = dev ().mode;
          buf->st_size = 0;
        }
@@ -611,7 +580,7 @@ fhandler_base::fstat_helper (struct __stat64 *buf,
     {
       buf->st_mode |= STD_RBITS;
 
-      if (!::has_attribute (dwFileAttributes, FILE_ATTRIBUTE_READONLY))
+      if (!::has_attribute (attributes, FILE_ATTRIBUTE_READONLY))
        buf->st_mode |= STD_WBITS;
       /* | S_IWGRP | S_IWOTH; we don't give write to group etc */
 
@@ -621,7 +590,7 @@ fhandler_base::fstat_helper (struct __stat64 *buf,
        /* nothing */;
       else if (is_fs_special ())
        {
-         buf->st_dev = dev ();
+         buf->st_dev = buf->st_rdev = dev ();
          buf->st_mode = dev ().mode;
          buf->st_size = 0;
        }
@@ -629,7 +598,7 @@ fhandler_base::fstat_helper (struct __stat64 *buf,
        {
          buf->st_mode |= S_IFREG;
          /* Check suffix for executable file. */
-         if (pc.exec_state () == dont_know_if_executable)
+         if (pc.exec_state () != is_executable)
            {
              PUNICODE_STRING path = pc.get_nt_native_path ();
 
@@ -638,26 +607,38 @@ fhandler_base::fstat_helper (struct __stat64 *buf,
                  || RtlEqualUnicodePathSuffix (path, &ro_u_com, TRUE))
                pc.set_exec ();
            }
-         /* No known sufix, check file header.  This catches binaries and
+         /* No known suffix, check file header.  This catches binaries and
             shebang scripts. */
          if (pc.exec_state () == dont_know_if_executable)
            {
              OBJECT_ATTRIBUTES attr;
-             HANDLE h;
+             NTSTATUS status = 0;
              IO_STATUS_BLOCK io;
 
-             InitializeObjectAttributes (&attr, &ro_u_empty, 0, get_handle (),
-                                         NULL);
-             if (NT_SUCCESS (NtOpenFile (&h, FILE_READ_DATA, &attr, &io,
-                                         FILE_SHARE_VALID_FLAGS, 0)))
+             /* We have to re-open the file.  Either the file is not opened
+                for reading, or the read will change the file position of the
+                original handle. */
+             pc.init_reopen_attr (&attr, h);
+             status = NtOpenFile (&h, SYNCHRONIZE | FILE_READ_DATA,
+                                  &attr, &io, FILE_SHARE_VALID_FLAGS,
+                                  FILE_OPEN_FOR_BACKUP_INTENT
+                                  | FILE_SYNCHRONOUS_IO_NONALERT);
+             if (!NT_SUCCESS (status))
+               debug_printf ("%p = NtOpenFile(%S)", status,
+                             pc.get_nt_native_path ());
+             else
                {
                  LARGE_INTEGER off = { QuadPart:0LL };
                  char magic[3];
 
-                 if (NT_SUCCESS (NtReadFile (h, NULL, NULL, NULL, &io, magic,
-                                             3, &off, NULL))
-                     && has_exec_chars (magic, io.Information))
+                 status = NtReadFile (h, NULL, NULL, NULL,
+                                      &io, magic, 3, &off, NULL);
+                 if (!NT_SUCCESS (status))
+                   debug_printf ("%p = NtReadFile(%S)", status,
+                                 pc.get_nt_native_path ());
+                 else if (has_exec_chars (magic, io.Information))
                    {
+                     /* Heureka, it's an executable */
                      pc.set_exec ();
                      buf->st_mode |= STD_XBITS;
                    }
@@ -670,12 +651,19 @@ fhandler_base::fstat_helper (struct __stat64 *buf,
 
       /* This fakes the permissions of all files to match the current umask. */
       buf->st_mode &= ~(cygheap->umask);
+      /* If the FS supports ACLs, we're here because we couldn't even open
+        the file for READ_CONTROL access.  Chances are high that the file's
+        security descriptor has no ACE for "Everyone", so we should not fake
+        any access for "others". */
+      if (has_acls ())
+       buf->st_mode &= ~(S_IROTH | S_IWOTH | S_IXOTH);
     }
 
  done:
-  syscall_printf ("0 = fstat (, %p) st_atime=%x st_size=%D, st_mode=%p, st_ino=%D, sizeof=%d",
-                 buf, buf->st_atime, buf->st_size, buf->st_mode,
-                 buf->st_ino, sizeof (*buf));
+  syscall_printf ("0 = fstat (%S, %p) st_atime=%x st_size=%D, st_mode=%p, "
+                 "st_ino=%D, sizeof=%d",
+                 pc.get_nt_native_path (), buf, buf->st_atime, buf->st_size,
+                 buf->st_mode, buf->st_ino, sizeof (*buf));
   return 0;
 }
 
@@ -693,15 +681,19 @@ fhandler_disk_file::fstatvfs (struct statvfs *sfs)
   IO_STATUS_BLOCK io;
   FILE_FS_FULL_SIZE_INFORMATION full_fsi;
   FILE_FS_SIZE_INFORMATION fsi;
+  /* We must not use the stat handle here, even if it exists.  The handle
+     has been opened with FILE_OPEN_REPARSE_POINT, thus, in case of a volume
+     mount point, it points to the FS of the mount point, rather than to the
+     mounted FS. */
   HANDLE fh = get_handle ();
 
   if (!fh)
     {
       OBJECT_ATTRIBUTES attr;
       opened = NT_SUCCESS (NtOpenFile (&fh, READ_CONTROL,
-                                    pc.get_object_attr (attr, sec_none_nih),
-                                    &io, FILE_SHARE_VALID_FLAGS,
-                                    FILE_OPEN_FOR_BACKUP_INTENT));
+                                      pc.get_object_attr (attr, sec_none_nih),
+                                      &io, FILE_SHARE_VALID_FLAGS,
+                                      FILE_OPEN_FOR_BACKUP_INTENT));
       if (!opened)
        {
          /* Can't open file.  Try again with parent dir. */
@@ -709,8 +701,8 @@ fhandler_disk_file::fstatvfs (struct statvfs *sfs)
          RtlSplitUnicodePath (pc.get_nt_native_path (), &dirname, NULL);
          attr.ObjectName = &dirname;
          opened = NT_SUCCESS (NtOpenFile (&fh, READ_CONTROL, &attr, &io,
-                                        FILE_SHARE_VALID_FLAGS,
-                                        FILE_OPEN_FOR_BACKUP_INTENT));
+                                          FILE_SHARE_VALID_FLAGS,
+                                          FILE_OPEN_FOR_BACKUP_INTENT));
          if (!opened)
            goto out;
        }
@@ -787,7 +779,7 @@ fhandler_disk_file::fchmod (mode_t mode)
 
   if (!get_handle ())
     {
-      query_open (query_write_control);
+      query_open (query_write_dac);
       if (!(oret = open (O_BINARY, 0)))
        {
          /* Need WRITE_DAC|WRITE_OWNER to write ACLs. */
@@ -856,7 +848,7 @@ fhandler_disk_file::fchmod (mode_t mode)
       OBJECT_ATTRIBUTES attr;
       HANDLE fh;
 
-      InitializeObjectAttributes (&attr, &ro_u_empty, 0, get_handle (), NULL);
+      pc.init_reopen_attr (&attr, get_handle ());
       if (NT_SUCCESS (NtOpenFile (&fh, FILE_WRITE_ATTRIBUTES, &attr, &io,
                                  FILE_SHARE_VALID_FLAGS,
                                  FILE_OPEN_FOR_BACKUP_INTENT)))
@@ -975,13 +967,7 @@ cant_access_acl:
              set_errno (ENOSPC);
            else
              {
-               if (!get_handle ())
-                 {
-                   query_open (query_read_attributes);
-                   oret = open (O_BINARY, 0);
-                 }
-               if ((oret && !fstat_by_handle (&st))
-                   || !fstat_by_name (&st))
+               if (!fstat (&st))
                  {
                    aclbufp[0].a_type = USER_OBJ;
                    aclbufp[0].a_id = st.st_uid;
@@ -1009,7 +995,8 @@ cant_access_acl:
     }
   else
     {
-      if (!get_handle ())
+      if ((cmd == SETACL && !get_handle ())
+         || (cmd != SETACL && !get_stat_handle ()))
        {
          query_open (cmd == SETACL ? query_write_control : query_read_control);
          if (!(oret = open (O_BINARY, 0)))
@@ -1046,10 +1033,16 @@ cant_access_acl:
            if (!aclbufp)
              set_errno(EFAULT);
            else
-             res = getacl (get_handle (), pc, nentries, aclbufp);
+             res = getacl (get_stat_handle (), pc, nentries, aclbufp);
+             /* For this ENOSYS case, see security.cc:get_file_attribute(). */
+             if (res == -1 && get_errno () == ENOSYS)
+               goto cant_access_acl;
            break;
          case GETACLCNT:
-           res = getacl (get_handle (), pc, 0, NULL);
+           res = getacl (get_stat_handle (), pc, 0, NULL);
+           /* Ditto. */
+           if (res == -1 && get_errno () == ENOSYS)
+             goto cant_access_acl;
            break;
          default:
            set_errno (EINVAL);
@@ -1186,7 +1179,8 @@ fhandler_disk_file::ftruncate (_off64_t length, bool allow_truncate)
 int
 fhandler_disk_file::link (const char *newpath)
 {
-  path_conv newpc (newpath, PC_SYM_NOFOLLOW | PC_POSIX, stat_suffixes);
+  size_t nlen = strlen (newpath);
+  path_conv newpc (newpath, PC_SYM_NOFOLLOW | PC_POSIX | PC_NULLEMPTY, stat_suffixes);
   if (newpc.error)
     {
       set_errno (newpc.error);
@@ -1200,10 +1194,21 @@ fhandler_disk_file::link (const char *newpath)
       return -1;
     }
 
-  char new_buf[strlen (newpath) + 5];
+  if (isdirsep (newpath[nlen - 1]) || has_dot_last_component (newpath, false))
+    {
+      set_errno (ENOENT);
+      return -1;
+    }
+
+  char new_buf[nlen + 5];
   if (!newpc.error)
     {
-      if (pc.is_lnk_special ())
+      /* If the original file is a lnk special file (except for sockets),
+        and if the original file has a .lnk suffix, add one to the hardlink
+        as well. */
+      if (pc.is_lnk_special () && !pc.issocket ()
+         && RtlEqualUnicodePathSuffix (pc.get_nt_native_path (),
+                                       &ro_u_lnk, TRUE))
        {
          /* Shortcut hack. */
          stpcpy (stpcpy (new_buf, newpath), ".lnk");
@@ -1224,17 +1229,13 @@ fhandler_disk_file::link (const char *newpath)
        }
     }
 
-  HANDLE fh;
-  NTSTATUS status;
-  OBJECT_ATTRIBUTES attr;
-  IO_STATUS_BLOCK io;
-  status = NtOpenFile (&fh, READ_CONTROL,
-                      pc.get_object_attr (attr, sec_none_nih), &io,
-                      FILE_SHARE_VALID_FLAGS,
-                      FILE_OPEN_FOR_BACKUP_INTENT | FILE_OPEN_REPARSE_POINT);
-  if (!NT_SUCCESS (status))
+  /* We only need READ_CONTROL access so the handle returned in pc is
+     sufficient.  And if the file couldn't be opened with READ_CONTROL
+     access in path_conv, we won't be able to do it here anyway. */
+  HANDLE fh = get_stat_handle ();
+  if (!fh)
     {
-      __seterrno_from_nt_status (status);
+      set_errno (EACCES);
       return -1;
     }
   PUNICODE_STRING tgt = newpc.get_nt_native_path ();
@@ -1243,23 +1244,17 @@ fhandler_disk_file::link (const char *newpath)
   pfli->ReplaceIfExists = FALSE;
   pfli->RootDirectory = NULL;
   memcpy (pfli->FileName, tgt->Buffer, pfli->FileNameLength = tgt->Length);
+
+  NTSTATUS status;
+  IO_STATUS_BLOCK io;
   status = NtSetInformationFile (fh, &io, pfli, size, FileLinkInformation);
-  NtClose (fh);
   if (!NT_SUCCESS (status))
     {
       if (status == STATUS_INVALID_DEVICE_REQUEST)
        {
-         /* FS doesn't support hard links.  Try to copy file. */
-         WCHAR pcw[(pc.get_nt_native_path ()->Length / sizeof (WCHAR)) + 1];
-         WCHAR newpcw[(newpc.get_nt_native_path ()->Length / sizeof (WCHAR))
-                      + 1];
-         if (!CopyFileW (pc.get_wide_win32_path (pcw),
-                         newpc.get_wide_win32_path (newpcw), TRUE))
-           {
-             __seterrno ();
-             return -1;
-           }
-         SetFileAttributesW (newpcw, pc.file_attributes ());
+         /* FS doesn't support hard links.  Linux returns EPERM. */
+         set_errno (EPERM);
+         return -1;
        }
       else
        {
@@ -1302,8 +1297,7 @@ fhandler_base::utimens_fs (const struct timespec *tvp)
       closeit = true;
     }
 
-  gettimeofday (reinterpret_cast<struct timeval *> (&timeofday), 0);
-  timeofday.tv_nsec *= 1000;
+  clock_gettime (CLOCK_REALTIME, &timeofday);
   if (!tvp)
     tmp[1] = tmp[0] = timeofday;
   else
@@ -1311,6 +1305,8 @@ fhandler_base::utimens_fs (const struct timespec *tvp)
       if ((tvp[0].tv_nsec < UTIME_NOW || tvp[0].tv_nsec > 999999999L)
          || (tvp[1].tv_nsec < UTIME_NOW || tvp[1].tv_nsec > 999999999L))
        {
+         if (closeit)
+           close_fs ();
          set_errno (EINVAL);
          return -1;
        }
@@ -1337,7 +1333,7 @@ fhandler_base::utimens_fs (const struct timespec *tvp)
       OBJECT_ATTRIBUTES attr;
       HANDLE fh;
 
-      InitializeObjectAttributes (&attr, &ro_u_empty, 0, get_handle (), NULL);
+      pc.init_reopen_attr (&attr, get_handle ());
       if (NT_SUCCESS (NtOpenFile (&fh, FILE_WRITE_ATTRIBUTES, &attr, &io,
                                  FILE_SHARE_VALID_FLAGS,
                                  FILE_OPEN_FOR_BACKUP_INTENT)))
@@ -1405,7 +1401,7 @@ fhandler_base::open_fs (int flags, mode_t mode)
       return 0;
     }
 
-    ino = get_ino_by_handle (pc, get_handle ());
+    ino = pc.get_ino_by_handle (get_handle ());
     /* A unique ID is necessary to recognize fhandler entries which are
        duplicated by dup(2) or fork(2). */
     AllocateLocallyUniqueId ((PLUID) &unique_id);
@@ -1458,14 +1454,6 @@ fhandler_disk_file::mkdir (mode_t mode)
 {
   int res = -1;
   SECURITY_ATTRIBUTES sa = sec_none_nih;
-  security_descriptor sd;
-
-  /* See comments in fhander_base::open () for an explanation why we defer
-     setting security attributes on remote files. */
-  if (has_acls () && !pc.isremote ())
-    set_security_attribute (pc, S_IFDIR | ((mode & 07777) & ~cygheap->umask),
-                           &sa, sd);
-
   NTSTATUS status;
   HANDLE dir;
   OBJECT_ATTRIBUTES attr;
@@ -1500,9 +1488,10 @@ fhandler_disk_file::mkdir (mode_t mode)
                         p, plen);
   if (NT_SUCCESS (status))
     {
-      if (has_acls () && pc.isremote ())
+      if (has_acls ())
        set_file_attribute (dir, pc, ILLEGAL_UID, ILLEGAL_GID,
-                           S_IFDIR | ((mode & 07777) & ~cygheap->umask));
+                           S_JUSTCREATED | S_IFDIR
+                           | ((mode & 07777) & ~cygheap->umask));
       NtClose (dir);
       res = 0;
     }
@@ -1533,18 +1522,20 @@ fhandler_disk_file::rmdir ()
   /* Check for existence of remote dirs after trying to delete them.
      Two reasons:
      - Sometimes SMB indicates failure when it really succeeds.
-     - Removeing a directory on a samba drive doesn't return an error if the
-       directory can't be removed because it's not empty.  */
+     - Removing a directory on a Samba drive using an old Samba version
+       sometimes doesn't return an error, if the directory can't be removed
+       because it's not empty. */
   if (isremote ())
     {
       OBJECT_ATTRIBUTES attr;
       FILE_BASIC_INFORMATION fbi;
+      NTSTATUS q_status;
 
-      if (NT_SUCCESS (NtQueryAttributesFile
-                           (pc.get_object_attr (attr, sec_none_nih), &fbi)))
-       status = STATUS_DIRECTORY_NOT_EMPTY;
-      else
+      q_status = NtQueryAttributesFile (pc.get_object_attr (attr, sec_none_nih),                                        &fbi);
+      if (!NT_SUCCESS (status) && q_status == STATUS_OBJECT_NAME_NOT_FOUND)
        status = STATUS_SUCCESS;
+      else if (NT_SUCCESS (status) && NT_SUCCESS (q_status))
+       status = STATUS_DIRECTORY_NOT_EMPTY;
     }
   if (!NT_SUCCESS (status))
     {
@@ -1622,19 +1613,41 @@ fhandler_disk_file::opendir (int fd)
              OBJECT_ATTRIBUTES attr;
              NTSTATUS status;
              IO_STATUS_BLOCK io;
-
-             status = NtOpenFile (&get_handle (),
-                                  SYNCHRONIZE | FILE_LIST_DIRECTORY,
-                                  pc.get_object_attr (attr, sec_none_nih),
-                                  &io, FILE_SHARE_VALID_FLAGS,
-                                  FILE_SYNCHRONOUS_IO_NONALERT
-                                  | FILE_OPEN_FOR_BACKUP_INTENT
-                                  | FILE_DIRECTORY_FILE);
-             if (!NT_SUCCESS (status))
+             /* Tools like ls(1) call dirfd() to fetch the directory
+                descriptor for calls to facl or fstat.  The tight access mask
+                used so far is not sufficient to reuse the handle for these
+                calls, instead the facl/fstat calls find the handle to be
+                unusable and have to re-open the file for reading attributes
+                and control data.  So, what we do here is to try to open the
+                directory with more relaxed access mask which enables to use
+                the handle for the aforementioned purpose.  This should work
+                in almost all cases.  Only if it doesn't work due to
+                permission problems, we drop the additional access bits and
+                try again. */
+             ACCESS_MASK fstat_mask = READ_CONTROL | FILE_READ_ATTRIBUTES;
+
+             do
                {
-                 __seterrno_from_nt_status (status);
-                 goto free_mounts;
+                 status = NtOpenFile (&get_handle (),
+                                      SYNCHRONIZE | FILE_LIST_DIRECTORY
+                                      | fstat_mask,
+                                      pc.get_object_attr (attr, sec_none_nih),
+                                      &io, FILE_SHARE_VALID_FLAGS,
+                                      FILE_SYNCHRONOUS_IO_NONALERT
+                                      | FILE_OPEN_FOR_BACKUP_INTENT
+                                      | FILE_DIRECTORY_FILE);
+                 if (!NT_SUCCESS (status))
+                   {
+                     if (status == STATUS_ACCESS_DENIED && fstat_mask)
+                       fstat_mask = 0;
+                     else
+                       {
+                         __seterrno_from_nt_status (status);
+                         goto free_mounts;
+                       }
+                   }
                }
+             while (!NT_SUCCESS (status));
            }
 
          /* FileIdBothDirectoryInformation is apparently unsupported on
@@ -1644,7 +1657,7 @@ fhandler_disk_file::opendir (int fd)
             OS/FS combinations (say, Win2K/CDFS or so).  Instead of
             testing in readdir for yet another error code, let's use
             FileIdBothDirectoryInformation only on filesystems supporting
-            persistent ACLs, FileDirectoryInformation otherwise.
+            persistent ACLs, FileBothDirectoryInformation otherwise.
 
             NFS clients hide dangling symlinks from directory queries,
             unless you use the FileNamesInformation info class.
@@ -1716,7 +1729,7 @@ readdir_get_ino (const char *path, bool dot_dot)
       strcpy (c, "..");
       path = fname;
     }
-  path_conv pc (path, PC_SYM_NOFOLLOW | PC_POSIX | PC_NOWARN);
+  path_conv pc (path, PC_SYM_NOFOLLOW | PC_POSIX | PC_NOWARN | PC_KEEP_HANDLE);
   if (pc.isspecial ())
     {
       if (!stat_worker (pc, &st))
@@ -1724,17 +1737,18 @@ readdir_get_ino (const char *path, bool dot_dot)
     }
   else if (!pc.hasgood_inode ())
     ino = hash_path_name (0, pc.get_nt_native_path ());
-  else if (NT_SUCCESS (NtOpenFile (&hdl, READ_CONTROL,
-                                  pc.get_object_attr (attr, sec_none_nih),
-                                  &io, FILE_SHARE_VALID_FLAGS,
-                                  FILE_OPEN_FOR_BACKUP_INTENT
-                                  | (pc.is_rep_symlink ()
-                                     ? FILE_OPEN_REPARSE_POINT : 0))))
-    {
-      ino = get_ino_by_handle (pc, hdl);
+  else if ((hdl = pc.handle ()) != NULL
+          || NT_SUCCESS (NtOpenFile (&hdl, READ_CONTROL,
+                                     pc.get_object_attr (attr, sec_none_nih),
+                                     &io, FILE_SHARE_VALID_FLAGS,
+                                     FILE_OPEN_FOR_BACKUP_INTENT
+                                     | (pc.is_rep_symlink ()
+                                     ? FILE_OPEN_REPARSE_POINT : 0)))
+         )
+    {
+      ino = pc.get_ino_by_handle (hdl);
       if (!ino)
        ino = hash_path_name (0, pc.get_nt_native_path ());
-      NtClose (hdl);
     }
   return ino;
 }
@@ -1749,23 +1763,25 @@ fhandler_disk_file::readdir_helper (DIR *dir, dirent *de, DWORD w32_err,
       if ((de->d_ino = d_mounts (dir)->check_missing_mount (fname)))
        added = true;
       if (!added)
-       return geterrno_from_win_error (w32_err);
+       {
+         fname->Length = 0;
+         return geterrno_from_win_error (w32_err);
+       }
 
       attr = 0;
       dir->__flags &= ~dirent_set_d_ino;
     }
 
-  /* Set d_type if type can be determined from file attributes.
-     FILE_ATTRIBUTE_SYSTEM ommitted to leave DT_UNKNOWN for old symlinks.
-     For new symlinks, d_type will be reset to DT_UNKNOWN below.  */
+  /* Set d_type if type can be determined from file attributes.  For .lnk
+     symlinks, d_type will be reset below.  Reparse points can be NTFS
+     symlinks, even if they have the FILE_ATTRIBUTE_DIRECTORY flag set. */
   if (attr &&
-      !(attr & (  ~FILE_ATTRIBUTE_VALID_FLAGS
-               | FILE_ATTRIBUTE_SYSTEM
-               | FILE_ATTRIBUTE_REPARSE_POINT)))
+      !(attr & (~FILE_ATTRIBUTE_VALID_FLAGS | FILE_ATTRIBUTE_REPARSE_POINT)))
     {
       if (attr & FILE_ATTRIBUTE_DIRECTORY)
        de->d_type = DT_DIR;
-      else
+      /* FILE_ATTRIBUTE_SYSTEM might denote system-bit type symlinks. */
+      else if (!(attr & FILE_ATTRIBUTE_SYSTEM))
        de->d_type = DT_REG;
     }
 
@@ -1780,19 +1796,29 @@ fhandler_disk_file::readdir_helper (DIR *dir, dirent *de, DWORD w32_err,
 
       InitializeObjectAttributes (&attr, fname, pc.objcaseinsensitive (),
                                  get_handle (), NULL);
-      if (is_volume_mountpoint (&attr)
-         && (NT_SUCCESS (NtOpenFile (&reph, READ_CONTROL, &attr, &io,
-                                     FILE_SHARE_VALID_FLAGS,
-                                     FILE_OPEN_FOR_BACKUP_INTENT))))
+      de->d_type = readdir_check_reparse_point (&attr);
+      if (de->d_type == DT_DIR)
        {
-         de->d_ino = get_ino_by_handle (pc, reph);
-         NtClose (reph);
+         /* Volume mountpoints are treated as directories.  We have to fix
+            the inode number, otherwise we have the inode number of the
+            mount point, rather than the inode number of the toplevel
+            directory of the mounted drive. */
+         if (NT_SUCCESS (NtOpenFile (&reph, READ_CONTROL, &attr, &io,
+                                     FILE_SHARE_VALID_FLAGS,
+                                     FILE_OPEN_FOR_BACKUP_INTENT)))
+           {
+             de->d_ino = pc.get_ino_by_handle (reph);
+             NtClose (reph);
+           }
        }
     }
 
-  /* Check for Windows shortcut. If it's a Cygwin or U/WIN
-     symlink, drop the .lnk suffix. */
-  if ((attr & FILE_ATTRIBUTE_READONLY) && fname->Length > 4 * sizeof (WCHAR))
+  /* Check for Windows shortcut. If it's a Cygwin or U/WIN symlink, drop the
+     .lnk suffix and set d_type accordingly. */
+  if ((attr & (FILE_ATTRIBUTE_DIRECTORY
+              | FILE_ATTRIBUTE_REPARSE_POINT
+              | FILE_ATTRIBUTE_READONLY)) == FILE_ATTRIBUTE_READONLY
+      && fname->Length > 4 * sizeof (WCHAR))
     {
       UNICODE_STRING uname;
 
@@ -1817,10 +1843,20 @@ fhandler_disk_file::readdir_helper (DIR *dir, dirent *de, DWORD w32_err,
              fbuf.Length -= 2 * sizeof (WCHAR);
            }
          path_conv fpath (&fbuf, PC_SYM_NOFOLLOW);
-         if (fpath.issymlink () || fpath.is_fs_special ())
+         if (fpath.issymlink ())
            {
              fname->Length -= 4 * sizeof (WCHAR);
-             de->d_type = DT_UNKNOWN;
+             de->d_type = DT_LNK;
+           }
+         else if (fpath.isfifo ())
+           {
+             fname->Length -= 4 * sizeof (WCHAR);
+             de->d_type = DT_FIFO;
+           }
+         else if (fpath.is_fs_special ())
+           {
+             fname->Length -= 4 * sizeof (WCHAR);
+             de->d_type = S_ISCHR (fpath.dev.mode) ? DT_CHR : DT_BLK;
            }
        }
     }
@@ -1828,10 +1864,16 @@ fhandler_disk_file::readdir_helper (DIR *dir, dirent *de, DWORD w32_err,
   sys_wcstombs (de->d_name, NAME_MAX + 1, fname->Buffer,
                fname->Length / sizeof (WCHAR));
 
-  if (dir->__d_position == 0 && !strcmp (de->d_name, "."))
-    dir->__flags |= dirent_saw_dot;
-  else if (dir->__d_position == 1 && !strcmp (de->d_name, ".."))
-    dir->__flags |= dirent_saw_dot_dot;
+  /* Don't try to optimize relative to dir->__d_position.  On several
+     filesystems it's no safe bet that "." and ".." entries always
+     come first. */
+  if (de->d_name[0] == '.')
+    {
+      if (de->d_name[1] == '\0')
+       dir->__flags |= dirent_saw_dot;
+      else if (de->d_name[1] == '.' && de->d_name[2] == '\0')
+       dir->__flags |= dirent_saw_dot_dot;
+    }
   return 0;
 }
 
@@ -1859,32 +1901,37 @@ fhandler_disk_file::readdir (DIR *dir, dirent *de)
                                         FALSE, NULL, dir->__d_position == 0);
          /* FileIdBothDirectoryInformation isn't supported for remote drives
             on NT4 and 2K systems, and it's also not supported on 2K at all,
-            when accessing network drives on any remote OS.  We just fall
-            back to using a standard directory query in this case and note
-            this case using the dirent_get_d_ino flag. */
-         if (status == STATUS_INVALID_LEVEL
-             || status == STATUS_INVALID_PARAMETER
-             || status == STATUS_INVALID_INFO_CLASS)
+            when accessing network drives on any remote OS.  There are also
+            hacked versions of Samba 3.0.x out there (Debian-based it seems),
+            which return STATUS_NOT_SUPPORTED rather than handling this info
+            class.  We just fall back to using a standard directory query in
+            this case and note this case using the dirent_get_d_ino flag. */
+         if (!NT_SUCCESS (status) && status != STATUS_NO_MORE_FILES
+             && (status == STATUS_INVALID_LEVEL
+                 || status == STATUS_NOT_SUPPORTED
+                 || status == STATUS_INVALID_PARAMETER
+                 || status == STATUS_INVALID_NETWORK_RESPONSE
+                 || status == STATUS_INVALID_INFO_CLASS))
            dir->__flags &= ~dirent_get_d_ino;
          /* Something weird happens on Samba up to version 3.0.21c, which is
             fixed in 3.0.22.  FileIdBothDirectoryInformation seems to work
             nicely, but only up to the 128th entry in the directory.  After
             reaching this entry, the next call to NtQueryDirectoryFile
             (FileIdBothDirectoryInformation) returns STATUS_INVALID_LEVEL.
-            Why should we care, we can just switch to FileDirectoryInformation,
-            isn't it?  Nope!  The next call to
-              NtQueryDirectoryFile(FileDirectoryInformation)
-            actually returns STATUS_NO_MORE_FILES, regardless how many files
-            are left unread in the directory.  This does not happen when using
-            FileDirectoryInformation right from the start, but since
+            Why should we care, we can just switch to
+            FileBothDirectoryInformation, isn't it?  Nope!  The next call to
+            NtQueryDirectoryFile(FileBothDirectoryInformation) actually
+            returns STATUS_NO_MORE_FILES, regardless how many files are left
+            unread in the directory.  This does not happen when using
+            FileBothDirectoryInformation right from the start, but since
             we can't decide whether the server we're talking with has this
             bug or not, we end up serving Samba shares always in the slow
-            mode using FileDirectoryInformation.  So, what we do here is
+            mode using FileBothDirectoryInformation.  So, what we do here is
             to implement the solution suggested by Andrew Tridgell,  we just
             reread all entries up to dir->d_position using
-            FileDirectoryInformation.
+            FileBothDirectoryInformation.
             However, We do *not* mark this server as broken and fall back to
-            using FileDirectoryInformation further on.  This would slow
+            using FileBothDirectoryInformation further on.  This would slow
             down every access to such a server, even for directories under
             128 entries.  Also, bigger dirs only suffer from one additional
             call per full directory scan, which shouldn't be too big a hit.
@@ -1899,7 +1946,7 @@ fhandler_disk_file::readdir (DIR *dir, dirent *de)
                      status = NtQueryDirectoryFile (get_handle (), NULL, NULL,
                                           NULL, &io, d_cache (dir),
                                           DIR_BUF_SIZE,
-                                          FileDirectoryInformation,
+                                          FileBothDirectoryInformation,
                                           FALSE, NULL, cnt == 0);
                      if (!NT_SUCCESS (status))
                        goto go_ahead;
@@ -1919,7 +1966,7 @@ fhandler_disk_file::readdir (DIR *dir, dirent *de)
                                       d_cache (dir), DIR_BUF_SIZE,
                                       (dir->__flags & dirent_nfs_d_ino)
                                       ? FileNamesInformation
-                                      : FileDirectoryInformation,
+                                      : FileBothDirectoryInformation,
                                       FALSE, NULL, dir->__d_position == 0);
     }
 
@@ -1952,54 +1999,91 @@ go_ahead:
        }
       else
        {
-         FileName = ((PFILE_DIRECTORY_INFORMATION) buf)->FileName;
-         FileNameLength = ((PFILE_DIRECTORY_INFORMATION) buf)->FileNameLength;
-         FileAttributes = ((PFILE_DIRECTORY_INFORMATION) buf)->FileAttributes;
+         FileName = ((PFILE_BOTH_DIRECTORY_INFORMATION) buf)->FileName;
+         FileNameLength =
+               ((PFILE_BOTH_DIRECTORY_INFORMATION) buf)->FileNameLength;
+         FileAttributes =
+               ((PFILE_BOTH_DIRECTORY_INFORMATION) buf)->FileAttributes;
        }
       RtlInitCountedUnicodeString (&fname, FileName, FileNameLength);
       de->d_ino = d_mounts (dir)->check_mount (&fname, de->d_ino);
       if (de->d_ino == 0 && (dir->__flags & dirent_set_d_ino))
        {
-         OBJECT_ATTRIBUTES attr;
-
-         if (dir->__d_position == 0 && FileNameLength == 2
-             && FileName[0] == '.')
-           de->d_ino = get_ino_by_handle (pc, get_handle ());
-         else if (dir->__d_position == 1 && FileNameLength == 4
+         /* Don't try to optimize relative to dir->__d_position.  On several
+            filesystems it's no safe bet that "." and ".." entries always
+            come first. */
+         if (FileNameLength == sizeof (WCHAR) && FileName[0] == '.')
+           de->d_ino = pc.get_ino_by_handle (get_handle ());
+         else if (FileNameLength == 2 * sizeof (WCHAR)
                   && FileName[0] == L'.' && FileName[1] == L'.')
-           if (!(dir->__flags & dirent_isroot))
-             de->d_ino = readdir_get_ino (get_name (), true);
-           else
-             de->d_ino = get_ino_by_handle (pc, get_handle ());
+           {
+             if (!(dir->__flags & dirent_isroot))
+               de->d_ino = readdir_get_ino (get_name (), true);
+             else
+               de->d_ino = pc.get_ino_by_handle (get_handle ());
+           }
          else
            {
+             OBJECT_ATTRIBUTES attr;
              HANDLE hdl;
+             NTSTATUS f_status;
 
              InitializeObjectAttributes (&attr, &fname,
                                          pc.objcaseinsensitive (),
                                          get_handle (), NULL);
-             if (NT_SUCCESS (NtOpenFile (&hdl, READ_CONTROL, &attr, &io,
-                                         FILE_SHARE_VALID_FLAGS,
-                                         FILE_OPEN_FOR_BACKUP_INTENT
-                                         | FILE_OPEN_REPARSE_POINT)))
+             /* FILE_OPEN_REPARSE_POINT on NFS is a no-op, so the normal
+                NtOpenFile here returns the inode number of the symlink target,
+                rather than the inode number of the symlink itself.
+                
+                Worse, trying to open a symlink without setting the special
+                "ActOnSymlink" EA triggers a bug in Windows 7 which results
+                in a timeout of up to 20 seconds, followed by two exceptions
+                in the NT kernel.
+
+                Since both results are far from desirable, we open symlinks
+                on NFS so that we get the right inode and a happy W7.
+                And, since some filesystems choke on the EAs, we don't
+                use them unconditionally. */
+             f_status = (dir->__flags & dirent_nfs_d_ino)
+                        ? NtCreateFile (&hdl, READ_CONTROL, &attr, &io,
+                                        NULL, 0, FILE_SHARE_VALID_FLAGS,
+                                        FILE_OPEN, FILE_OPEN_FOR_BACKUP_INTENT,
+                                        &nfs_aol_ffei, sizeof nfs_aol_ffei)
+                        : NtOpenFile (&hdl, READ_CONTROL, &attr, &io,
+                                      FILE_SHARE_VALID_FLAGS,
+                                      FILE_OPEN_FOR_BACKUP_INTENT
+                                      | FILE_OPEN_REPARSE_POINT);
+             if (NT_SUCCESS (f_status))
                {
-                 de->d_ino = get_ino_by_handle (pc, hdl);
+                 /* We call NtQueryInformationFile here, rather than
+                    pc.get_ino_by_handle(), otherwise we can't short-circuit
+                    dirent_set_d_ino correctly. */
+                 FILE_INTERNAL_INFORMATION fai;
+                 f_status = NtQueryInformationFile (hdl, &io, &fai, sizeof fai,
+                                                    FileInternalInformation);
                  NtClose (hdl);
+                 if (NT_SUCCESS (f_status))
+                   {
+                     if (pc.isgood_inode (fai.FileId.QuadPart))
+                       de->d_ino = fai.FileId.QuadPart;
+                     else
+                       /* Untrusted file system.  Don't try to fetch inode
+                          number again. */
+                       dir->__flags &= ~dirent_set_d_ino;
+                   }
                }
            }
-         /* Untrusted file system.  Don't try to fetch inode number again. */
-         if (de->d_ino == 0)
-           dir->__flags &= ~dirent_set_d_ino;
        }
     }
 
   if (!(res = readdir_helper (dir, de, RtlNtStatusToDosError (status),
-                             buf ? FileAttributes : 0, &fname)))
+                             FileAttributes, &fname)))
     dir->__d_position++;
   else if (!(dir->__flags & dirent_saw_dot))
     {
       strcpy (de->d_name , ".");
-      de->d_ino = get_ino_by_handle (pc, get_handle ());
+      de->d_ino = pc.get_ino_by_handle (get_handle ());
+      de->d_type = DT_DIR;
       dir->__d_position++;
       dir->__flags |= dirent_saw_dot;
       res = 0;
@@ -2010,24 +2094,27 @@ go_ahead:
       if (!(dir->__flags & dirent_isroot))
        de->d_ino = readdir_get_ino (get_name (), true);
       else
-       de->d_ino = get_ino_by_handle (pc, get_handle ());
+       de->d_ino = pc.get_ino_by_handle (get_handle ());
+      de->d_type = DT_DIR;
       dir->__d_position++;
       dir->__flags |= dirent_saw_dot_dot;
       res = 0;
     }
 
-  syscall_printf ("%d = readdir (%p, %p) (%s)", res, dir, &de, res ? "***" : de->d_name);
+  syscall_printf ("%d = readdir (%p, %p) (L\"%lS\" > \"%ls\") (attr %p > type %d)",
+                 res, dir, &de, res ? NULL : &fname, res ? "***" : de->d_name,
+                 FileAttributes, de->d_type);
   return res;
 }
 
-_off64_t
+long
 fhandler_disk_file::telldir (DIR *dir)
 {
   return dir->__d_position;
 }
 
 void
-fhandler_disk_file::seekdir (DIR *dir, _off64_t loc)
+fhandler_disk_file::seekdir (DIR *dir, long loc)
 {
   rewinddir (dir);
   while (loc > dir->__d_position)
@@ -2050,8 +2137,7 @@ fhandler_disk_file::rewinddir (DIR *dir)
       IO_STATUS_BLOCK io;
       HANDLE new_dir;
 
-      InitializeObjectAttributes (&attr, &ro_u_empty, pc.objcaseinsensitive (),
-                                 get_handle (), NULL);
+      pc.init_reopen_attr (&attr, get_handle ());
       status = NtOpenFile (&new_dir, SYNCHRONIZE | FILE_LIST_DIRECTORY,
                           &attr, &io, FILE_SHARE_VALID_FLAGS,
                           FILE_SYNCHRONOUS_IO_NONALERT
@@ -2123,14 +2209,11 @@ fhandler_cygdrive::close ()
   return 0;
 }
 
-#define DRVSZ sizeof ("x:\\")
 void
 fhandler_cygdrive::set_drives ()
 {
-  const int len = 2 + 26 * DRVSZ;
-  char *p = const_cast<char *> (get_win32_name ());
-  pdrive = p;
-  ndrives = GetLogicalDriveStrings (len, p) / DRVSZ;
+  pdrive = pdrive_buf;
+  ndrives = GetLogicalDriveStrings (sizeof pdrive_buf, pdrive_buf) / DRVSZ;
 }
 
 int
@@ -2138,7 +2221,7 @@ fhandler_cygdrive::fstat (struct __stat64 *buf)
 {
   fhandler_base::fstat (buf);
   buf->st_ino = 2;
-  buf->st_mode = S_IFDIR | S_IXUSR | S_IXGRP | S_IXOTH;
+  buf->st_mode = S_IFDIR | STD_RBITS | STD_XBITS;
   if (!ndrives)
     set_drives ();
   char flptst[] = "X:";
@@ -2146,7 +2229,7 @@ fhandler_cygdrive::fstat (struct __stat64 *buf)
   for (const char *p = pdrive; p && *p; p = strchr (p, '\0') + 1)
     if (is_floppy ((flptst[0] = *p, flptst))
        || GetFileAttributes (p) == INVALID_FILE_ATTRIBUTES)
-      --n;
+      n--;
   buf->st_nlink = n + 2;
   return 0;
 }
@@ -2198,13 +2281,13 @@ fhandler_cygdrive::readdir (DIR *dir, dirent *de)
 void
 fhandler_cygdrive::rewinddir (DIR *dir)
 {
-  pdrive = get_win32_name ();
+  pdrive = pdrive_buf;
   dir->__d_position = 0;
 }
 
 int
 fhandler_cygdrive::closedir (DIR *dir)
 {
-  pdrive = get_win32_name ();
+  pdrive = pdrive_buf;
   return 0;
 }