This is the mail archive of the cygwin-developers mailing list for the Cygwin project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

Re: Cygwin Filesystem Performance degradation 1.7.5 vs 1.7.7, and methods for improving performance


Hi,

Again, some performance tests (compared latest version from cvs with Corrina's patch and my patch, against 1.7.5).
tested both with my test program (which simply call lstat), and with ls -l /bin.
I also improved a little bit the code in my patch.


Results:
1.7.5 unpatched:
  3592 files stat() 489.3ms, per file: 0.1362ms
  /bin$ time ls -l > /dev/null - 0m1.140s
1.7.5 (with my patch):
  3592 files stat() 80.08ms, per file: 0.02229ms
  /bin$ time ls -l > /dev/null - 0m0.203s

1.7.8 unpatched:
  3592 files stat() 3353ms, per file: 0.9333ms
  /bin$ time ls -l > /dev/null - 0m3.907s
1.7.8 (Corrina's xstat):
  3592 files stat() 169.9ms, per file: 0.04731ms
  /bin$ time ls -l > /dev/null - 0m0.407s
1.7.8 (My QIF patch):
  3592 files stat() 190.4ms, per file: 0.05301ms
  /bin$ time ls -l > /dev/null - 0m0.547s

There are here two separate methods for improving performance:

1) xstat patch: Uses QDF() with a search expression, allowing one open+QDF+close to locate foo+foo.exe+foo.lnk.
Upside: 10x faster than 1.7.8 unpatched. 25% faster than QIF patch.
Downside: does not retrieve st_nlink (but its not that of a problem, since usage of st_nlink in applicates is very rate, and its controllable via CYGWIN=xstat, and its by default off (to keep compatibility))


2) QIF patch: Uses QIF() without READ access when opening, thus telling NTFS and the antivirus that we have no intentions to read the file's data, and thus NTFS will not perform read-ahead, and the AV will not scan the contents for a virus.
Upside: 8x faster than 1.7.8 unpatched. Retrives st_nlink.
Downside: not as fast as the xstat patch.


But it seems that the good news is that these patches can work in parallel (they are in different parts of the code, and the patches dont conflict).

So having BOTH patches in the code gives all users the benefit of 8x performance over 1.7.8 (QIF), and a possibility to manually set CYGWIN=xstat for another 30% (xstat) - if they are professionals and know what they are doing.

So - attached is the MERGE patch (xstat+QIF) which brings the best of both worlds :-)

Yoni.

Index: cygwin/environ.cc
===================================================================
RCS file: /cvs/src/src/winsup/cygwin/environ.cc,v
retrieving revision 1.183
diff -u -p -r1.183 environ.cc
--- cygwin/environ.cc	18 May 2010 14:30:50 -0000	1.183
+++ cygwin/environ.cc	7 Oct 2010 12:12:19 -0000
@@ -31,6 +31,7 @@ details. */
 #include "child_info.h"
 #include "ntdll.h"

+extern bool use_xcheck;
 extern bool dos_file_warning;
 extern bool ignore_case_with_glob;
 extern bool allow_winsymlinks;
@@ -605,6 +606,7 @@ static struct parse_thing
   {"tty", {NULL}, set_process_state, NULL, {{0}, {PID_USETTY}}},
   {"upcaseenv", {&create_upcaseenv}, justset, NULL, {{false}, {true}}},
   {"winsymlinks", {&allow_winsymlinks}, justset, NULL, {{false}, {true}}},
+  {"xcheck", {&use_xcheck}, justset, NULL, {{false}, {true}}},
   {NULL, {0}, justset, 0, {{0}, {0}}}
 };

Index: cygwin/fhandler_disk_file.cc
===================================================================
RCS file: /cvs/src/src/winsup/cygwin/fhandler_disk_file.cc,v
retrieving revision 1.344
diff -u -p -r1.344 fhandler_disk_file.cc
--- cygwin/fhandler_disk_file.cc	2 Oct 2010 19:03:44 -0000	1.344
+++ cygwin/fhandler_disk_file.cc	7 Oct 2010 12:12:31 -0000
@@ -345,11 +345,11 @@ fhandler_base::fstat_by_handle (struct _
   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. */
+ on the information stored in pc.finfo. So we overwrite them here. */
if (get_io_handle ())
{
- PFILE_NETWORK_OPEN_INFORMATION pfnoi = pc.fnoi ();
- status = NtQueryInformationFile (h, &io, pfnoi, sizeof *pfnoi,
+ PFILE_CYGWIN_INFORMATION pfci = pc.finfo ();
+ status = NtQueryInformationFile (h, &io, pfci, sizeof *pfci,
FileNetworkOpenInformation);
if (!NT_SUCCESS (status))
{
@@ -403,6 +403,8 @@ fhandler_base::fstat_by_name (struct __s
WCHAR buf[NAME_MAX + 1];
} fdi_buf;


+  if (!ino)
+    ino = pc.finfo ()->FileId.QuadPart;
   if (!ino && pc.hasgood_inode ()
       && wincap.has_fileid_dirinfo () && !pc.has_buggy_fileid_dirinfo ())
     {
@@ -441,14 +443,20 @@ fhandler_base::fstat_fs (struct __stat64
   int oret;
   int open_flags = O_RDONLY | O_BINARY;

+ if (pc.fs_is_nfs ())
+ return fstat_by_nfs_ea (buf);
+
if (get_stat_handle ())
{
if (!nohandle () && !is_fs_special ())
- res = pc.fs_is_nfs () ? fstat_by_nfs_ea (buf) : fstat_by_handle (buf);
+ res = fstat_by_handle (buf);
if (res)
res = fstat_by_name (buf);
return res;
}
+ else if (pc.xchecked ())
+ return fstat_by_name (buf);
+
/* 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
@@ -485,19 +493,19 @@ fhandler_base::fstat_helper (struct __st
IO_STATUS_BLOCK st;
FILE_COMPRESSION_INFORMATION fci;
HANDLE h = get_stat_handle ();
- PFILE_NETWORK_OPEN_INFORMATION pfnoi = pc.fnoi ();
+ PFILE_CYGWIN_INFORMATION pfci = pc.finfo ();
ULONG attributes = pc.file_attributes ();


- to_timestruc_t ((PFILETIME) &pfnoi->LastAccessTime, &buf->st_atim);
- to_timestruc_t ((PFILETIME) &pfnoi->LastWriteTime, &buf->st_mtim);
+ to_timestruc_t ((PFILETIME) &pfci->LastAccessTime, &buf->st_atim);
+ to_timestruc_t ((PFILETIME) &pfci->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,
+ to_timestruc_t (pfci->ChangeTime.QuadPart ? (PFILETIME) &pfci->ChangeTime
+ : (PFILETIME) &pfci->LastWriteTime,
&buf->st_ctim);
- to_timestruc_t ((PFILETIME) &pfnoi->CreationTime, &buf->st_birthtim);
+ to_timestruc_t ((PFILETIME) &pfci->CreationTime, &buf->st_birthtim);
buf->st_rdev = buf->st_dev = get_dev ();
- buf->st_size = (_off64_t) pfnoi->EndOfFile.QuadPart;
+ buf->st_size = (_off64_t) pfci->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. */
@@ -515,10 +523,10 @@ fhandler_base::fstat_helper (struct __st


buf->st_blksize = PREFERRED_IO_BLKSIZE;

-  if (pfnoi->AllocationSize.QuadPart >= 0LL)
+  if (pfci->AllocationSize.QuadPart >= 0LL)
     /* A successful NtQueryInformationFile returns the allocation size
        correctly for compressed and sparse files as well. */
-    buf->st_blocks = (pfnoi->AllocationSize.QuadPart + S_BLKSIZE - 1)
+    buf->st_blocks = (pfci->AllocationSize.QuadPart + S_BLKSIZE - 1)
 		     / S_BLKSIZE;
   else if (::has_attribute (attributes, FILE_ATTRIBUTE_COMPRESSED
 					| FILE_ATTRIBUTE_SPARSE_FILE)
@@ -591,7 +599,7 @@ fhandler_base::fstat_helper (struct __st
 	{
 	  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 ();

Index: cygwin/ntdll.h
===================================================================
RCS file: /cvs/src/src/winsup/cygwin/ntdll.h,v
retrieving revision 1.104
diff -u -p -r1.104 ntdll.h
--- cygwin/ntdll.h 24 Sep 2010 12:41:33 -0000 1.104
+++ cygwin/ntdll.h 7 Oct 2010 12:12:37 -0000
@@ -1074,8 +1074,12 @@ extern "C"
if (dirname)
RtlInitCountedUnicodeString (dirname, path->Buffer, len * sizeof (WCHAR));
if (basename)
- RtlInitCountedUnicodeString (basename, &path->Buffer[len],
- path->Length - len * sizeof (WCHAR));
+ {
+ RtlInitCountedUnicodeString (basename, &path->Buffer[len],
+ path->Length - len * sizeof (WCHAR));
+ /* Preserve MaximumLength! */
+ basename->MaximumLength = path->MaximumLength - len * sizeof (WCHAR);
+ }
}
/* Check if prefix is a prefix of path. */
inline
Index: cygwin/path.cc
===================================================================
RCS file: /cvs/src/src/winsup/cygwin/path.cc,v
retrieving revision 1.615
diff -u -p -r1.615 path.cc
--- cygwin/path.cc 2 Oct 2010 19:03:44 -0000 1.615
+++ cygwin/path.cc 7 Oct 2010 12:12:50 -0000
@@ -95,6 +95,8 @@ struct symlink_info
_major_t major;
_minor_t minor;
_mode_t mode;
+ int xcheck (char *path, const suffix_info *suffixes, fs_info &fs,
+ path_conv_handle &conv_hdl);
int check (char *path, const suffix_info *suffixes, fs_info &fs,
path_conv_handle &conv_hdl);
int set (char *path);
@@ -105,6 +107,10 @@ struct symlink_info
int check_nfs_symlink (HANDLE h);
int posixify (char *srcbuf);
bool set_error (int);
+ bool set_error_from_nt_status (NTSTATUS status)
+ {
+ return set_error (geterrno_from_win_error (RtlNtStatusToDosError (status)));
+ }
};


muto NO_COPY cwdstuff::cwd_lock;
@@ -435,7 +441,7 @@ get_nt_native_path (const char *path, UN
if (dos)
{
/* Unfortunately we can't just use transform_chars with the tfx_rev_chars
- table since only leading and trainlig spaces and dots are affected.
+ table since only leading and trailing spaces and dots are affected.
So we step to every backslash and fix surrounding dots and spaces.
That makes these broken filesystems a bit slower, but, hey. */
PWCHAR cp = upath.Buffer + 7;
@@ -586,6 +592,8 @@ path_conv::check (const UNICODE_STRING *
path_conv::check (path, opt, suffixes);
}


+bool use_xcheck = false;
+
 void
 path_conv::check (const char *src, unsigned opt,
 		  const suffix_info *suffixes)
@@ -651,6 +659,8 @@ path_conv::check (const char *src, unsig
       error = ENOENT;
       return;
     }
+  if (use_xcheck)
+    opt |= PC_XCHECK;

   bool is_msdos = false;
   /* This loop handles symlink expansion.  */
@@ -860,7 +870,10 @@ path_conv::check (const char *src, unsig

is_fs_via_procsys:

-	  symlen = sym.check (full_path, suff, fs, conv_handle);
+	  if (opt & PC_XCHECK)
+	    symlen = sym.xcheck (full_path, suff, fs, conv_handle);
+	  else
+	    symlen = sym.check (full_path, suff, fs, conv_handle);

is_virtual_symlink:

@@ -2199,6 +2212,186 @@ symlink_info::parse_device (const char *
   return isdevice = true;
 }

+/* Private class to store a single suffix. Used by ucs_suffixes exclusively. */
+#define UCS_MAX_LEN 6
+class ucs_suffix
+{
+ UNICODE_STRING name;
+ WCHAR buf[UCS_MAX_LEN];
+ void set (const suffix_info *sin)
+ {
+ sys_mbstowcs (buf, UCS_MAX_LEN, sin->name);
+ RtlInitUnicodeString (&name, buf);
+ }
+ void add_lnk ()
+ { RtlInitUnicodeString (&name, wcscpy (buf, L".lnk")); }
+ bool check (PUNICODE_STRING fname)
+ { return RtlEqualUnicodePathSuffix (fname, &name, TRUE); }
+ PCWSTR get () const { return buf; }
+ friend class ucs_suffixes;
+};
+
+/* Replacement class to store the incoming suffixes to symlink_info::xcheck.
+ The suffixes are stored as UNICODE_STRINGS for quicker comparison.
+ Ultimately this can be used for symlink_info::check as well. The
+ constructor also takes over the job of suffix_scan::has */
+#define UCS_MAX_SUFFIXES 8
+class ucs_suffixes
+{
+ ucs_suffix suf[UCS_MAX_SUFFIXES];
+ int cnt;
+ int lnk_idx;
+public:
+ ucs_suffixes (const suffix_info *sin, const char *path, char *&ext_here)
+ : cnt (0), lnk_idx (-1)
+ {
+ if (sin)
+ for (int i = 0; i < UCS_MAX_SUFFIXES - 1 && sin[i].name; ++i)
+ if (*sin[i].name)
+ suf[cnt++].set (sin + i);
+
+ const char *fname = strrchr (path, '\\');
+ fname = fname ? fname + 1 : path;
+ ext_here = strrchr (fname, '.');
+ char *eopath = strchr (path, '\0');
+
+ if (ext_here)
+ {
+ if (sin)
+ {
+ /* Check if the extension matches a known extension */
+ for (const suffix_info *ex = sin; ex->name != NULL; ex++)
+ if (ascii_strcasematch (ext_here, ex->name))
+ {
+ cnt = 0;
+ return;
+ }
+ }
+ /* Didn't match. Use last resort -- .lnk. */
+ if (ascii_strcasematch (ext_here, ".lnk"))
+ {
+ cnt = 0;
+ return;
+ }
+ }
+ ext_here = eopath;
+ /* Avoid attaching suffixes if the resulting filename would be invalid. */
+ if (eopath - fname > NAME_MAX - 4)
+ cnt = 0;
+ }
+ void add_lnk () { lnk_idx = cnt; suf[cnt++].add_lnk (); }
+ int check (PUNICODE_STRING fname)
+ {
+ for (int i = 0; i < cnt; ++i)
+ if (suf[i].check (fname))
+ return i;
+ return -1;
+ }
+ PCWSTR get (int i) const { return i < 0 || i >= cnt ? L"" : suf[i].get (); }
+ int count () const { return cnt; }
+ bool lnk_match (int i) const { return i >= 0 && i == lnk_idx; }
+};
+
+/* This class is an abstraction for the various FILE_INFO classes used by
+ symlink_info::xcheck. The constructor chooses the right info class
+ based on the information fetched from fs_info::update. The idea here is
+ that symlink_info::xcheck doesn't have to know the actual info class
+ used to get the necessary information. The caller has to provide the
+ buffer and the buffer size for the file info. */
+class file_info
+{
+ FILE_INFORMATION_CLASS fic;
+ PBYTE buffer;
+ PBYTE curptr;
+ size_t size;
+ UNICODE_STRING uname;
+public:
+ file_info (fs_info &fs, PVOID buf, size_t siz)
+ : buffer ((PBYTE) buf), curptr (NULL), size (siz)
+ {
+ if (fs.is_nfs ())
+ fic = FileNamesInformation;
+ else if (fs.hasgood_inode () && wincap.has_fileid_dirinfo ()
+ && !fs.has_buggy_fileid_dirinfo ())
+ fic = FileIdBothDirectoryInformation;
+ else
+ fic = FileBothDirectoryInformation;
+ }
+ operator PVOID () const { return buffer; }
+ operator ULONG () const { return size; }
+ operator FILE_INFORMATION_CLASS () const { return fic; }
+ bool next ()
+ {
+ bool ret = true;
+
+ if (!curptr)
+ curptr = buffer;
+ else if (PFILE_NAMES_INFORMATION (curptr)->NextEntryOffset != 0)
+ curptr += PFILE_NAMES_INFORMATION (curptr)->NextEntryOffset;
+ else
+ {
+ curptr = NULL;
+ ret = false;
+ }
+ return ret;
+ }
+ PUNICODE_STRING name ()
+ {
+ if (!curptr)
+ return NULL;
+ switch (fic)
+ {
+ case FileNamesInformation:
+ uname.Length = uname.MaximumLength
+ = PFILE_NAMES_INFORMATION (curptr)->FileNameLength;
+ uname.Buffer = PFILE_NAMES_INFORMATION (curptr)->FileName;
+ break;
+ case FileBothDirectoryInformation:
+ uname.Length = uname.MaximumLength
+ = PFILE_BOTH_DIRECTORY_INFORMATION (curptr)->FileNameLength;
+ uname.Buffer = PFILE_BOTH_DIRECTORY_INFORMATION (curptr)->FileName;
+ break;
+ case FileIdBothDirectoryInformation:
+ uname.Length = uname.MaximumLength
+ = PFILE_ID_BOTH_DIR_INFORMATION (curptr)->FileNameLength;
+ uname.Buffer = PFILE_ID_BOTH_DIR_INFORMATION (curptr)->FileName;
+ break;
+ default:
+ return NULL;
+ }
+ return &uname;
+ }
+ ULONG file_attributes ()
+ {
+ if (curptr && fic != FileNamesInformation)
+ return PFILE_BOTH_DIRECTORY_INFORMATION (curptr)->FileAttributes;
+ return INVALID_FILE_ATTRIBUTES;
+ }
+ void copy (PFILE_CYGWIN_INFORMATION pfci, PUNICODE_STRING dir,
+ PUNICODE_STRING file)
+ {
+ if (curptr && fic != FileNamesInformation)
+ {
+ memcpy (pfci, &PFILE_ID_BOTH_DIR_INFORMATION (curptr)->CreationTime,
+ 4 * sizeof (LARGE_INTEGER));
+ pfci->EndOfFile.QuadPart
+ = PFILE_ID_BOTH_DIR_INFORMATION (curptr)->EndOfFile.QuadPart;
+ pfci->AllocationSize.QuadPart
+ = PFILE_ID_BOTH_DIR_INFORMATION (curptr)->AllocationSize.QuadPart;
+ pfci->FileAttributes
+ = PFILE_ID_BOTH_DIR_INFORMATION (curptr)->FileAttributes;
+ pfci->ReparseTag
+ = PFILE_ID_BOTH_DIR_INFORMATION (curptr)->EaSize;
+ if (fic == FileIdBothDirectoryInformation)
+ pfci->FileId.QuadPart
+ = PFILE_ID_BOTH_DIR_INFORMATION (curptr)->FileId.QuadPart;
+ else
+ pfci->FileId.QuadPart = hash_path_name (hash_path_name (0, dir),
+ file);
+ }
+ }
+};
+
/* Check if PATH is a symlink. PATH must be a valid Win32 path name.


    If PATH is a symlink, put the value of the symlink--the file to
@@ -2217,6 +2410,373 @@ symlink_info::parse_device (const char *
    stored into BUF if PATH is a symlink.  */

int
+symlink_info::xcheck (char *path, const suffix_info *suffixes, fs_info &fs,
+ path_conv_handle &conv_hdl)
+{
+ int res = 0;
+ HANDLE dh;
+ NTSTATUS status;
+ UNICODE_STRING upath, dirname, basename, filter;
+ OBJECT_ATTRIBUTES attr;
+ IO_STATUS_BLOCK io;
+ bool is_root_dir = false;
+ const ULONG ci_flag = cygwin_shared->obcaseinsensitive
+ || (pflags & PATH_NOPOSIX) ? OBJ_CASE_INSENSITIVE : 0;
+
+ contents[0] = '\0';
+ issymlink = isdevice = ext_tacked_on = false;
+ error = major = minor = mode = 0;
+ fileattr = INVALID_FILE_ATTRIBUTES;
+
+ ucs_suffixes suff (suffixes, path, ext_here);
+ extn = ext_here - path;
+ pflags &= ~(PATH_SYMLINK | PATH_LNK | PATH_REP | PC_KEEP_HANDLE);
+ pflags |= PATH_NOACL | PATH_NOTEXEC | PATH_XCHECKED;
+
+ tmp_pathbuf tp;
+ tp.u_get (&upath);
+ get_nt_native_path (path, upath, pflags & PATH_DOS);
+ /* First check if we the path is the root dir of a drive or share.
+ Checking the root dir info requires to open a handle to the root
+ dir directly since there's no (accessible) parent dir anyway. */
+ if (upath.Buffer[upath.Length / sizeof (WCHAR) - 1] == L'\\') /* \??\X:\ */
+ is_root_dir = true;
+ else if (RtlEqualUnicodePathPrefix (&upath, &ro_u_uncp, FALSE))
+ {
+ /* The idea here is that a valid path to the root dir of a share has
+ only one additional backslash following the UNC path prefix, while
+ any deeper path has at least two following backslashes. */
+ PWCHAR p;
+ p = wcschr (upath.Buffer + (ro_u_uncp.Length / sizeof (WCHAR)), L'\\');
+ if (p && !wcschr (p + 1, L'\\'))
+ is_root_dir = true;
+ }
+ if (is_root_dir)
+ {
+ InitializeObjectAttributes (&attr, &upath, ci_flag, NULL, NULL);
+ status = NtOpenFile (&dh,
+ READ_CONTROL | FILE_READ_ATTRIBUTES | FILE_READ_EA,
+ &attr, &io, FILE_SHARE_VALID_FLAGS,
+ FILE_OPEN_FOR_BACKUP_INTENT);
+ if (!NT_SUCCESS (status))
+ {
+ set_error_from_nt_status (status);
+ return 0;
+ }
+ fs.update (&upath, dh);
+ if (fs.is_nfs ())
+ {
+ status = nfs_fetch_fattr3 (dh, conv_hdl.nfsattr ());
+ if (NT_SUCCESS (status))
+ fileattr = ((conv_hdl.nfsattr ()->type & 7) == NF3DIR)
+ ? FILE_ATTRIBUTE_DIRECTORY : 0;
+ }
+ else
+ {
+ PFILE_CYGWIN_INFORMATION pfci = conv_hdl.finfo ();
+
+ status = NtQueryInformationFile (dh, &io, pfci, sizeof *pfci,
+ FileNetworkOpenInformation);
+ if ((status == STATUS_INVALID_PARAMETER
+ || status == STATUS_NOT_IMPLEMENTED)
+ && RtlEqualUnicodePathPrefix (&upath, &ro_u_uncp, FALSE))
+ {
+ /* This occurs when accessing SMB share root dirs hosted on
+ NT4 (STATUS_INVALID_PARAMETER), or when trying to access
+ SMB share root dirs from NT4 (STATUS_NOT_IMPLEMENTED). */
+ FILE_BASIC_INFORMATION fbi;
+
+ status = NtQueryInformationFile (dh, &io, &fbi, sizeof fbi,
+ FileBasicInformation);
+ if (NT_SUCCESS (status))
+ {
+ memcpy (pfci, &fbi, 4 * sizeof (LARGE_INTEGER));
+ pfci->EndOfFile.QuadPart
+ = pfci->AllocationSize.QuadPart = 0;
+ pfci->FileAttributes = fbi.FileAttributes;
+ }
+ }
+ pfci->ReparseTag = 0;
+ if (!fs.hasgood_inode ()
+ || !NT_SUCCESS (NtQueryInformationFile (dh, &io, &pfci->FileId,
+ sizeof pfci->FileId,
+ FileInternalInformation)))
+ pfci->FileId.QuadPart = hash_path_name (0, &upath);
+ if (NT_SUCCESS (status))
+ fileattr = pfci->FileAttributes;
+ }
+ NtClose (dh);
+ if (!NT_SUCCESS (status))
+ set_error_from_nt_status (status);
+ return 0;
+ }
+ /* The default case. We open the parent dir and fetch the file info by
+ NtQueryDirectoryFile. */
+ RtlSplitUnicodePath (&upath, &dirname, &basename);
+ InitializeObjectAttributes (&attr, &dirname, ci_flag, NULL, NULL);
+ status = NtOpenFile (&dh, 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))
+ {
+ set_error_from_nt_status (status);
+ return 0;
+ }
+ /* Check the fs info. This tells us which info class to use. */
+ fs.update (&upath, dh);
+ /* Only add the .lnk suffix if not on NFS. */
+ if (!fs.is_nfs ())
+ suff.add_lnk ();
+ else if (fs.has_dos_filenames_only () && !(pflags & PATH_DOS))
+ {
+ /* Filesystem doesn't grok leading spaces or trailing dots or spaces.
+ Regenerate unicode path accordingly. */
+ pflags |= PATH_DOS;
+ get_nt_native_path (path, upath, true);
+ RtlSplitUnicodePath (&upath, &dirname, &basename);
+ }
+ /* Copy basename into filter and check if we have to test for suffixes.
+ If so, append the wildcard expression. We use a more or less undocumented
+ wildcard style, which is also used by CMD. It allows to specify filter
+ expressions following the old DOS rules back when only 8+3 filenames
+ existed. Our filter expression only allows the filename itself, plus
+ all files which start with filename, then a dot, then a maximum of three
+ characters. This fortunately avoids the usage of the '*' wildcard which
+ could easily become a PITA in some directories... */
+ filter = basename;
+ if (!*ext_here && suff.count() > 0)
+ RtlAppendUnicodeToString (&filter, L"\">>>"); /* DOS_DOT, 3 * DOS_QM */
+
+ bool found = false;
+ int suff_idx = -1;
+ PUNICODE_STRING fname = NULL;
+ file_info fi (fs, tp.w_get (), USHRT_MAX);
+
+ /* Now call NtQueryDirectoryFile until we find the file or there is no more
+ file. */
+ for (BOOLEAN restart = TRUE; !found; restart = FALSE)
+ {
+ status = NtQueryDirectoryFile (dh, NULL, NULL, NULL, &io, fi, fi, fi,
+ FALSE, &filter, restart);
+ if (!NT_SUCCESS (status))
+ break;
+ while (fi.next ())
+ {
+ fname = fi.name ();
+ /* This expression *only* allows the filename itself, or any
+ suffix with exactly three chars. */
+ if (fname->Length != basename.Length
+ && fname->Length != basename.Length + 4 * sizeof (WCHAR))
+ continue;
+ /* Even if the directory handle has been opened case-sensitive,
+ the filter expression is *always* evaluated case-insensitive.
+ That means, if case-sensitivity is switched on, we have to
+ check that the returned filename matches the case of our
+ incoming filename. Grrr! */
+ if (!ci_flag && !RtlEqualUnicodePathPrefix (fname, &basename, FALSE))
+ continue;
+ if (fname->Length == basename.Length
+ || (suff_idx = suff.check (fname)) >= 0)
+ {
+ /* Yeah! */
+ found = true;
+ break;
+ }
+ }
+ }
+ if (!found)
+ set_error (ENOENT);
+ else
+ {
+ HANDLE fh;
+
+ /* Append suffix (if any) to the incoming path buffer. */
+ if (suff_idx >= 0)
+ {
+ sys_wcstombs (ext_here, 5, suff.get (suff_idx));
+ ext_tacked_on = true;
+ }
+ if (fs.is_nfs ())
+ {
+ /* On NFS, we need the real stat info. So, given that we
+ have to open the file anyway, this method is slower than
+ symlink_info::check on NFS! Something to keep in mind. */
+ fileattr = 0;
+ InitializeObjectAttributes (&attr, fname, ci_flag, dh, NULL);
+ status = NtCreateFile (&fh, FILE_READ_EA, &attr, &io, NULL, 0,
+ FILE_SHARE_VALID_FLAGS, FILE_OPEN,
+ FILE_OPEN_FOR_BACKUP_INTENT,
+ &nfs_aol_ffei, sizeof nfs_aol_ffei);
+ if (!NT_SUCCESS (status))
+ {
+ set_error_from_nt_status (status);
+ goto out;
+ }
+ status = nfs_fetch_fattr3 (fh, conv_hdl.nfsattr ());
+ if (NT_SUCCESS (status))
+ {
+ if ((conv_hdl.nfsattr ()->type & 7) == NF3DIR)
+ {
+ if (ext_tacked_on)
+ {
+ fileattr = INVALID_FILE_ATTRIBUTES;
+ set_error (ENOENT);
+ }
+ else
+ fileattr = FILE_ATTRIBUTE_DIRECTORY;
+ }
+ /* Eventually, check for symlink. */
+ else if ((conv_hdl.nfsattr ()->type & 7) == NF3LNK)
+ res = check_nfs_symlink (fh);
+ }
+ NtClose (fh);
+ }
+ else
+ {
+ PFILE_CYGWIN_INFORMATION pfci;
+
+ /* Copy file info over to path_conv. */
+ fileattr = fi.file_attributes ();
+ if (ext_tacked_on && (fileattr & FILE_ATTRIBUTE_DIRECTORY))
+ {
+ set_error (ENOENT);
+ fileattr = INVALID_FILE_ATTRIBUTES;
+ goto out;
+ }
+ fi.copy (pfci = conv_hdl.finfo (), &dirname, fname);
+ /* Eventually, check for various symlink types. */
+ if ((fileattr & (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_DIRECTORY))
+ == FILE_ATTRIBUTE_READONLY && suff.lnk_match (suff_idx))
+ {
+ InitializeObjectAttributes (&attr, fname, ci_flag, dh, NULL);
+ status = NtOpenFile (&fh, SYNCHRONIZE | GENERIC_READ, &attr,
+ &io, FILE_SHARE_VALID_FLAGS,
+ FILE_OPEN_FOR_BACKUP_INTENT);
+ if (!NT_SUCCESS (status))
+ goto out;
+ res = check_shortcut (fh);
+ if (!res)
+ {
+ if (ext_tacked_on)
+ {
+ fileattr = INVALID_FILE_ATTRIBUTES;
+ set_error (ENOENT);
+ }
+ }
+ else if (contents[0] != ':' || contents[1] != '\\')
+ parse_device (contents);
+ NtClose (fh);
+ }
+ else if (suff.lnk_match (suff_idx))
+ {
+ set_error (ENOENT);
+ fileattr = INVALID_FILE_ATTRIBUTES;
+ goto out;
+ }
+ else if ((fileattr & FILE_ATTRIBUTE_REPARSE_POINT)
+ && !fs.is_remote_drive())
+ {
+ if (pfci->ReparseTag != IO_REPARSE_TAG_SYMLINK
+ && pfci->ReparseTag != IO_REPARSE_TAG_MOUNT_POINT)
+ {
+ fileattr &= ~FILE_ATTRIBUTE_REPARSE_POINT;
+ goto out;
+ }
+ InitializeObjectAttributes (&attr, fname, ci_flag, dh, NULL);
+ status = NtOpenFile (&fh, READ_CONTROL, &attr, &io,
+ FILE_SHARE_VALID_FLAGS,
+ FILE_OPEN_REPARSE_POINT
+ | FILE_OPEN_FOR_BACKUP_INTENT);
+ if (!NT_SUCCESS (status))
+ {
+ fileattr &= ~FILE_ATTRIBUTE_REPARSE_POINT;
+ goto out;
+ }
+ res = check_reparse_point (fh);
+ if (res == -1)
+ {
+ fs.update (&upath, NULL);
+ res = 0;
+ }
+ else if (res)
+ conv_hdl.finfo ()->FileAttributes &= ~FILE_ATTRIBUTE_DIRECTORY;
+ NtClose (fh);
+ }
+ else if ((fileattr & (FILE_ATTRIBUTE_SYSTEM
+ | FILE_ATTRIBUTE_DIRECTORY))
+ == FILE_ATTRIBUTE_SYSTEM)
+ {
+ InitializeObjectAttributes (&attr, fname, ci_flag, dh, NULL);
+ status = NtOpenFile (&fh, SYNCHRONIZE | GENERIC_READ, &attr,
+ &io, FILE_SHARE_VALID_FLAGS,
+ FILE_OPEN_FOR_BACKUP_INTENT);
+ if (!NT_SUCCESS (status))
+ goto out;
+ res = check_sysfile (fh);
+ NtClose (fh);
+ }
+ }
+ }
+
+out:
+ NtClose (dh);
+ syscall_printf ("%d = symlink.xcheck (%s, %p) (%p)",
+ res, path, contents, pflags);
+ issymlink = res > 0;
+ return res;
+}
+
+static void zNtClose(HANDLE *h)
+{
+ if (!*h)
+ return;
+ NtClose(*h);
+ *h = NULL;
+}
+
+# define MIN_STAT_ACCESS (READ_CONTROL | FILE_READ_ATTRIBUTES)
+# define FULL_STAT_ACCESS (SYNCHRONIZE | GENERIC_READ)
+static int get_file_handle(HANDLE *h, OBJECT_ATTRIBUTES attr,
+ UNICODE_STRING upath, int read, ACCESS_MASK *access,
+ PVOID eabuf, ULONG easize)
+{
+ IO_STATUS_BLOCK io;
+ NTSTATUS status;
+ zNtClose(h);
+ /* The EA given to NtCreateFile allows to get a handle to a symlink on
+ an NFS share, rather than getting a handle to the target of the
+ symlink (which would spoil the task of this method quite a bit).
+ Fortunately it's ignored on most other file systems so we don't have
+ to special case NFS too much. */
+ if (read)
+ {
+ status = NtCreateFile (h, *access = FULL_STAT_ACCESS, &attr, &io, NULL,
+ 0, FILE_SHARE_VALID_FLAGS, FILE_OPEN,
+ FILE_OPEN_REPARSE_POINT
+ | FILE_OPEN_FOR_BACKUP_INTENT,
+ eabuf, easize);
+ if (eabuf)
+ debug_printf ("%p = NtCreateFile (2:%S)", status, &upath);
+ else
+ debug_printf ("%p = NtOpenFile (no-EA 1:%S)", status, &upath);
+ if (status != STATUS_ACCESS_DENIED)
+ return status;
+ }
+ status = NtCreateFile (h, *access = MIN_STAT_ACCESS | FILE_READ_EA,
+ &attr, &io, NULL, 0, FILE_SHARE_VALID_FLAGS,
+ FILE_OPEN,
+ FILE_OPEN_REPARSE_POINT
+ | FILE_OPEN_FOR_BACKUP_INTENT, eabuf, easize);
+ if (eabuf)
+ debug_printf ("%p = NtCreateFile (1:%S)", status, &upath);
+ else
+ debug_printf ("%p = NtOpenFile (no-EAs 2:%S)", status, &upath);
+ return status;
+}
+
+int
symlink_info::check (char *path, const suffix_info *suffixes, fs_info &fs,
path_conv_handle &conv_hdl)
{
@@ -2257,8 +2817,6 @@ restart:
PVOID eabuf = &nfs_aol_ffei;
ULONG easize = sizeof nfs_aol_ffei;


-# define MIN_STAT_ACCESS	(READ_CONTROL | FILE_READ_ATTRIBUTES)
-# define FULL_STAT_ACCESS	(SYNCHRONIZE | GENERIC_READ)
   ACCESS_MASK access = 0;

   bool had_ext = !!*ext_here;
@@ -2268,33 +2826,8 @@ restart:

error = 0;
get_nt_native_path (suffix.path, upath, pflags & PATH_DOS);
- if (h)
- {
- NtClose (h);
- h = NULL;
- }
- /* The EA given to NtCreateFile allows to get a handle to a symlink on
- an NFS share, rather than getting a handle to the target of the
- symlink (which would spoil the task of this method quite a bit).
- Fortunately it's ignored on most other file systems so we don't have
- to special case NFS too much. */
- status = NtCreateFile (&h, access = FULL_STAT_ACCESS, &attr, &io, NULL,
- 0, FILE_SHARE_VALID_FLAGS, FILE_OPEN,
- FILE_OPEN_REPARSE_POINT
- | FILE_OPEN_FOR_BACKUP_INTENT,
- eabuf, easize);
- if (status == STATUS_ACCESS_DENIED && eabuf)
- {
- status = NtCreateFile (&h, access = MIN_STAT_ACCESS | FILE_READ_EA,
- &attr, &io, NULL, 0, FILE_SHARE_VALID_FLAGS,
- FILE_OPEN,
- FILE_OPEN_REPARSE_POINT
- | FILE_OPEN_FOR_BACKUP_INTENT,
- eabuf, easize);
- debug_printf ("%p = NtCreateFile (2:%S)", status, &upath);
- }
- else
- debug_printf ("%p = NtCreateFile (1:%S)", status, &upath);
+ status = get_file_handle(&h, attr, upath, 0, &access, eabuf, easize);
+
/* No right to access EAs or EAs not supported? */
if (!NT_SUCCESS (status)
&& (status == STATUS_ACCESS_DENIED
@@ -2314,20 +2847,7 @@ restart:
eabuf = NULL;
easize = 0;
}
- status = NtOpenFile (&h, access = FULL_STAT_ACCESS, &attr, &io,
- FILE_SHARE_VALID_FLAGS,
- FILE_OPEN_REPARSE_POINT
- | FILE_OPEN_FOR_BACKUP_INTENT);
- if (status == STATUS_ACCESS_DENIED)
- {
- status = NtOpenFile (&h, access = MIN_STAT_ACCESS, &attr, &io,
- FILE_SHARE_VALID_FLAGS,
- FILE_OPEN_REPARSE_POINT
- | FILE_OPEN_FOR_BACKUP_INTENT);
- debug_printf ("%p = NtOpenFile (no-EAs 2:%S)", status, &upath);
- }
- else
- debug_printf ("%p = NtOpenFile (no-EA 1:%S)", status, &upath);
+ status = get_file_handle(&h, attr, upath, 0, &access, NULL, 0);
}
if (status == STATUS_OBJECT_NAME_NOT_FOUND)
{
@@ -2351,8 +2871,7 @@ restart:
fs.update (&upath, h);
if (!fs.is_udf ())
{
- NtClose (h);
- h = NULL;
+ zNtClose(&h);
status = STATUS_OBJECT_NAME_NOT_FOUND;
}
}
@@ -2411,9 +2930,9 @@ restart:
}
else
{
- PFILE_NETWORK_OPEN_INFORMATION pfnoi = conv_hdl.fnoi ();
+ PFILE_CYGWIN_INFORMATION pfci = conv_hdl.finfo ();


-	      status = NtQueryInformationFile (h, &io, pfnoi, sizeof *pfnoi,
+	      status = NtQueryInformationFile (h, &io, pfci, sizeof *pfci,
 					       FileNetworkOpenInformation);
 	      if ((status == STATUS_INVALID_PARAMETER
 		   || status == STATUS_NOT_IMPLEMENTED)
@@ -2428,14 +2947,16 @@ restart:
 						   FileBasicInformation);
 		  if (NT_SUCCESS (status))
 		    {
-		      memcpy (pfnoi, &fbi, 4 * sizeof (LARGE_INTEGER));
-		      pfnoi->EndOfFile.QuadPart
-			= pfnoi->AllocationSize.QuadPart = 0;
-		      pfnoi->FileAttributes = fbi.FileAttributes;
+		      memcpy (pfci, &fbi, 4 * sizeof (LARGE_INTEGER));
+		      pfci->EndOfFile.QuadPart
+			= pfci->AllocationSize.QuadPart = 0;
+		      pfci->FileAttributes = fbi.FileAttributes;
 		    }
 		}
+	      pfci->ReparseTag = 0;
+	      pfci->FileId.QuadPart = 0;
 	      if (NT_SUCCESS (status))
-		fileattr = pfnoi->FileAttributes;
+		fileattr = pfci->FileAttributes;
 	    }
 	}
       if (!NT_SUCCESS (status))
@@ -2525,18 +3046,22 @@ restart:
 		    }
 		  else
 		    {
-		      PFILE_NETWORK_OPEN_INFORMATION pfnoi = conv_hdl.fnoi ();
+		      PFILE_CYGWIN_INFORMATION pfci = conv_hdl.finfo ();

fileattr = fdi_buf.fdi.FileAttributes;
- memcpy (pfnoi, &fdi_buf.fdi.CreationTime, sizeof *pfnoi);
+ memcpy (pfci, &fdi_buf.fdi.CreationTime,
+ 4 * sizeof (LARGE_INTEGER));
/* Amazing, but true: The FILE_NETWORK_OPEN_INFORMATION
structure has the AllocationSize and EndOfFile members
interchanged relative to the directory information
classes. */
- pfnoi->AllocationSize.QuadPart
+ pfci->AllocationSize.QuadPart
= fdi_buf.fdi.AllocationSize.QuadPart;
- pfnoi->EndOfFile.QuadPart
+ pfci->EndOfFile.QuadPart
= fdi_buf.fdi.EndOfFile.QuadPart;
+ pfci->FileAttributes = fdi_buf.fdi.FileAttributes;
+ pfci->ReparseTag = fdi_buf.fdi.EaSize;
+ pfci->FileId.QuadPart = 0;
}
}
ext_tacked_on = !!*ext_here;
@@ -2565,6 +3090,8 @@ restart:
== FILE_ATTRIBUTE_READONLY && suffix.lnk_match ())
{
if (!(access & GENERIC_READ))
+ status = get_file_handle(&h, attr, upath, 1, &access, eabuf, easize);
+ if (!NT_SUCCESS(status) || !(access & GENERIC_READ))
res = 0;
else
res = check_shortcut (h);
@@ -2625,7 +3152,7 @@ restart:
else if (res)
{
/* A symlink is never a directory. */
- conv_hdl.fnoi ()->FileAttributes &= ~FILE_ATTRIBUTE_DIRECTORY;
+ conv_hdl.finfo ()->FileAttributes &= ~FILE_ATTRIBUTE_DIRECTORY;
break;
}
}
@@ -2634,9 +3161,13 @@ restart:
have the `system' file attribute. Only files can be symlinks
(which can be symlinks to directories). */
else if ((fileattr & (FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_DIRECTORY))
- == FILE_ATTRIBUTE_SYSTEM)
+ == FILE_ATTRIBUTE_SYSTEM &&
+ conv_hdl.finfo ()->EndOfFile.QuadPart > strlen("!<symlink>") &&
+ conv_hdl.finfo ()->EndOfFile.QuadPart < MAX_PATH)
{
if (!(access & GENERIC_READ))
+ status = get_file_handle(&h, attr, upath, 1, &access, eabuf, easize);
+ if (!NT_SUCCESS(status) || !(access & GENERIC_READ))
res = 0;
else
res = check_sysfile (h);
Index: cygwin/path.h
===================================================================
RCS file: /cvs/src/src/winsup/cygwin/path.h,v
retrieving revision 1.151
diff -u -p -r1.151 path.h
--- cygwin/path.h 5 Oct 2010 14:19:17 -0000 1.151
+++ cygwin/path.h 7 Oct 2010 12:12:53 -0000
@@ -59,6 +59,7 @@ enum pathconv_arg
PC_NULLEMPTY = 0x0020,
PC_POSIX = 0x0080,
PC_NOWARN = 0x0100,
+ PC_XCHECK = 0x00200000,
PC_KEEP_HANDLE = 0x00400000,
PC_NO_ACCESS_CHECK = 0x00800000
};
@@ -86,28 +87,31 @@ enum path_types
PATH_TEXT = 0x02000000,
PATH_REP = 0x04000000,
PATH_HAS_SYMLINKS = 0x10000000,
+ PATH_XCHECKED = 0x20000000,
PATH_SOCKET = 0x40000000
};


 class symlink_info;
-struct _FILE_NETWORK_OPEN_INFORMATION;
+
+typedef struct _FILE_CYGWIN_INFORMATION
+{
+  LARGE_INTEGER CreationTime;
+  LARGE_INTEGER LastAccessTime;
+  LARGE_INTEGER LastWriteTime;
+  LARGE_INTEGER ChangeTime;
+  LARGE_INTEGER AllocationSize;
+  LARGE_INTEGER EndOfFile;
+  ULONG FileAttributes;
+  ULONG ReparseTag;
+  LARGE_INTEGER FileId;
+} FILE_CYGWIN_INFORMATION, *PFILE_CYGWIN_INFORMATION;

class path_conv_handle
{
HANDLE hdl;
ACCESS_MASK acc;
union {
- /* Identical to FILE_NETWORK_OPEN_INFORMATION. We don't want to pull in
- ntdll.h here, though. */
- struct {
- LARGE_INTEGER CreationTime;
- LARGE_INTEGER LastAccessTime;
- LARGE_INTEGER LastWriteTime;
- LARGE_INTEGER ChangeTime;
- LARGE_INTEGER AllocationSize;
- LARGE_INTEGER EndOfFile;
- ULONG FileAttributes;
- } _fnoi;
+ FILE_CYGWIN_INFORMATION fci;
/* For NFS. */
fattr3 _fattr3;
} attribs;
@@ -132,8 +136,8 @@ public:
}
inline HANDLE handle () const { return hdl; }
inline ACCESS_MASK access () const { return acc; }
- inline struct _FILE_NETWORK_OPEN_INFORMATION *fnoi ()
- { return (struct _FILE_NETWORK_OPEN_INFORMATION *) &attribs._fnoi; }
+ inline PFILE_CYGWIN_INFORMATION finfo ()
+ { return (PFILE_CYGWIN_INFORMATION) &attribs.fci; }
inline struct fattr3 *nfsattr ()
{ return (struct fattr3 *) &attribs._fattr3; }
};
@@ -175,6 +179,7 @@ class path_conv
return O_TEXT;
return 0;
}
+ int xchecked () const { return path_flags & PATH_XCHECKED; }
int issymlink () const {return path_flags & PATH_SYMLINK;}
int is_lnk_symlink () const {return path_flags & PATH_LNK;}
int is_rep_symlink () const {return path_flags & PATH_REP;}
@@ -326,7 +331,7 @@ class path_conv


HANDLE handle () const { return conv_handle.handle (); }
ACCESS_MASK access () const { return conv_handle.access (); }
- struct _FILE_NETWORK_OPEN_INFORMATION *fnoi () { return conv_handle.fnoi (); }
+ PFILE_CYGWIN_INFORMATION finfo () { return conv_handle.finfo (); }
struct fattr3 *nfsattr () { return conv_handle.nfsattr (); }
void reset_conv_handle () { conv_handle.set (NULL, 0); }
void close_conv_handle () { conv_handle.close (); }



Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]