From 697be62c5f2b86e8ad93dfcaa0df07890c24d989 Mon Sep 17 00:00:00 2001 From: Eli Zaretskii Date: Mon, 31 Aug 2015 17:48:26 +0300 Subject: [PATCH] Make file-accessible-directory-p reliable on MS-Windows * src/w32.c (w32_accessible_directory_p): New function. * src/w32.h (w32_accessible_directory_p): Add prototype. * src/fileio.c (file_accessible_directory_p) [WINDOWSNT]: Call w32_accessible_directory_p to test a directory for accessibility by the current user. (Bug#21346) (Ffile_accessible_directory_p): Remove the w32 specific caveat from the doc string. --- src/fileio.c | 22 +++++++++++++--------- src/w32.c | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/w32.h | 1 + 3 files changed, 65 insertions(+), 9 deletions(-) diff --git a/src/fileio.c b/src/fileio.c index debd1f30a4f..a36dfbcfa36 100644 --- a/src/fileio.c +++ b/src/fileio.c @@ -2655,11 +2655,7 @@ and the directory must allow you to open files in it. In order to use a directory as a buffer's current directory, this predicate must return true. A directory name spec may be given instead; then the value is t if the directory so specified exists and really is a readable and -searchable directory. - -The result might be a false positive on MS-Windows in some rare cases, -i.e., this function could return t for a directory that is not -accessible by the current user. */) +searchable directory. */) (Lisp_Object filename) { Lisp_Object absname; @@ -2689,10 +2685,18 @@ bool file_accessible_directory_p (Lisp_Object file) { #ifdef DOS_NT - /* There's no need to test whether FILE is searchable, as the - searchable/executable bit is invented on DOS_NT platforms. */ +# ifdef WINDOWSNT + /* We need a special-purpose test because (a) NTFS security data is + not reflected in Posix-style mode bits, and (b) the trick with + accessing "DIR/.", used below on Posix hosts, doesn't work on + Windows, because "DIR/." is normalized to just "DIR" before + hitting the disk. */ + return (SBYTES (file) == 0 + || w32_accessible_directory_p (SSDATA (file), SBYTES (file))); +# else /* MSDOS */ return file_directory_p (SSDATA (file)); -#else +# endif /* MSDOS */ +#else /* !DOS_NT */ /* On POSIXish platforms, use just one system call; this avoids a race and is typically faster. */ const char *data = SSDATA (file); @@ -2725,7 +2729,7 @@ file_accessible_directory_p (Lisp_Object file) SAFE_FREE (); errno = saved_errno; return ok; -#endif +#endif /* !DOS_NT */ } DEFUN ("file-regular-p", Ffile_regular_p, Sfile_regular_p, 1, 1, 0, diff --git a/src/w32.c b/src/w32.c index b421667e241..60fbe92e082 100644 --- a/src/w32.c +++ b/src/w32.c @@ -3847,6 +3847,57 @@ faccessat (int dirfd, const char * path, int mode, int flags) return 0; } +/* A special test for DIRNAME being a directory accessible by the + current user. This is needed because the security permissions in + directory's ACLs are not visible in the Posix-style mode bits + returned by 'stat' and in attributes returned by GetFileAttributes. + So a directory would seem like it's readable by the current user, + but will in fact error out with EACCES when they actually try. */ +int +w32_accessible_directory_p (const char *dirname, ptrdiff_t dirlen) +{ + char pattern[MAX_UTF8_PATH]; + bool last_slash = dirlen > 0 && IS_DIRECTORY_SEP (dirname[dirlen - 1]); + HANDLE dh; + + strcpy (pattern, map_w32_filename (dirname, NULL)); + + /* Note: No need to resolve symlinks in FILENAME, because FindFirst + opens the directory that is the target of a symlink. */ + if (w32_unicode_filenames) + { + wchar_t pat_w[MAX_PATH + 2]; + WIN32_FIND_DATAW dfd_w; + + filename_to_utf16 (pattern, pat_w); + if (!last_slash) + wcscat (pat_w, L"\\"); + wcscat (pat_w, L"*"); + dh = FindFirstFileW (pat_w, &dfd_w); + } + else + { + char pat_a[MAX_PATH + 2]; + WIN32_FIND_DATAA dfd_a; + + filename_to_ansi (pattern, pat_a); + if (!last_slash) + strcpy (pat_a, "\\"); + strcat (pat_a, "*"); + /* In case DIRNAME cannot be expressed in characters from the + current ANSI codepage. */ + if (_mbspbrk (pat_a, "?")) + dh = INVALID_HANDLE_VALUE; + else + dh = FindFirstFileA (pat_a, &dfd_a); + } + + if (dh == INVALID_HANDLE_VALUE) + return 0; + FindClose (dh); + return 1; +} + /* A version of 'access' to be used locally with file names in locale-specific encoding. Does not resolve symlinks and does not support file names on FAT12 and FAT16 volumes, but that's OK, since diff --git a/src/w32.h b/src/w32.h index 338cb06b193..2c711502593 100644 --- a/src/w32.h +++ b/src/w32.h @@ -195,6 +195,7 @@ extern int filename_to_utf16 (const char *, wchar_t *); extern int codepage_for_filenames (CPINFO *); extern Lisp_Object ansi_encode_filename (Lisp_Object); extern int w32_copy_file (const char *, const char *, int, int, int); +extern int w32_accessible_directory_p (const char *, ptrdiff_t); extern BOOL init_winsock (int load_now); extern void srandom (int); -- 2.11.4.GIT