OSDN Git Service

gas/opcodes: blackfin: move dsp mac func defines to common header
[pf3gnuchains/sourceware.git] / winsup / cygwin / fhandler_disk_file.cc
index c93fc68..cc2c237 100644 (file)
@@ -1,7 +1,7 @@
 /* fhandler_disk_file.cc
 
    Copyright 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004,
-   2005, 2006, 2007 Red Hat, Inc.
+   2005, 2006, 2007, 2008, 2009, 2010, 2011 Red Hat, Inc.
 
 This file is part of Cygwin.
 
@@ -10,16 +10,11 @@ Cygwin license.  Please consult the file "CYGWIN_LICENSE" for
 details. */
 
 #include "winsup.h"
-#include <unistd.h>
 #include <stdlib.h>
-#include <sys/cygwin.h>
 #include <sys/acl.h>
 #include <sys/statvfs.h>
-#include <signal.h>
 #include "cygerrno.h"
-#include "perprocess.h"
 #include "security.h"
-#include "cygwin/version.h"
 #include "path.h"
 #include "fhandler.h"
 #include "dtable.h"
@@ -27,8 +22,8 @@ details. */
 #include "shared_info.h"
 #include "pinfo.h"
 #include "ntdll.h"
-#include <assert.h>
-#include <ctype.h>
+#include "tls_pbuf.h"
+#include "pwdgrp.h"
 #include <winioctl.h>
 
 #define _COMPILING_NEWLIB
@@ -83,23 +78,20 @@ public:
     {
       if (parent_dir_len == 1) /* root dir */
        {
-         UNICODE_STRING proc;
-
-         RtlInitUnicodeString (&proc, L"proc");
-         if (RtlEqualUnicodeString (fname, &proc, TRUE))
+         if (RtlEqualUnicodeString (fname, &ro_u_proc, FALSE))
            {
              found[__DIR_PROC] = true;
              return 2;
            }
          if (fname->Length / sizeof (WCHAR) == mount_table->cygdrive_len - 2
-             && RtlEqualUnicodeString (fname, &cygdrive, TRUE))
+             && RtlEqualUnicodeString (fname, &cygdrive, FALSE))
            {
              found[__DIR_CYGDRIVE] = true;
              return 2;
            }
        }
       for (int i = 0; i < count; ++i)
-       if (RtlEqualUnicodeString (fname, &mounts[i], TRUE))
+       if (RtlEqualUnicodeString (fname, &mounts[i], FALSE))
          {
            found[i] = true;
            return eval ? eval_ino (i) : 1;
@@ -125,7 +117,7 @@ public:
            {
              found[__DIR_PROC] = true;
              if (retname)
-               RtlInitUnicodeString (retname, L"proc");
+               *retname = ro_u_proc;
              return 2;
            }
          if (!found[__DIR_CYGDRIVE])
@@ -144,12 +136,29 @@ public:
     void rewind () { memset (found, 0, sizeof found); }
 };
 
-static inline bool
-is_volume_mountpoint (POBJECT_ATTRIBUTES attr)
+inline bool
+path_conv::isgood_inode (__ino64_t ino) const
 {
-  bool ret = false;
+  /* We can't trust remote inode numbers of only 32 bit.  That means,
+     all remote inode numbers when running under NT4, as well as remote NT4
+     NTFS, as well as shares of Samba version < 3.0.
+     The known exception are SFU NFS shares, which return the valid 32 bit
+     inode number from the remote file system unchanged. */
+  return hasgood_inode () && (ino > UINT32_MAX || !isremote () || fs_is_nfs ());
+}
+
+/* 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)
+{
+  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,
@@ -160,15 +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;
 }
 
+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))
+      && 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)
 {
@@ -189,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, 0, &io, fdibuf,
-                                          65536, FileDirectoryInformation,
+  while (NT_SUCCESS (NtQueryDirectoryFile (fh, NULL, NULL, NULL, &io, fdibuf,
+                                          65536, FileBothDirectoryInformation,
                                           FALSE, NULL, first)))
     {
       if (first)
@@ -205,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))
        {
@@ -226,7 +264,7 @@ path_conv::ndisk_links (DWORD nNumberOfLinks)
                RtlInitCountedUnicodeString (&fname, pfdi->FileName,
                                             pfdi->FileNameLength);
                InitializeObjectAttributes (&attr, &fname,
-                                           OBJ_CASE_INSENSITIVE, fh, NULL);
+                                           objcaseinsensitive (), fh, NULL);
                if (is_volume_mountpoint (&attr))
                  ++count;
              }
@@ -246,68 +284,109 @@ path_conv::ndisk_links (DWORD nNumberOfLinks)
   delete dir;
   return count;
 }
+#endif
 
-inline bool
-path_conv::isgood_inode (__ino64_t ino) const
+/* 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.
+   The content is the NFS equivalent of struct stat. so there's not much
+   to do here except for copying. */
+int __stdcall
+fhandler_base::fstat_by_nfs_ea (struct __stat64 *buf)
 {
-  /* We can't trust remote inode numbers of only 32 bit.  That means,
-     all remote inode numbers when running under NT4, as well as remote NT4
-     NTFS, as well as shares of Samba version < 3.0.
-     The known exception are SFU NFS shares, which return the valid 32 bit
-     inode number from the remote file system unchanged. */
-  return hasgood_inode () && (ino > UINT32_MAX || !isremote () || fs_is_nfs ());
+  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;
+#else
+  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 + 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;
+  /* 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_STANDARD_INFORMATION fsi;
+  FILE_INTERNAL_INFORMATION fii;
+
+  HANDLE h = get_stat_handle ();
+  NTSTATUS status = 0;
   IO_STATUS_BLOCK io;
-  /* The entries potentially contain a name of MAX_PATH wide characters. */
-  const DWORD fvi_size = (NAME_MAX + 1) * sizeof (WCHAR)
-                        + sizeof (FILE_FS_VOLUME_INFORMATION);
-  const DWORD fai_size = (NAME_MAX + 1) * sizeof (WCHAR)
-                        + sizeof (FILE_ALL_INFORMATION);
-
-  PFILE_FS_VOLUME_INFORMATION pfvi = (PFILE_FS_VOLUME_INFORMATION)
-                                    alloca (fvi_size);
-  PFILE_ALL_INFORMATION pfai = (PFILE_ALL_INFORMATION) alloca (fai_size);
-
-  status = NtQueryVolumeInformationFile (get_handle (), &io, pfvi, fvi_size,
-                                        FileFsVolumeInformation);
-  if (!NT_SUCCESS (status))
+
+  /* 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 = NtQueryVolumeInformationFile(%S)", status,
-                   pc.get_nt_native_path ());
-      pfvi->VolumeSerialNumber = 0;
-    }
-  status = NtQueryInformationFile (get_handle (), &io, pfai, fai_size,
-                                  FileAllInformation);
-  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
     {
-      /* 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 ())
-       pfai->BasicInformation.FileAttributes &= ~FILE_ATTRIBUTE_DIRECTORY;
-      pc.file_attributes (pfai->BasicInformation.FileAttributes);
-      return fstat_helper (buf,
-                      pfai->BasicInformation.ChangeTime.QuadPart
-                      ? *(FILETIME *) &pfai->BasicInformation.ChangeTime
-                      : *(FILETIME *) &pfai->BasicInformation.LastWriteTime,
-                      *(FILETIME *) &pfai->BasicInformation.LastAccessTime,
-                      *(FILETIME *) &pfai->BasicInformation.LastWriteTime,
-                      *(FILETIME *) &pfai->BasicInformation.CreationTime,
-                      pfvi->VolumeSerialNumber,
-                      pfai->StandardInformation.EndOfFile.QuadPart,
-                      pfai->StandardInformation.AllocationSize.QuadPart,
-                      pfai->InternalInformation.FileId.QuadPart,
-                      pfai->StandardInformation.NumberOfLinks,
-                      pfai->BasicInformation.FileAttributes);
-    }
-  debug_printf ("%p = NtQueryInformationFile(%S)",
-               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;
+       }
+    }
+  return fstat_helper (buf, fsi.NumberOfLinks);
 }
 
 int __stdcall
@@ -319,96 +398,40 @@ fhandler_base::fstat_by_name (struct __stat64 *buf)
   UNICODE_STRING dirname;
   UNICODE_STRING basename;
   HANDLE dir;
-  const DWORD fdi_size = (NAME_MAX + 1) * sizeof (WCHAR)
-                        + sizeof (FILE_ID_BOTH_DIR_INFORMATION);
-  const DWORD fvi_size = (NAME_MAX + 1) * sizeof (WCHAR)
-                        + sizeof (FILE_FS_VOLUME_INFORMATION);
-  PFILE_ID_BOTH_DIR_INFORMATION pfdi = (PFILE_ID_BOTH_DIR_INFORMATION)
-                                      alloca (fdi_size);
-  PFILE_FS_VOLUME_INFORMATION pfvi = (PFILE_FS_VOLUME_INFORMATION)
-                                    alloca (fvi_size);
-  LARGE_INTEGER FileId;
-
-  if (!pc.exists ())
-    {
-      debug_printf ("already determined that pc does not exist");
-      set_errno (ENOENT);
-      return -1;
-    }
-  RtlSplitUnicodePath (pc.get_nt_native_path (), &dirname, &basename);
-  InitializeObjectAttributes (&attr, &dirname, OBJ_CASE_INSENSITIVE,
-                             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 ()
-      && NT_SUCCESS (status = NtQueryDirectoryFile (dir, NULL, NULL, 0, &io,
-                                                pfdi, fdi_size,
-                                                FileIdBothDirectoryInformation,
-                                                TRUE, &basename, TRUE)))
-    FileId = pfdi->FileId;
-  else if (NT_SUCCESS (status = NtQueryDirectoryFile (dir, NULL, NULL, 0, &io,
-                                                pfdi, fdi_size,
-                                                FileBothDirectoryInformation,
-                                                TRUE, &basename, TRUE)))
-    FileId.QuadPart = 0; /* get_namehash is called in fstat_helper. */
-  if (!NT_SUCCESS (status))
-    {
-      debug_printf ("%p = NtQueryDirectoryFile(%S)", status,
-                   pc.get_nt_native_path ());
-      NtClose (dir);
-      goto too_bad;
+  struct {
+    FILE_ID_BOTH_DIR_INFORMATION fdi;
+    WCHAR buf[NAME_MAX + 1];
+  } fdi_buf;
+
+  if (!ino && pc.hasgood_inode ()
+      && wincap.has_fileid_dirinfo () && !pc.has_buggy_fileid_dirinfo ())
+    {
+      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;
+       }
     }
-  status = NtQueryVolumeInformationFile (dir, &io, pfvi, fvi_size,
-                                        FileFsVolumeInformation);
-  if (!NT_SUCCESS (status))
-    {
-      debug_printf ("%p = NtQueryVolumeInformationFile(%S)",
-                   status, pc.get_nt_native_path ());
-      pfvi->VolumeSerialNumber = 0;
-    }
-  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 ())
-    pfdi->FileAttributes &= ~FILE_ATTRIBUTE_DIRECTORY;
-  pc.file_attributes (pfdi->FileAttributes);
-  return fstat_helper (buf,
-                      pfdi->ChangeTime.QuadPart ?
-                      *(FILETIME *) &pfdi->ChangeTime :
-                      *(FILETIME *) &pfdi->LastWriteTime,
-                      *(FILETIME *) &pfdi->LastAccessTime,
-                      *(FILETIME *) &pfdi->LastWriteTime,
-                      *(FILETIME *) &pfdi->CreationTime,
-                      pfvi->VolumeSerialNumber,
-                      pfdi->EndOfFile.QuadPart,
-                      pfdi->AllocationSize.QuadPart,
-                      pfdi->FileId.QuadPart,
-                      1,
-                      pfdi->FileAttributes);
-
-too_bad:
-  LARGE_INTEGER ft;
-  /* Arbitrary value: 2006-12-01 */
-  RtlSecondsSince1970ToTime (1164931200L, &ft);
-  return fstat_helper (buf,
-                      *(FILETIME *) &ft,
-                      *(FILETIME *) &ft,
-                      *(FILETIME *) &ft,
-                      *(FILETIME *) &ft,
-                      0,
-                      0ULL,
-                      -1LL,
-                      0ULL,
-                      1,
-                      pc.file_attributes ());
+  return fstat_helper (buf, 1);
 }
 
 int __stdcall
@@ -418,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 ();
@@ -447,74 +478,71 @@ 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
+  buf->st_nlink = nNumberOfLinks;
+#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_namehash ();
+    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 ())
@@ -522,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);
 
@@ -544,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;
        }
@@ -553,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 */
 
@@ -563,32 +590,55 @@ 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;
        }
       else
        {
          buf->st_mode |= S_IFREG;
+         /* Check suffix for executable file. */
+         if (pc.exec_state () != is_executable)
+           {
+             PUNICODE_STRING path = pc.get_nt_native_path ();
+
+             if (RtlEqualUnicodePathSuffix (path, &ro_u_exe, TRUE)
+                 || RtlEqualUnicodePathSuffix (path, &ro_u_lnk, TRUE)
+                 || RtlEqualUnicodePathSuffix (path, &ro_u_com, TRUE))
+               pc.set_exec ();
+           }
+         /* No known suffix, check file header.  This catches binaries and
+            shebang scripts. */
          if (pc.exec_state () == dont_know_if_executable)
            {
-             UNICODE_STRING same;
              OBJECT_ATTRIBUTES attr;
-             HANDLE h;
+             NTSTATUS status = 0;
              IO_STATUS_BLOCK io;
 
-             RtlInitUnicodeString (&same, L"");
-             InitializeObjectAttributes (&attr, &same, 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;
                    }
@@ -601,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;
 }
 
@@ -622,25 +679,21 @@ fhandler_disk_file::fstatvfs (struct statvfs *sfs)
   int ret = -1, opened = 0;
   NTSTATUS status;
   IO_STATUS_BLOCK io;
-  const size_t fvi_size = sizeof (FILE_FS_VOLUME_INFORMATION)
-                         + (NAME_MAX + 1) * sizeof (WCHAR);
-  PFILE_FS_VOLUME_INFORMATION pfvi = (PFILE_FS_VOLUME_INFORMATION)
-                                    alloca (fvi_size);
-  const size_t fai_size = sizeof (FILE_FS_ATTRIBUTE_INFORMATION)
-                         + (NAME_MAX + 1) * sizeof (WCHAR);
-  PFILE_FS_ATTRIBUTE_INFORMATION pfai = (PFILE_FS_ATTRIBUTE_INFORMATION)
-                                       alloca (fai_size);
   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. */
@@ -648,34 +701,19 @@ 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;
        }
     }
 
-  /* Get basic volume information. */
-  status = NtQueryVolumeInformationFile (fh, &io, pfvi, fvi_size,
-                                        FileFsVolumeInformation);
-  if (!NT_SUCCESS (status))
-    {
-      __seterrno_from_nt_status (status);
-      goto out;
-    }
-  status = NtQueryVolumeInformationFile (fh, &io, pfai, fai_size,
-                                        FileFsAttributeInformation);
-  if (!NT_SUCCESS (status))
-    {
-      __seterrno_from_nt_status (status);
-      goto out;
-    }
   sfs->f_files = ULONG_MAX;
   sfs->f_ffree = ULONG_MAX;
   sfs->f_favail = ULONG_MAX;
-  sfs->f_fsid = pfvi->VolumeSerialNumber;
-  sfs->f_flag = pfai->FileSystemAttributes;
-  sfs->f_namemax = pfai->MaximumComponentNameLength;
+  sfs->f_fsid = pc.fs_serial_number ();
+  sfs->f_flag = pc.fs_flags ();
+  sfs->f_namemax = pc.fs_name_len ();
   /* Get allocation related information.  Try to get "full" information
      first, which is only available since W2K.  If that fails, try to
      retrieve normal allocation information. */
@@ -733,17 +771,19 @@ fhandler_disk_file::fchmod (mode_t mode)
   extern int chmod_device (path_conv& pc, mode_t mode);
   int res = -1;
   int oret = 0;
+  NTSTATUS status;
+  IO_STATUS_BLOCK io;
 
   if (pc.is_fs_special ())
     return chmod_device (pc, 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. */
-         if (allow_ntsec && pc.has_acls ())
+         if (pc.has_acls ())
            return -1;
          /* Otherwise FILE_WRITE_ATTRIBUTES is sufficient. */
          query_open (query_write_attributes);
@@ -752,39 +792,83 @@ fhandler_disk_file::fchmod (mode_t mode)
        }
     }
 
-  if (allow_ntsec && pc.has_acls ())
+  if (pc.fs_is_nfs ())
+    {
+      /* chmod on NFS shares works by writing an EA of type NfsV3Attributes.
+        Only type and mode have to be set.  Apparently type isn't checked
+        for consistency, so it's sufficent to set it to NF3REG all the time. */
+      struct {
+       FILE_FULL_EA_INFORMATION ffei;
+       char buf[sizeof (NFS_V3_ATTR) + sizeof (fattr3)];
+      } ffei_buf;
+      ffei_buf.ffei.NextEntryOffset = 0;
+      ffei_buf.ffei.Flags = 0;
+      ffei_buf.ffei.EaNameLength = sizeof (NFS_V3_ATTR) - 1;
+      ffei_buf.ffei.EaValueLength = sizeof (fattr3);
+      strcpy (ffei_buf.ffei.EaName, NFS_V3_ATTR);
+      fattr3 *nfs_attr = (fattr3 *) (ffei_buf.ffei.EaName
+                                    + ffei_buf.ffei.EaNameLength + 1);
+      memset (nfs_attr, 0, sizeof (fattr3));
+      nfs_attr->type = NF3REG;
+      nfs_attr->mode = mode;
+      status = NtSetEaFile (get_handle (), &io,
+                           &ffei_buf.ffei, sizeof ffei_buf);
+      if (!NT_SUCCESS (status))
+       __seterrno_from_nt_status (status);
+      else
+       res = 0;
+      goto out;
+    }
+
+  if (pc.has_acls ())
     {
       if (pc.isdir ())
        mode |= S_IFDIR;
       if (!set_file_attribute (get_handle (), pc,
-                              ILLEGAL_UID, ILLEGAL_GID, mode)
-         && allow_ntsec)
+                              ILLEGAL_UID, ILLEGAL_GID, mode))
        res = 0;
     }
 
-  /* if the mode we want has any write bits set, we can't be read only. */
+  /* If the mode has any write bits set, the DOS R/O flag is in the way. */
   if (mode & (S_IWUSR | S_IWGRP | S_IWOTH))
     pc &= (DWORD) ~FILE_ATTRIBUTE_READONLY;
-  else
+  else if (!pc.has_acls ())    /* Never set DOS R/O if security is used. */
     pc |= (DWORD) FILE_ATTRIBUTE_READONLY;
-  if (mode & S_IFSOCK)
+  if (S_ISSOCK (mode))
     pc |= (DWORD) FILE_ATTRIBUTE_SYSTEM;
 
-  IO_STATUS_BLOCK io;
-  FILE_BASIC_INFORMATION fbi;
-  fbi.CreationTime.QuadPart = fbi.LastAccessTime.QuadPart
-  = fbi.LastWriteTime.QuadPart = fbi.ChangeTime.QuadPart = 0LL;
-  fbi.FileAttributes = pc.file_attributes () ?: FILE_ATTRIBUTE_NORMAL;
-  NTSTATUS status = NtSetInformationFile (get_handle (), &io, &fbi, sizeof fbi,
-                                         FileBasicInformation);
-  if (!NT_SUCCESS (status))
-    __seterrno_from_nt_status (status);
-  else if (!allow_ntsec || !pc.has_acls ())
-    /* Correct NTFS security attributes have higher priority */
-    res = 0;
+  status = NtSetAttributesFile (get_handle (), pc.file_attributes ());
+  /* MVFS needs a good amount of kicking to be convinced that it has to write
+     back metadata changes and to invalidate the cached metadata information
+     stored for the given handle.  This method to open a second handle to
+     the file and write the same metadata information twice has been found
+     experimentally: http://cygwin.com/ml/cygwin/2009-07/msg00533.html */
+  if (pc.fs_is_mvfs () && NT_SUCCESS (status) && !oret)
+    {
+      OBJECT_ATTRIBUTES attr;
+      HANDLE fh;
 
+      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)))
+       {
+         NtSetAttributesFile (fh, pc.file_attributes ());
+         NtClose (fh);
+       }
+    }
+  /* Correct NTFS security attributes have higher priority */
+  if (!pc.has_acls ())
+    {
+      if (!NT_SUCCESS (status))
+       __seterrno_from_nt_status (status);
+      else
+       res = 0;
+    }
+
+out:
   if (oret)
-    close ();
+    close_fs ();
 
   return res;
 }
@@ -794,10 +878,11 @@ fhandler_disk_file::fchown (__uid32_t uid, __gid32_t gid)
 {
   int oret = 0;
 
-  if (!pc.has_acls () || !allow_ntsec)
+  if (!pc.has_acls ())
     {
       /* fake - if not supported, pretend we're like win95
         where it just works */
+      /* FIXME: Could be supported on NFS when user->uid mapping is in place. */
       return 0;
     }
 
@@ -811,7 +896,8 @@ fhandler_disk_file::fchown (__uid32_t uid, __gid32_t gid)
   mode_t attrib = 0;
   if (pc.isdir ())
     attrib |= S_IFDIR;
-  int res = get_file_attribute (get_handle (), pc, &attrib, NULL, NULL);
+  __uid32_t old_uid;
+  int res = get_file_attribute (get_handle (), pc, &attrib, &old_uid, NULL);
   if (!res)
     {
       /* Typical Windows default ACLs can contain permissions for one
@@ -824,9 +910,32 @@ fhandler_disk_file::fchown (__uid32_t uid, __gid32_t gid)
       if (pc.issymlink ())
        attrib = S_IFLNK | STD_RBITS | STD_WBITS;
       res = set_file_attribute (get_handle (), pc, uid, gid, attrib);
+      /* If you're running a Samba server which has no winbidd running, the
+         uid<->SID mapping is disfunctional.  Even trying to chown to your
+        own account fails since the account used on the server is the UNIX
+        account which gets used for the standard user mapping.  This is a
+        default mechanism which doesn't know your real Windows SID.
+        There are two possible error codes in different Samba releases for
+        this situation, one of them is unfortunately the not very significant
+        STATUS_ACCESS_DENIED.  Instead of relying on the error codes, we're
+        using the below very simple heuristic.  If set_file_attribute failed,
+        and the original user account was either already unknown, or one of
+        the standard UNIX accounts, we're faking success. */
+      if (res == -1 && pc.fs_is_samba ())
+       {
+         cygsid sid;
+
+         if (old_uid == ILLEGAL_UID
+             || (sid.getfrompw (internal_getpwuid (old_uid))
+                 && EqualPrefixSid (sid, well_known_samba_unix_user_fake_sid)))
+           {
+             debug_printf ("Faking chown worked on standalone Samba");
+             res = 0;
+           }
+       }
     }
   if (oret)
-    close ();
+    close_fs ();
 
   return res;
 }
@@ -837,7 +946,7 @@ fhandler_disk_file::facl (int cmd, int nentries, __aclent32_t *aclbufp)
   int res = -1;
   int oret = 0;
 
-  if (!pc.has_acls () || !allow_ntsec)
+  if (!pc.has_acls ())
     {
 cant_access_acl:
       switch (cmd)
@@ -858,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;
@@ -892,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)))
@@ -929,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);
@@ -941,7 +1051,7 @@ cant_access_acl:
     }
 
   if (oret)
-    close ();
+    close_fs ();
 
   return res;
 }
@@ -1047,7 +1157,7 @@ fhandler_disk_file::ftruncate (_off64_t length, bool allow_truncate)
       /* Create sparse files only when called through ftruncate, not when
         called through posix_fallocate. */
       if (allow_truncate
-         && get_fs_flags (FILE_SUPPORTS_SPARSE_FILES)
+         && (pc.fs_flags () & FILE_SUPPORTS_SPARSE_FILES)
          && length >= fsi.EndOfFile.QuadPart + (128 * 1024))
        {
          status = NtFsControlFile (get_handle (), NULL, NULL, NULL, &io,
@@ -1069,13 +1179,11 @@ fhandler_disk_file::ftruncate (_off64_t length, bool allow_truncate)
 int
 fhandler_disk_file::link (const char *newpath)
 {
-  extern bool allow_winsymlinks;
-
-  path_conv newpc (newpath, PC_SYM_NOFOLLOW | PC_POSIX,
-                  transparent_exe ? stat_suffixes : NULL);
+  size_t nlen = strlen (newpath);
+  path_conv newpc (newpath, PC_SYM_NOFOLLOW | PC_POSIX | PC_NULLEMPTY, stat_suffixes);
   if (newpc.error)
     {
-      set_errno (newpc.case_clash ? ECASECLASH : newpc.error);
+      set_errno (newpc.error);
       return -1;
     }
 
@@ -1086,10 +1194,21 @@ fhandler_disk_file::link (const char *newpath)
       return -1;
     }
 
-  char new_buf[strlen (newpath) + 5];
-  if (!newpc.error && !newpc.case_clash)
+  if (isdirsep (newpath[nlen - 1]) || has_dot_last_component (newpath, false))
     {
-      if (allow_winsymlinks && pc.is_lnk_special ())
+      set_errno (ENOENT);
+      return -1;
+    }
+
+  char new_buf[nlen + 5];
+  if (!newpc.error)
+    {
+      /* 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");
@@ -1098,8 +1217,10 @@ fhandler_disk_file::link (const char *newpath)
        }
       else if (!pc.isdir ()
               && pc.is_binary ()
+              && RtlEqualUnicodePathSuffix (pc.get_nt_native_path (),
+                                            &ro_u_exe, TRUE)
               && !RtlEqualUnicodePathSuffix (newpc.get_nt_native_path (),
-                                             L".exe", TRUE))
+                                             &ro_u_exe, TRUE))
        {
          /* Executable hack. */
          stpcpy (stpcpy (new_buf, newpath), ".exe");
@@ -1108,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 ();
@@ -1127,25 +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 + 1];
-         WCHAR newpcw[newpc.get_nt_native_path ()->Length + 1];
-         if (!CopyFileW (pc.get_wide_win32_path (pcw),
-                         newpc.get_wide_win32_path (newpcw), TRUE))
-           {
-             __seterrno ();
-             return -1;
-           }
-         if (!allow_winsymlinks && pc.is_lnk_special ())
-           SetFileAttributesW (newpcw, pc.file_attributes ()
-                                       | FILE_ATTRIBUTE_SYSTEM
-                                       | FILE_ATTRIBUTE_READONLY);
+         /* FS doesn't support hard links.  Linux returns EPERM. */
+         set_errno (EPERM);
+         return -1;
        }
       else
        {
@@ -1157,16 +1266,16 @@ fhandler_disk_file::link (const char *newpath)
 }
 
 int
-fhandler_disk_file::utimes (const struct timeval *tvp)
+fhandler_disk_file::utimens (const struct timespec *tvp)
 {
-  return utimes_fs (tvp);
+  return utimens_fs (tvp);
 }
 
 int
-fhandler_base::utimes_fs (const struct timeval *tvp)
+fhandler_base::utimens_fs (const struct timespec *tvp)
 {
-  LARGE_INTEGER lastaccess, lastwrite;
-  struct timeval tmp[2];
+  struct timespec timeofday;
+  struct timespec tmp[2];
   bool closeit = false;
 
   if (!get_handle ())
@@ -1188,25 +1297,52 @@ fhandler_base::utimes_fs (const struct timeval *tvp)
       closeit = true;
     }
 
-  gettimeofday (&tmp[0], 0);
+  clock_gettime (CLOCK_REALTIME, &timeofday);
   if (!tvp)
+    tmp[1] = tmp[0] = timeofday;
+  else
     {
-      tmp[1] = tmp[0];
-      tvp = tmp;
+      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;
+       }
+      tmp[0] = (tvp[0].tv_nsec == UTIME_NOW) ? timeofday : tvp[0];
+      tmp[1] = (tvp[1].tv_nsec == UTIME_NOW) ? timeofday : tvp[1];
     }
-  timeval_to_filetime (&tvp[0], (FILETIME *) &lastaccess);
-  timeval_to_filetime (&tvp[1], (FILETIME *) &lastwrite);
-  debug_printf ("incoming lastaccess %08x %08x", tvp[0].tv_sec, tvp[0].tv_usec);
+  debug_printf ("incoming lastaccess %08x %08x", tmp[0].tv_sec, tmp[0].tv_nsec);
 
   IO_STATUS_BLOCK io;
   FILE_BASIC_INFORMATION fbi;
+
   fbi.CreationTime.QuadPart = 0LL;
-  fbi.LastAccessTime = lastaccess;
-  fbi.LastWriteTime = lastwrite;
+  /* UTIME_OMIT is handled in timespec_to_filetime by setting FILETIME to 0. */
+  timespec_to_filetime (&tmp[0], (LPFILETIME) &fbi.LastAccessTime);
+  timespec_to_filetime (&tmp[1], (LPFILETIME) &fbi.LastWriteTime);
   fbi.ChangeTime.QuadPart = 0LL;
   fbi.FileAttributes = 0;
   NTSTATUS status = NtSetInformationFile (get_handle (), &io, &fbi, sizeof fbi,
                                          FileBasicInformation);
+  /* For this special case for MVFS see the comment in
+     fhandler_disk_file::fchmod. */
+  if (pc.fs_is_mvfs () && NT_SUCCESS (status) && !closeit)
+    {
+      OBJECT_ATTRIBUTES attr;
+      HANDLE fh;
+
+      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)))
+       {
+         NtSetInformationFile (fh, &io, &fbi, sizeof fbi,
+                               FileBasicInformation);
+         NtClose (fh);
+       }
+    }
   if (closeit)
     close_fs ();
   /* Opening a directory on a 9x share from a NT machine works(!), but
@@ -1239,13 +1375,6 @@ fhandler_disk_file::open (int flags, mode_t mode)
 int
 fhandler_base::open_fs (int flags, mode_t mode)
 {
-  if (pc.case_clash && flags & O_CREAT)
-    {
-      debug_printf ("case clash detected");
-      set_errno (ECASECLASH);
-      return 0;
-    }
-
   /* Unfortunately NT allows to open directories for writing, but that's
      disallowed according to SUSv3. */
   if (pc.isdir () && (flags & O_ACCMODE) != O_RDONLY)
@@ -1272,7 +1401,10 @@ fhandler_base::open_fs (int flags, mode_t mode)
       return 0;
     }
 
-  set_fs_flags (pc.fs_flags ());
+    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);
 
 out:
   syscall_printf ("%d = fhandler_disk_file::open (%S, %p)", res,
@@ -1317,171 +1449,49 @@ fhandler_disk_file::pwrite (void *buf, size_t count, _off64_t offset)
   return res;
 }
 
-/* FIXME: The correct way to do this to get POSIX locking semantics is to
-   keep a linked list of posix lock requests and map them into Win32 locks.
-   he problem is that Win32 does not deal correctly with overlapping lock
-   requests. */
-
-int
-fhandler_disk_file::lock (int cmd, struct __flock64 *fl)
-{
-  _off64_t win32_start;
-  _off64_t win32_len;
-  _off64_t startpos;
-
-  /*
-   * We don't do getlck calls yet.
-   */
-
-  if (cmd == F_GETLK)
-    {
-      set_errno (ENOSYS);
-      return -1;
-    }
-
-  /*
-   * Calculate where in the file to start from,
-   * then adjust this by fl->l_start.
-   */
-
-  switch (fl->l_whence)
-    {
-      case SEEK_SET:
-       startpos = 0;
-       break;
-      case SEEK_CUR:
-       if ((startpos = lseek (0, SEEK_CUR)) == ILLEGAL_SEEK)
-         return -1;
-       break;
-      case SEEK_END:
-       {
-         BY_HANDLE_FILE_INFORMATION finfo;
-         if (GetFileInformationByHandle (get_handle (), &finfo) == 0)
-           {
-             __seterrno ();
-             return -1;
-           }
-         startpos = ((_off64_t)finfo.nFileSizeHigh << 32)
-                    + finfo.nFileSizeLow;
-         break;
-       }
-      default:
-       set_errno (EINVAL);
-       return -1;
-    }
-
-  /*
-   * Now the fun starts. Adjust the start and length
-   *  fields until they make sense.
-   */
-
-  win32_start = startpos + fl->l_start;
-  if (fl->l_len < 0)
-    {
-      win32_start -= fl->l_len;
-      win32_len = -fl->l_len;
-    }
-  else
-    win32_len = fl->l_len;
-
-  if (win32_start < 0)
-    {
-      /* watch the signs! */
-      win32_len -= -win32_start;
-      if (win32_len <= 0)
-       {
-         /* Failure ! */
-         set_errno (EINVAL);
-         return -1;
-       }
-      win32_start = 0;
-    }
-
-  DWORD off_high, off_low, len_high, len_low;
-
-  off_low = (DWORD)(win32_start & UINT32_MAX);
-  off_high = (DWORD)(win32_start >> 32);
-  if (win32_len == 0)
-    {
-      /* Special case if len == 0 for POSIX means lock to the end of
-        the entire file (and all future extensions).  */
-      len_low = len_high = UINT32_MAX;
-    }
-  else
-    {
-      len_low = (DWORD)(win32_len & UINT32_MAX);
-      len_high = (DWORD)(win32_len >> 32);
-    }
-
-  BOOL res;
-
-  DWORD lock_flags = (cmd == F_SETLK) ? LOCKFILE_FAIL_IMMEDIATELY : 0;
-  lock_flags |= (fl->l_type == F_WRLCK) ? LOCKFILE_EXCLUSIVE_LOCK : 0;
-
-  OVERLAPPED ov;
-
-  ov.Internal = 0;
-  ov.InternalHigh = 0;
-  ov.Offset = off_low;
-  ov.OffsetHigh = off_high;
-  ov.hEvent = (HANDLE) 0;
-
-  if (fl->l_type == F_UNLCK)
-    {
-      res = UnlockFileEx (get_handle (), 0, len_low, len_high, &ov);
-      if (res == 0 && GetLastError () == ERROR_NOT_LOCKED)
-       res = 1;
-    }
-  else
-    {
-      res = LockFileEx (get_handle (), lock_flags, 0,
-                       len_low, len_high, &ov);
-      /* Deal with the fail immediately case. */
-      /*
-       * FIXME !! I think this is the right error to check for
-       * but I must admit I haven't checked....
-       */
-      if ((res == 0) && (lock_flags & LOCKFILE_FAIL_IMMEDIATELY) &&
-                       (GetLastError () == ERROR_LOCK_FAILED))
-       {
-         set_errno (EAGAIN);
-         return -1;
-       }
-    }
-
-  if (res == 0)
-    {
-      __seterrno ();
-      return -1;
-    }
-
-  return 0;
-}
-
 int
 fhandler_disk_file::mkdir (mode_t mode)
 {
   int res = -1;
   SECURITY_ATTRIBUTES sa = sec_none_nih;
-  security_descriptor sd;
-
-  if (allow_ntsec && has_acls ())
-    set_security_attribute (S_IFDIR | ((mode & 07777) & ~cygheap->umask),
-                           &sa, sd);
-
   NTSTATUS status;
   HANDLE dir;
   OBJECT_ATTRIBUTES attr;
   IO_STATUS_BLOCK io;
-  ULONG fattr = FILE_ATTRIBUTE_DIRECTORY;
+  PFILE_FULL_EA_INFORMATION p = NULL;
+  ULONG plen = 0;
+
+  if (pc.fs_is_nfs ())
+    {
+      /* When creating a dir on an NFS share, we have to set the
+        file mode by writing a NFS fattr3 structure with the
+        correct mode bits set. */
+      plen = sizeof (FILE_FULL_EA_INFORMATION) + sizeof (NFS_V3_ATTR)
+            + sizeof (fattr3);
+      p = (PFILE_FULL_EA_INFORMATION) alloca (plen);
+      p->NextEntryOffset = 0;
+      p->Flags = 0;
+      p->EaNameLength = sizeof (NFS_V3_ATTR) - 1;
+      p->EaValueLength = sizeof (fattr3);
+      strcpy (p->EaName, NFS_V3_ATTR);
+      fattr3 *nfs_attr = (fattr3 *) (p->EaName + p->EaNameLength + 1);
+      memset (nfs_attr, 0, sizeof (fattr3));
+      nfs_attr->type = NF3DIR;
+      nfs_attr->mode = (mode & 07777) & ~cygheap->umask;
+    }
   status = NtCreateFile (&dir, FILE_LIST_DIRECTORY | SYNCHRONIZE,
                         pc.get_object_attr (attr, sa), &io, NULL,
-                        fattr, FILE_SHARE_VALID_FLAGS, FILE_CREATE,
+                        FILE_ATTRIBUTE_DIRECTORY, FILE_SHARE_VALID_FLAGS,
+                        FILE_CREATE,
                         FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT
                         | FILE_OPEN_FOR_BACKUP_INTENT,
-                        NULL, 0);
+                        p, plen);
   if (NT_SUCCESS (status))
     {
+      if (has_acls ())
+       set_file_attribute (dir, pc, ILLEGAL_UID, ILLEGAL_GID,
+                           S_JUSTCREATED | S_IFDIR
+                           | ((mode & 07777) & ~cygheap->umask));
       NtClose (dir);
       res = 0;
     }
@@ -1512,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))
     {
@@ -1542,12 +1554,12 @@ fhandler_disk_file::rmdir ()
 
 #define DIR_BUF_SIZE   (DIR_NUM_ENTRIES \
                         * (sizeof (FILE_ID_BOTH_DIR_INFORMATION) \
-                           + 2 * (NAME_MAX + 1)))
+                           + (NAME_MAX + 1) * sizeof (WCHAR)))
 
 struct __DIR_cache
 {
+  char  __cache[DIR_BUF_SIZE]; /* W2K needs this buffer 8 byte aligned. */
   ULONG __pos;
-  char  __cache[DIR_BUF_SIZE];
 };
 
 #define d_cachepos(d)  (((__DIR_cache *) (d)->__d_dirname)->__pos)
@@ -1601,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
@@ -1623,11 +1657,23 @@ 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, FileBothDirectoryInformation otherwise. */
+            persistent ACLs, FileBothDirectoryInformation otherwise.
+
+            NFS clients hide dangling symlinks from directory queries,
+            unless you use the FileNamesInformation info class.
+            On newer NFS clients (>=Vista) FileIdBothDirectoryInformation
+            works fine, but only if the NFS share is mounted to a drive
+            letter.  TODO: We don't test that here for now, but it might
+            be worth to test if there's a speed gain in using
+            FileIdBothDirectoryInformation, because it doesn't require to
+            open the file to read the inode number. */
          if (pc.hasgood_inode ())
            {
              dir->__flags |= dirent_set_d_ino;
-             if (wincap.has_fileid_dirinfo ())
+             if (pc.fs_is_nfs ())
+               dir->__flags |= dirent_nfs_d_ino;
+             else if (wincap.has_fileid_dirinfo ()
+                      && !pc.has_buggy_fileid_dirinfo ())
                dir->__flags |= dirent_get_d_ino;
            }
        }
@@ -1664,18 +1710,6 @@ free_dir:
   return res;
 }
 
-static inline __ino64_t
-readdir_get_ino_by_handle (HANDLE hdl)
-{
-  IO_STATUS_BLOCK io;
-  FILE_INTERNAL_INFORMATION pfai;
-
-  if (!NtQueryInformationFile (hdl, &io, &pfai, sizeof pfai,
-                              FileInternalInformation))
-    return pfai.FileId.QuadPart;
-  return 0;
-}
-
 __ino64_t __stdcall
 readdir_get_ino (const char *path, bool dot_dot)
 {
@@ -1695,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);
+  path_conv pc (path, PC_SYM_NOFOLLOW | PC_POSIX | PC_NOWARN | PC_KEEP_HANDLE);
   if (pc.isspecial ())
     {
       if (!stat_worker (pc, &st))
@@ -1703,15 +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 = readdir_get_ino_by_handle (hdl);
-      NtClose (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 ());
     }
   return ino;
 }
@@ -1726,12 +1763,28 @@ 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.  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_REPARSE_POINT)))
+    {
+      if (attr & FILE_ATTRIBUTE_DIRECTORY)
+       de->d_type = DT_DIR;
+      /* FILE_ATTRIBUTE_SYSTEM might denote system-bit type symlinks. */
+      else if (!(attr & FILE_ATTRIBUTE_SYSTEM))
+       de->d_type = DT_REG;
+    }
+
   /* Check for directory reparse point.  These are potential volume mount
      points which have another inode than the underlying directory. */
   if ((attr & (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT))
@@ -1741,60 +1794,86 @@ fhandler_disk_file::readdir_helper (DIR *dir, dirent *de, DWORD w32_err,
       OBJECT_ATTRIBUTES attr;
       IO_STATUS_BLOCK io;
 
-      InitializeObjectAttributes (&attr, fname, OBJ_CASE_INSENSITIVE,
+      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 = readdir_get_ino_by_handle (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;
-      UNICODE_STRING lname;
 
       RtlInitCountedUnicodeString (&uname,
                                   fname->Buffer
                                   + fname->Length / sizeof (WCHAR) - 4,
                                   4 * sizeof (WCHAR));
-      RtlInitCountedUnicodeString (&lname, (PWCHAR) L".lnk",
-                                  4 * sizeof (WCHAR));
-
-      if (RtlEqualUnicodeString (&uname, &lname, TRUE))
+      if (RtlEqualUnicodeString (&uname, &ro_u_lnk, TRUE))
        {
-         UNICODE_STRING dirname = *pc.get_nt_native_path ();
-         dirname.Buffer += 4; /* Skip leading \??\ */
-         dirname.Length -= 4 * sizeof (WCHAR);
+         tmp_pathbuf tp;
          UNICODE_STRING fbuf;
-         ULONG len = dirname.Length + fname->Length + 2 * sizeof (WCHAR);
 
-         RtlInitEmptyUnicodeString (&fbuf, (PCWSTR) alloca (len), len);
-         RtlCopyUnicodeString (&fbuf, &dirname);
+         tp.u_get (&fbuf);
+         RtlCopyUnicodeString (&fbuf, pc.get_nt_native_path ());
          RtlAppendUnicodeToString (&fbuf, L"\\");
          RtlAppendUnicodeStringToString (&fbuf, fname);
+         fbuf.Buffer += 4; /* Skip leading \??\ */
+         fbuf.Length -= 4 * sizeof (WCHAR);
+         if (fbuf.Buffer[1] != L':') /* UNC path */
+           {
+             *(fbuf.Buffer += 2) = L'\\';
+             fbuf.Length -= 2 * sizeof (WCHAR);
+           }
          path_conv fpath (&fbuf, PC_SYM_NOFOLLOW);
-         if (fpath.issymlink () || fpath.is_fs_special ())
-           fname->Length -= 4 * sizeof (WCHAR);
+         if (fpath.issymlink ())
+           {
+             fname->Length -= 4 * sizeof (WCHAR);
+             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;
+           }
        }
     }
 
-  char tmp[NAME_MAX + 1];
-  sys_wcstombs (tmp, NAME_MAX, fname->Buffer, fname->Length / sizeof (WCHAR));
-  if (pc.isencoded ())
-    fnunmunge (de->d_name, tmp);
-  else
-    strcpy (de->d_name, tmp);
-  if (dir->__d_position == 0 && !strcmp (tmp, "."))
-    dir->__flags |= dirent_saw_dot;
-  else if (dir->__d_position == 1 && !strcmp (tmp, ".."))
-    dir->__flags |= dirent_saw_dot_dot;
+  sys_wcstombs (de->d_name, NAME_MAX + 1, fname->Buffer,
+               fname->Length / sizeof (WCHAR));
+
+  /* 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;
 }
 
@@ -1805,6 +1884,8 @@ fhandler_disk_file::readdir (DIR *dir, dirent *de)
   NTSTATUS status = STATUS_SUCCESS;
   PFILE_ID_BOTH_DIR_INFORMATION buf = NULL;
   PWCHAR FileName;
+  ULONG FileNameLength;
+  ULONG FileAttributes = 0;
   IO_STATUS_BLOCK io;
   UNICODE_STRING fname;
 
@@ -1814,18 +1895,23 @@ fhandler_disk_file::readdir (DIR *dir, dirent *de)
     {
       if ((dir->__flags & dirent_get_d_ino))
        {
-         status = NtQueryDirectoryFile (get_handle (), NULL, NULL, 0, &io,
+         status = NtQueryDirectoryFile (get_handle (), NULL, NULL, NULL, &io,
                                         d_cache (dir), DIR_BUF_SIZE,
                                         FileIdBothDirectoryInformation,
                                         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
@@ -1858,7 +1944,8 @@ fhandler_disk_file::readdir (DIR *dir, dirent *de)
                  if (d_cachepos (dir) == 0)
                    {
                      status = NtQueryDirectoryFile (get_handle (), NULL, NULL,
-                                          0, &io, d_cache (dir), DIR_BUF_SIZE,
+                                          NULL, &io, d_cache (dir),
+                                          DIR_BUF_SIZE,
                                           FileBothDirectoryInformation,
                                           FALSE, NULL, cnt == 0);
                      if (!NT_SUCCESS (status))
@@ -1875,15 +1962,19 @@ fhandler_disk_file::readdir (DIR *dir, dirent *de)
            }
        }
       if (!(dir->__flags & dirent_get_d_ino))
-       status = NtQueryDirectoryFile (get_handle (), NULL, NULL, 0, &io,
+       status = NtQueryDirectoryFile (get_handle (), NULL, NULL, NULL, &io,
                                       d_cache (dir), DIR_BUF_SIZE,
-                                      FileBothDirectoryInformation,
+                                      (dir->__flags & dirent_nfs_d_ino)
+                                      ? FileNamesInformation
+                                      : FileBothDirectoryInformation,
                                       FALSE, NULL, dir->__d_position == 0);
     }
 
 go_ahead:
 
-  if (!NT_SUCCESS (status))
+  if (status == STATUS_NO_MORE_FILES)
+    /*nothing*/;
+  else if (!NT_SUCCESS (status))
     debug_printf ("NtQueryDirectoryFile failed, status %p, win32 error %lu",
                  status, RtlNtStatusToDosError (status));
   else
@@ -1896,56 +1987,103 @@ go_ahead:
       if ((dir->__flags & dirent_get_d_ino))
        {
          FileName = buf->FileName;
+         FileNameLength = buf->FileNameLength;
+         FileAttributes = buf->FileAttributes;
          if ((dir->__flags & dirent_set_d_ino))
            de->d_ino = buf->FileId.QuadPart;
        }
+      else if ((dir->__flags & dirent_nfs_d_ino))
+       {
+         FileName = ((PFILE_NAMES_INFORMATION) buf)->FileName;
+         FileNameLength = ((PFILE_NAMES_INFORMATION) buf)->FileNameLength;
+       }
       else
-       FileName = ((PFILE_BOTH_DIR_INFORMATION) buf)->FileName;
-      RtlInitCountedUnicodeString (&fname, FileName, buf->FileNameLength);
+       {
+         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 && buf->FileNameLength == 2
-             && FileName[0] == '.')
-           de->d_ino = readdir_get_ino_by_handle (get_handle ());
-         else if (dir->__d_position == 1 && buf->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 = readdir_get_ino_by_handle (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, OBJ_CASE_INSENSITIVE,
+             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 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 = readdir_get_ino_by_handle (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;
+                   }
                }
            }
-         /* Enforce namehash as inode number on untrusted file systems. */
-         if (!pc.isgood_inode (de->d_ino))
-           {
-             dir->__flags &= ~dirent_set_d_ino;
-             de->d_ino = 0;
-           }
        }
     }
 
   if (!(res = readdir_helper (dir, de, RtlNtStatusToDosError (status),
-                             buf ? buf->FileAttributes : 0, &fname)))
+                             FileAttributes, &fname)))
     dir->__d_position++;
   else if (!(dir->__flags & dirent_saw_dot))
     {
       strcpy (de->d_name , ".");
-      de->d_ino = readdir_get_ino_by_handle (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;
@@ -1956,24 +2094,27 @@ go_ahead:
       if (!(dir->__flags & dirent_isroot))
        de->d_ino = readdir_get_ino (get_name (), true);
       else
-       de->d_ino = readdir_get_ino_by_handle (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)
@@ -1991,15 +2132,12 @@ fhandler_disk_file::rewinddir (DIR *dir)
         to NtQueryDirectoryFile on remote shares is ignored, thus
         resulting in not being able to rewind on remote shares.  By
         reopening the directory, we get a fresh new directory pointer. */
-      UNICODE_STRING fname;
       OBJECT_ATTRIBUTES attr;
       NTSTATUS status;
       IO_STATUS_BLOCK io;
       HANDLE new_dir;
 
-      RtlInitUnicodeString (&fname, L"");
-      InitializeObjectAttributes (&attr, &fname, OBJ_CASE_INSENSITIVE,
-                                 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
@@ -2071,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
@@ -2086,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:";
@@ -2094,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;
 }
@@ -2146,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;
 }