From 3a8d0cc825635e07da2a90c4ac987b476fc9b05d Mon Sep 17 00:00:00 2001 From: Paul Eggert Date: Mon, 31 Jul 2017 12:31:02 -0700 Subject: [PATCH] Avoid most stat calls when completing file names * admin/merge-gnulib (GNULIB_MODULES): Add d-type. * lib/gnulib.mk.in, m4/gnulib-comp.m4: Regenerate. * m4/d-type.m4: New file, copied from gnulib. * src/dired.c (DT_UNKNOWN, DT_DIR, DT_LINK) [!HAVE_STRUCT_DIRENT_D_TYPE]: New constants. (dirent_type): New function. (file_name_completion): Use it, to avoid unnecessary calls to stat-like functions on GNU/Linux and other platforms with d_type. (file_name_completion_stat): Just follow the link; there is no need to try first with AT_SYMLINK_NOFOLLOW since the directory entry was already checked to exist. --- admin/merge-gnulib | 3 +- lib/gnulib.mk.in | 2 +- m4/d-type.m4 | 32 +++++++++++++++++++++ m4/gnulib-comp.m4 | 3 ++ src/dired.c | 71 ++++++++++++++++++++++++++-------------------- 5 files changed, 79 insertions(+), 32 deletions(-) create mode 100644 m4/d-type.m4 diff --git a/admin/merge-gnulib b/admin/merge-gnulib index 18c9ee8def7..c23e8a40ea7 100755 --- a/admin/merge-gnulib +++ b/admin/merge-gnulib @@ -30,7 +30,8 @@ GNULIB_MODULES=' careadlinkat close-stream count-leading-zeros count-one-bits count-trailing-zeros crypto/md5 crypto/sha1 crypto/sha256 crypto/sha512 - diffseq dtoastr dtotimespec dup2 environ execinfo explicit_bzero faccessat + d-type diffseq dtoastr dtotimespec dup2 + environ execinfo explicit_bzero faccessat fcntl fcntl-h fdatasync fdopendir filemode filevercmp flexmember fstatat fsync getloadavg getopt-gnu gettime gettimeofday gitlog-to-changelog diff --git a/lib/gnulib.mk.in b/lib/gnulib.mk.in index 3e57391372a..11c1ecf05ad 100644 --- a/lib/gnulib.mk.in +++ b/lib/gnulib.mk.in @@ -21,7 +21,7 @@ # the same distribution terms as the rest of that program. # # Generated by gnulib-tool. -# Reproduce by: gnulib-tool --import --lib=libgnu --source-base=lib --m4-base=m4 --doc-base=doc --tests-base=tests --aux-dir=build-aux --avoid=close --avoid=dup --avoid=fchdir --avoid=fstat --avoid=malloc-posix --avoid=msvc-inval --avoid=msvc-nothrow --avoid=open --avoid=openat-die --avoid=opendir --avoid=raise --avoid=save-cwd --avoid=select --avoid=setenv --avoid=sigprocmask --avoid=stat --avoid=stdarg --avoid=stdbool --avoid=threadlib --avoid=tzset --avoid=unsetenv --avoid=utime --avoid=utime-h --gnu-make --makefile-name=gnulib.mk.in --conditional-dependencies --no-libtool --macro-prefix=gl --no-vc-files alloca-opt binary-io byteswap c-ctype c-strcase careadlinkat close-stream count-leading-zeros count-one-bits count-trailing-zeros crypto/md5 crypto/sha1 crypto/sha256 crypto/sha512 diffseq dtoastr dtotimespec dup2 environ execinfo explicit_bzero faccessat fcntl fcntl-h fdatasync fdopendir filemode filevercmp flexmember fstatat fsync getloadavg getopt-gnu gettime gettimeofday gitlog-to-changelog ignore-value intprops largefile lstat manywarnings memrchr minmax mkostemp mktime nstrftime pipe2 pselect pthread_sigmask putenv qcopy-acl readlink readlinkat sig2str socklen stat-time std-gnu11 stdalign stddef stdio stpcpy strtoimax symlink sys_stat sys_time time time_r time_rz timegm timer-time timespec-add timespec-sub unlocked-io update-copyright utimens vla warnings +# Reproduce by: gnulib-tool --import --lib=libgnu --source-base=lib --m4-base=m4 --doc-base=doc --tests-base=tests --aux-dir=build-aux --avoid=close --avoid=dup --avoid=fchdir --avoid=fstat --avoid=malloc-posix --avoid=msvc-inval --avoid=msvc-nothrow --avoid=open --avoid=openat-die --avoid=opendir --avoid=raise --avoid=save-cwd --avoid=select --avoid=setenv --avoid=sigprocmask --avoid=stat --avoid=stdarg --avoid=stdbool --avoid=threadlib --avoid=tzset --avoid=unsetenv --avoid=utime --avoid=utime-h --gnu-make --makefile-name=gnulib.mk.in --conditional-dependencies --no-libtool --macro-prefix=gl --no-vc-files alloca-opt binary-io byteswap c-ctype c-strcase careadlinkat close-stream count-leading-zeros count-one-bits count-trailing-zeros crypto/md5 crypto/sha1 crypto/sha256 crypto/sha512 d-type diffseq dtoastr dtotimespec dup2 environ execinfo explicit_bzero faccessat fcntl fcntl-h fdatasync fdopendir filemode filevercmp flexmember fstatat fsync getloadavg getopt-gnu gettime gettimeofday gitlog-to-changelog ignore-value intprops largefile lstat manywarnings memrchr minmax mkostemp mktime nstrftime pipe2 pselect pthread_sigmask putenv qcopy-acl readlink readlinkat sig2str socklen stat-time std-gnu11 stdalign stddef stdio stpcpy strtoimax symlink sys_stat sys_time time time_r time_rz timegm timer-time timespec-add timespec-sub unlocked-io update-copyright utimens vla warnings MOSTLYCLEANFILES += core *.stackdump diff --git a/m4/d-type.m4 b/m4/d-type.m4 new file mode 100644 index 00000000000..c819fc02f84 --- /dev/null +++ b/m4/d-type.m4 @@ -0,0 +1,32 @@ +# serial 12 + +dnl From Jim Meyering. +dnl +dnl Check whether struct dirent has a member named d_type. +dnl + +# Copyright (C) 1997, 1999-2004, 2006, 2009-2017 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +AC_DEFUN([gl_CHECK_TYPE_STRUCT_DIRENT_D_TYPE], + [AC_CACHE_CHECK([for d_type member in directory struct], + [gl_cv_struct_dirent_d_type], + [AC_LINK_IFELSE([AC_LANG_PROGRAM([[ +#include +#include + ]], + [[struct dirent dp; dp.d_type = 0;]])], + [gl_cv_struct_dirent_d_type=yes], + [gl_cv_struct_dirent_d_type=no]) + ] + ) + if test $gl_cv_struct_dirent_d_type = yes; then + AC_DEFINE([HAVE_STRUCT_DIRENT_D_TYPE], [1], + [Define if there is a member named d_type in the struct describing + directory headers.]) + fi + ] +) diff --git a/m4/gnulib-comp.m4 b/m4/gnulib-comp.m4 index 2f135773930..188c116c851 100644 --- a/m4/gnulib-comp.m4 +++ b/m4/gnulib-comp.m4 @@ -61,6 +61,7 @@ AC_DEFUN([gl_EARLY], # Code from module crypto/sha1: # Code from module crypto/sha256: # Code from module crypto/sha512: + # Code from module d-type: # Code from module diffseq: # Code from module dirent: # Code from module dirfd: @@ -199,6 +200,7 @@ AC_DEFUN([gl_INIT], gl_SHA1 gl_SHA256 gl_SHA512 + gl_CHECK_TYPE_STRUCT_DIRENT_D_TYPE gl_DIRENT_H AC_REQUIRE([gl_C99_STRTOLD]) gl_FUNC_DUP2 @@ -968,6 +970,7 @@ AC_DEFUN([gl_FILE_LIST], [ m4/count-leading-zeros.m4 m4/count-one-bits.m4 m4/count-trailing-zeros.m4 + m4/d-type.m4 m4/dirent_h.m4 m4/dirfd.m4 m4/dup2.m4 diff --git a/src/dired.c b/src/dired.c index 5ea00fb8db4..288ba6b1038 100644 --- a/src/dired.c +++ b/src/dired.c @@ -64,6 +64,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) { @@ -434,7 +449,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 +463,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 +526,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 +806,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 * -- 2.39.2