summaryrefslogtreecommitdiff
path: root/src/dired.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/dired.c')
-rw-r--r--src/dired.c166
1 files changed, 113 insertions, 53 deletions
diff --git a/src/dired.c b/src/dired.c
index 5ea00fb8db4..239b1acd1fb 100644
--- a/src/dired.c
+++ b/src/dired.c
@@ -15,7 +15,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
-along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. */
+along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
#include <config.h>
@@ -51,7 +51,8 @@ extern int is_slow_fs (const char *);
#endif
static ptrdiff_t scmp (const char *, const char *, ptrdiff_t);
-static Lisp_Object file_attributes (int, char const *, Lisp_Object);
+static Lisp_Object file_attributes (int, char const *, Lisp_Object,
+ Lisp_Object, Lisp_Object);
/* Return the number of bytes in DP's name. */
static ptrdiff_t
@@ -64,6 +65,21 @@ dirent_namelen (struct dirent *dp)
#endif
}
+#ifndef HAVE_STRUCT_DIRENT_D_TYPE
+enum { DT_UNKNOWN, DT_DIR, DT_LNK };
+#endif
+
+/* Return the file type of DP. */
+static int
+dirent_type (struct dirent *dp)
+{
+#ifdef HAVE_STRUCT_DIRENT_D_TYPE
+ return dp->d_type;
+#else
+ return DT_UNKNOWN;
+#endif
+}
+
static DIR *
open_directory (Lisp_Object dirname, int *fdp)
{
@@ -146,7 +162,7 @@ read_dirent (DIR *dir, Lisp_Object dirname)
/* Function shared by Fdirectory_files and Fdirectory_files_and_attributes.
If not ATTRS, return a list of directory filenames;
if ATTRS, return a list of directory filenames and their attributes.
- In the latter case, ID_FORMAT is passed to Ffile_attributes. */
+ In the latter case, pass ID_FORMAT to file_attributes. */
Lisp_Object
directory_files_internal (Lisp_Object directory, Lisp_Object full,
@@ -210,7 +226,7 @@ directory_files_internal (Lisp_Object directory, Lisp_Object full,
if (attrs)
{
/* Do this only once to avoid doing it (in w32.c:stat) for each
- file in the directory, when we call Ffile_attributes below. */
+ file in the directory, when we call file_attributes below. */
record_unwind_protect (directory_files_internal_w32_unwind,
Vw32_get_true_file_attributes);
w32_save = Vw32_get_true_file_attributes;
@@ -289,7 +305,7 @@ directory_files_internal (Lisp_Object directory, Lisp_Object full,
if (attrs)
{
Lisp_Object fileattrs
- = file_attributes (fd, dp->d_name, id_format);
+ = file_attributes (fd, dp->d_name, directory, name, id_format);
list = Fcons (Fcons (finalname, fileattrs), list);
}
else
@@ -336,7 +352,7 @@ If NOSORT is non-nil, the list is not sorted--its order is unpredictable.
return call5 (handler, Qdirectory_files, directory,
full, match, nosort);
- return directory_files_internal (directory, full, match, nosort, 0, Qnil);
+ return directory_files_internal (directory, full, match, nosort, false, Qnil);
}
DEFUN ("directory-files-and-attributes", Fdirectory_files_and_attributes,
@@ -364,7 +380,8 @@ which see. */)
return call6 (handler, Qdirectory_files_and_attributes,
directory, full, match, nosort, id_format);
- return directory_files_internal (directory, full, match, nosort, 1, id_format);
+ return directory_files_internal (directory, full, match, nosort,
+ true, id_format);
}
@@ -434,7 +451,7 @@ is matched against file and directory names relative to DIRECTORY. */)
return file_name_completion (file, directory, 1, Qnil);
}
-static int file_name_completion_stat (int, struct dirent *, struct stat *);
+static bool file_name_completion_dirp (int, struct dirent *, ptrdiff_t);
static Lisp_Object
file_name_completion (Lisp_Object file, Lisp_Object dirname, bool all_flag,
@@ -448,7 +465,6 @@ file_name_completion (Lisp_Object file, Lisp_Object dirname, bool all_flag,
Lisp_Object bestmatch, tem, elt, name;
Lisp_Object encoded_file;
Lisp_Object encoded_dir;
- struct stat st;
bool directoryp;
/* If not INCLUDEALL, exclude files in completion-ignored-extensions as
well as "." and "..". Until shown otherwise, assume we can't exclude
@@ -512,10 +528,21 @@ file_name_completion (Lisp_Object file, Lisp_Object dirname, bool all_flag,
>= 0))
continue;
- if (file_name_completion_stat (fd, dp, &st) < 0)
- continue;
+ switch (dirent_type (dp))
+ {
+ case DT_DIR:
+ directoryp = true;
+ break;
+
+ case DT_LNK: case DT_UNKNOWN:
+ directoryp = file_name_completion_dirp (fd, dp, len);
+ break;
+
+ default:
+ directoryp = false;
+ break;
+ }
- directoryp = S_ISDIR (st.st_mode) != 0;
tem = Qnil;
/* If all_flag is set, always include all.
It would not actually be helpful to the user to ignore any possible
@@ -781,32 +808,18 @@ scmp (const char *s1, const char *s2, ptrdiff_t len)
return len - l;
}
-static int
-file_name_completion_stat (int fd, struct dirent *dp, struct stat *st_addr)
+/* Return true if in the directory FD the directory entry DP, whose
+ string length is LEN, is that of a subdirectory that can be searched. */
+static bool
+file_name_completion_dirp (int fd, struct dirent *dp, ptrdiff_t len)
{
- int value;
-
-#ifdef MSDOS
- /* Some fields of struct stat are *very* expensive to compute on MS-DOS,
- but aren't required here. Avoid computing the following fields:
- st_inode, st_size and st_nlink for directories, and the execute bits
- in st_mode for non-directory files with non-standard extensions. */
-
- unsigned short save_djstat_flags = _djstat_flags;
-
- _djstat_flags = _STAT_INODE | _STAT_EXEC_MAGIC | _STAT_DIRSIZE;
-#endif /* MSDOS */
-
- /* We want to return success if a link points to a nonexistent file,
- but we want to return the status for what the link points to,
- in case it is a directory. */
- value = fstatat (fd, dp->d_name, st_addr, AT_SYMLINK_NOFOLLOW);
- if (value == 0 && S_ISLNK (st_addr->st_mode))
- fstatat (fd, dp->d_name, st_addr, 0);
-#ifdef MSDOS
- _djstat_flags = save_djstat_flags;
-#endif /* MSDOS */
- return value;
+ USE_SAFE_ALLOCA;
+ char *subdir_name = SAFE_ALLOCA (len + 2);
+ memcpy (subdir_name, dp->d_name, len);
+ strcpy (subdir_name + len, "/");
+ bool dirp = faccessat (fd, subdir_name, F_OK, AT_EACCESS) == 0;
+ SAFE_FREE ();
+ return dirp;
}
static char *
@@ -912,14 +925,17 @@ so last access time will always be midnight of that day. */)
}
encoded = ENCODE_FILE (filename);
- return file_attributes (AT_FDCWD, SSDATA (encoded), id_format);
+ return file_attributes (AT_FDCWD, SSDATA (encoded), Qnil, filename,
+ id_format);
}
static Lisp_Object
-file_attributes (int fd, char const *name, Lisp_Object id_format)
+file_attributes (int fd, char const *name,
+ Lisp_Object dirname, Lisp_Object filename,
+ Lisp_Object id_format)
{
+ ptrdiff_t count = SPECPDL_INDEX ();
struct stat s;
- int lstat_result;
/* An array to hold the mode string generated by filemodestring,
including its terminating space and null byte. */
@@ -927,22 +943,67 @@ file_attributes (int fd, char const *name, Lisp_Object id_format)
char *uname = NULL, *gname = NULL;
-#ifdef WINDOWSNT
- /* We usually don't request accurate owner and group info, because
- it can be very expensive on Windows to get that, and most callers
- of 'lstat' don't need that. But here we do want that information
- to be accurate. */
- w32_stat_get_owner_group = 1;
-#endif
+ int err = EINVAL;
- lstat_result = fstatat (fd, name, &s, AT_SYMLINK_NOFOLLOW);
+#ifdef O_PATH
+ int namefd = openat (fd, name, O_PATH | O_CLOEXEC | O_NOFOLLOW);
+ if (namefd < 0)
+ err = errno;
+ else
+ {
+ record_unwind_protect_int (close_file_unwind, namefd);
+ if (fstat (namefd, &s) != 0)
+ {
+ err = errno;
+ /* The Linux kernel before version 3.6 does not support
+ fstat on O_PATH file descriptors. Handle this error like
+ missing support for O_PATH. */
+ if (err == EBADF)
+ err = EINVAL;
+ }
+ else
+ {
+ err = 0;
+ fd = namefd;
+ name = "";
+ }
+ }
+#endif
+ if (err == EINVAL)
+ {
#ifdef WINDOWSNT
- w32_stat_get_owner_group = 0;
+ /* We usually don't request accurate owner and group info,
+ because it can be expensive on Windows to get that, and most
+ callers of 'lstat' don't need that. But here we do want that
+ information to be accurate. */
+ w32_stat_get_owner_group = 1;
#endif
+ if (fstatat (fd, name, &s, AT_SYMLINK_NOFOLLOW) == 0)
+ err = 0;
+#ifdef WINDOWSNT
+ w32_stat_get_owner_group = 0;
+#endif
+ }
- if (lstat_result < 0)
- return Qnil;
+ if (err != 0)
+ return unbind_to (count, Qnil);
+
+ Lisp_Object file_type;
+ if (S_ISLNK (s.st_mode))
+ {
+ /* On systems lacking O_PATH support there is a race if the
+ symlink is replaced between the call to fstatat and the call
+ to emacs_readlinkat. Detect this race unless the replacement
+ is also a symlink. */
+ file_type = emacs_readlinkat (fd, name);
+ if (NILP (file_type))
+ return unbind_to (count, Qnil);
+ }
+ else
+ file_type = S_ISDIR (s.st_mode) ? Qt : Qnil;
+
+ unbind_to (count, Qnil);
if (!(NILP (id_format) || EQ (id_format, Qinteger)))
{
@@ -953,8 +1014,7 @@ file_attributes (int fd, char const *name, Lisp_Object id_format)
filemodestring (&s, modes);
return CALLN (Flist,
- (S_ISLNK (s.st_mode) ? emacs_readlinkat (fd, name)
- : S_ISDIR (s.st_mode) ? Qt : Qnil),
+ file_type,
make_number (s.st_nlink),
(uname
? DECODE_SYSTEM (build_unibyte_string (uname))