Cygwin Filesystem Performance degradation 1.7.5 vs 1.7.7, and methods for improving performance
Yoni Londner
yonihola2@gmail.com
Thu Oct 7 06:52:00 GMT 2010
Hi,
Attached is a patch on symlink_info::check.
I changed symlink_info::check so it will create a file handle with
GENERIC_READ permissions only when it needs it. in all other cases it
opens it with READ_CONTROL | FILE_READ_ATTRIBUTES.
This improved the performance of stat significally, without changing the
behaviour.
FROM:
/bin$ time ls -l > /dev/null
real 0m3.875s
user 0m0.108s
sys 0m0.452s
TO:
/bin$ time ls -l > /dev/null
real 0m0.562s
user 0m0.171s
sys 0m0.296s
Yoni.
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 06:31:06 -0000
@@ -2216,6 +2216,45 @@ symlink_info::parse_device (const char *
Return -1 on error, 0 if PATH is not a symlink, or the length
stored into BUF if PATH is a symlink. */
+# 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;
+ /* 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 +2296,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;
@@ -2273,28 +2310,9 @@ restart:
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 +2332,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)
{
@@ -2565,6 +2570,12 @@ restart:
== FILE_ATTRIBUTE_READONLY && suffix.lnk_match ())
{
if (!(access & GENERIC_READ))
+ {
+ NtClose(h);
+ 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);
@@ -2637,6 +2648,12 @@ restart:
== FILE_ATTRIBUTE_SYSTEM)
{
if (!(access & GENERIC_READ))
+ {
+ NtClose(h);
+ 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);
On 6/10/2010 6:47 PM, Corinna Vinschen wrote:
> On Oct 6 18:35, Corinna Vinschen wrote:
>> On Oct 6 14:37, Yoni Londner wrote:
>>> The difference in huge. while opening the file with GENERIC_READ
>>> took almost 1ms per file, opening with FILE_READ_ATTRIBUTES only
>>> took 0.027ms per file:
>>> testing /bin QIFR 3588 files stat() 3312ms, per file: 0.9229ms
>>> testing /bin QIF 3588 files stat() 97.66ms, per file: 0.02722ms
>>
>> I can't reproduce any noticable difference:
>>
>> 0.2093ms<= QIF<= 0.3153ms
>> 0.2112ms<= QIFR<= 0.3306ms
>>
>>> I will start working on a patch doing this, and hope to post it soon.
>>
>> Did anybody of you notice the license problem which has been mentioned at
>> leats three times now? Please read http://cygwin.com/contrib.html.
>>
>> If it's really as easy as dropping the GENERIC_READ, then I can do that
>> in symlink_info::check easily. That's basically what is different
>> between 1.7.5 and 1.7.7. "It seemed to be a good idea at the time".
>>
>>> Currently we have nothing to do with the driver, but maybe in the
>>> future we will find better ways to improve performance, that will
>>> require a kernel driver.
>>
>> I'd prefer to stick to user space.
>
> Attached is a preliminary version of the new symlink_info::xcheck
> function, which uses NtQueryDirectoryFile. First tests show that
> `ls -l' is about three times faster than when using symlink_info::check.
> To activate the function you can use the "CYGWIN=xcheck" setting.
> Of course you will not get correct ACLs and a wrong st_nlink but
> that has been discussed to death already.
>
> The buzzword here is "preliminary". I think I don't like the "xcheck"
> name for the function and the flags, nor do I like the CYGWIN=xcheck
> setting at all. However, since I'm mostely offline the next couple of
> weeks, there's at least something to play with.
>
>
> Corinna
>
>
>
> Index: environ.cc
> ===================================================================
> RCS file: /cvs/src/src/winsup/cygwin/environ.cc,v
> retrieving revision 1.183
> diff -u -p -r1.183 environ.cc
> --- environ.cc 18 May 2010 14:30:50 -0000 1.183
> +++ environ.cc 5 Oct 2010 16:41:14 -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: 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
> --- fhandler_disk_file.cc 2 Oct 2010 19:03:44 -0000 1.344
> +++ fhandler_disk_file.cc 5 Oct 2010 16:41:14 -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: ntdll.h
> ===================================================================
> RCS file: /cvs/src/src/winsup/cygwin/ntdll.h,v
> retrieving revision 1.104
> diff -u -p -r1.104 ntdll.h
> --- ntdll.h 24 Sep 2010 12:41:33 -0000 1.104
> +++ ntdll.h 5 Oct 2010 16:41:14 -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: path.cc
> ===================================================================
> RCS file: /cvs/src/src/winsup/cygwin/path.cc,v
> retrieving revision 1.615
> diff -u -p -r1.615 path.cc
> --- path.cc 2 Oct 2010 19:03:44 -0000 1.615
> +++ path.cc 5 Oct 2010 16:41:15 -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,325 @@ 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;
> +}
> +
> +int
> symlink_info::check (char *path, const suffix_info *suffixes, fs_info&fs,
> path_conv_handle&conv_hdl)
> {
> @@ -2411,9 +2923,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 +2940,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 +3039,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;
> @@ -2625,7 +3143,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;
> }
> }
> Index: path.h
> ===================================================================
> RCS file: /cvs/src/src/winsup/cygwin/path.h,v
> retrieving revision 1.151
> diff -u -p -r1.151 path.h
> --- path.h 5 Oct 2010 14:19:17 -0000 1.151
> +++ path.h 5 Oct 2010 16:41:15 -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 (); }
>
More information about the Cygwin-developers
mailing list