]> code.delx.au - gnu-emacs/blobdiff - src/dired.c
Add ifdef NS_IMPL_COCOA aound OSX version check (for clang)
[gnu-emacs] / src / dired.c
index 38dfa23c1bf1d8e31e57af04ef38ea7f1c4c931a..d3fe5b4943de727b5a922cb306a42baf54458fb3 100644 (file)
@@ -1,5 +1,6 @@
 /* Lisp functions for making directory listings.
 /* Lisp functions for making directory listings.
-   Copyright (C) 1985-1986, 1993-1994, 1999-2012 Free Software Foundation, Inc.
+   Copyright (C) 1985-1986, 1993-1994, 1999-2014 Free Software
+   Foundation, Inc.
 
 This file is part of GNU Emacs.
 
 
 This file is part of GNU Emacs.
 
@@ -22,7 +23,6 @@ along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.  */
 #include <stdio.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <stdio.h>
 #include <sys/types.h>
 #include <sys/stat.h>
-#include <setjmp.h>
 
 #ifdef HAVE_PWD_H
 #include <pwd.h>
 
 #ifdef HAVE_PWD_H
 #include <pwd.h>
@@ -30,55 +30,27 @@ along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.  */
 #include <grp.h>
 
 #include <errno.h>
 #include <grp.h>
 
 #include <errno.h>
+#include <fcntl.h>
 #include <unistd.h>
 
 #include <unistd.h>
 
-/* The d_nameln member of a struct dirent includes the '\0' character
-   on some systems, but not on others.  What's worse, you can't tell
-   at compile-time which one it will be, since it really depends on
-   the sort of system providing the filesystem you're reading from,
-   not the system you are running on.  Paul Eggert
-   <eggert@bi.twinsun.com> says this occurs when Emacs is running on a
-   SunOS 4.1.2 host, reading a directory that is remote-mounted from a
-   Solaris 2.1 host and is in a native Solaris 2.1 filesystem.
-
-   Since applying strlen to the name always works, we'll just do that.  */
-#define NAMLEN(p) strlen (p->d_name)
-
-#ifdef HAVE_DIRENT_H
-
 #include <dirent.h>
 #include <dirent.h>
-#define DIRENTRY struct dirent
-
-#else /* not HAVE_DIRENT_H */
-
-#include <sys/dir.h>
-#include <sys/stat.h>
-
-#define DIRENTRY struct direct
-
-extern DIR *opendir (char *);
-extern struct direct *readdir (DIR *);
-
-#endif /* HAVE_DIRENT_H */
-
 #include <filemode.h>
 #include <filemode.h>
-
-#ifdef MSDOS
-#define DIRENTRY_NONEMPTY(p) ((p)->d_name[0] != 0)
-#else
-#define DIRENTRY_NONEMPTY(p) ((p)->d_ino)
-#endif
+#include <stat-time.h>
 
 #include "lisp.h"
 #include "systime.h"
 
 #include "lisp.h"
 #include "systime.h"
+#include "character.h"
 #include "buffer.h"
 #include "commands.h"
 #include "buffer.h"
 #include "commands.h"
-#include "character.h"
 #include "charset.h"
 #include "coding.h"
 #include "regex.h"
 #include "blockinput.h"
 
 #include "charset.h"
 #include "coding.h"
 #include "regex.h"
 #include "blockinput.h"
 
+#ifdef MSDOS
+#include "msdos.h"     /* for fstatat */
+#endif
+
 static Lisp_Object Qdirectory_files;
 static Lisp_Object Qdirectory_files_and_attributes;
 static Lisp_Object Qfile_name_completion;
 static Lisp_Object Qdirectory_files;
 static Lisp_Object Qdirectory_files_and_attributes;
 static Lisp_Object Qfile_name_completion;
@@ -87,43 +59,93 @@ static Lisp_Object Qfile_attributes;
 static Lisp_Object Qfile_attributes_lessp;
 
 static ptrdiff_t scmp (const char *, const char *, ptrdiff_t);
 static Lisp_Object Qfile_attributes_lessp;
 
 static ptrdiff_t scmp (const char *, const char *, ptrdiff_t);
-static Lisp_Object Ffile_attributes (Lisp_Object, Lisp_Object);
+static Lisp_Object file_attributes (int, char const *, Lisp_Object);
 \f
 \f
+/* Return the number of bytes in DP's name.  */
+static ptrdiff_t
+dirent_namelen (struct dirent *dp)
+{
+#ifdef _D_EXACT_NAMLEN
+  return _D_EXACT_NAMLEN (dp);
+#else
+  return strlen (dp->d_name);
+#endif
+}
+
+static DIR *
+open_directory (char const *name, int *fdp)
+{
+  DIR *d;
+  int fd, opendir_errno;
+
+  block_input ();
+
+#ifdef DOS_NT
+  /* Directories cannot be opened.  The emulation assumes that any
+     file descriptor other than AT_FDCWD corresponds to the most
+     recently opened directory.  This hack is good enough for Emacs.  */
+  fd = 0;
+  d = opendir (name);
+  opendir_errno = errno;
+#else
+  fd = emacs_open (name, O_RDONLY | O_DIRECTORY, 0);
+  if (fd < 0)
+    {
+      opendir_errno = errno;
+      d = 0;
+    }
+  else
+    {
+      d = fdopendir (fd);
+      opendir_errno = errno;
+      if (! d)
+       emacs_close (fd);
+    }
+#endif
+
+  unblock_input ();
+
+  *fdp = fd;
+  errno = opendir_errno;
+  return d;
+}
+
 #ifdef WINDOWSNT
 #ifdef WINDOWSNT
-Lisp_Object
+void
 directory_files_internal_w32_unwind (Lisp_Object arg)
 {
   Vw32_get_true_file_attributes = arg;
 directory_files_internal_w32_unwind (Lisp_Object arg)
 {
   Vw32_get_true_file_attributes = arg;
-  return Qnil;
 }
 #endif
 
 }
 #endif
 
-static Lisp_Object
-directory_files_internal_unwind (Lisp_Object dh)
+static void
+directory_files_internal_unwind (void *dh)
 {
 {
-  DIR *d = (DIR *) XSAVE_VALUE (dh)->pointer;
-  BLOCK_INPUT;
+  DIR *d = dh;
+  block_input ();
   closedir (d);
   closedir (d);
-  UNBLOCK_INPUT;
-  return Qnil;
+  unblock_input ();
 }
 
 /* Function shared by Fdirectory_files and Fdirectory_files_and_attributes.
 }
 
 /* Function shared by Fdirectory_files and Fdirectory_files_and_attributes.
-   When ATTRS is zero, return a list of directory filenames; when
-   non-zero, return a list of directory filenames and their 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.  */
 
 Lisp_Object
    In the latter case, ID_FORMAT is passed to Ffile_attributes.  */
 
 Lisp_Object
-directory_files_internal (Lisp_Object directory, Lisp_Object full, Lisp_Object match, Lisp_Object nosort, int attrs, Lisp_Object id_format)
+directory_files_internal (Lisp_Object directory, Lisp_Object full,
+                         Lisp_Object match, Lisp_Object nosort, bool attrs,
+                         Lisp_Object id_format)
 {
   DIR *d;
 {
   DIR *d;
+  int fd;
   ptrdiff_t directory_nbytes;
   Lisp_Object list, dirfilename, encoded_directory;
   struct re_pattern_buffer *bufp = NULL;
   ptrdiff_t directory_nbytes;
   Lisp_Object list, dirfilename, encoded_directory;
   struct re_pattern_buffer *bufp = NULL;
-  int needsep = 0;
+  bool needsep = 0;
   ptrdiff_t count = SPECPDL_INDEX ();
   struct gcpro gcpro1, gcpro2, gcpro3, gcpro4, gcpro5;
   ptrdiff_t count = SPECPDL_INDEX ();
   struct gcpro gcpro1, gcpro2, gcpro3, gcpro4, gcpro5;
-  DIRENTRY *dp;
+  struct dirent *dp;
 #ifdef WINDOWSNT
   Lisp_Object w32_save = Qnil;
 #endif
 #ifdef WINDOWSNT
   Lisp_Object w32_save = Qnil;
 #endif
@@ -163,17 +185,14 @@ directory_files_internal (Lisp_Object directory, Lisp_Object full, Lisp_Object m
   /* Now *bufp is the compiled form of MATCH; don't call anything
      which might compile a new regexp until we're done with the loop!  */
 
   /* Now *bufp is the compiled form of MATCH; don't call anything
      which might compile a new regexp until we're done with the loop!  */
 
-  BLOCK_INPUT;
-  d = opendir (SSDATA (dirfilename));
-  UNBLOCK_INPUT;
+  d = open_directory (SSDATA (dirfilename), &fd);
   if (d == NULL)
   if (d == NULL)
-    report_file_error ("Opening directory", Fcons (directory, Qnil));
+    report_file_error ("Opening directory", directory);
 
   /* Unfortunately, we can now invoke expand-file-name and
      file-attributes on filenames, both of which can throw, so we must
      do a proper unwind-protect.  */
 
   /* Unfortunately, we can now invoke expand-file-name and
      file-attributes on filenames, both of which can throw, so we must
      do a proper unwind-protect.  */
-  record_unwind_protect (directory_files_internal_unwind,
-                        make_save_value (d, 0));
+  record_unwind_protect_ptr (directory_files_internal_unwind, d);
 
 #ifdef WINDOWSNT
   if (attrs)
 
 #ifdef WINDOWSNT
   if (attrs)
@@ -208,110 +227,92 @@ directory_files_internal (Lisp_Object directory, Lisp_Object full, Lisp_Object m
   /* Loop reading blocks until EOF or error.  */
   for (;;)
     {
   /* Loop reading blocks until EOF or error.  */
   for (;;)
     {
+      ptrdiff_t len;
+      bool wanted = 0;
+      Lisp_Object name, finalname;
+      struct gcpro gcpro1, gcpro2;
+
       errno = 0;
       dp = readdir (d);
       errno = 0;
       dp = readdir (d);
+      if (!dp)
+       {
+         if (errno == EAGAIN || errno == EINTR)
+           {
+             QUIT;
+             continue;
+           }
+         break;
+       }
 
 
-      if (dp == NULL && (0
-#ifdef EAGAIN
-                        || errno == EAGAIN
-#endif
-#ifdef EINTR
-                        || errno == EINTR
-#endif
-                        ))
-       { QUIT; continue; }
+      len = dirent_namelen (dp);
+      name = finalname = make_unibyte_string (dp->d_name, len);
+      GCPRO2 (finalname, name);
 
 
-      if (dp == NULL)
-       break;
+      /* Note: DECODE_FILE can GC; it should protect its argument,
+        though.  */
+      name = DECODE_FILE (name);
+      len = SBYTES (name);
+
+      /* Now that we have unwind_protect in place, we might as well
+        allow matching to be interrupted.  */
+      immediate_quit = 1;
+      QUIT;
+
+      if (NILP (match)
+         || re_search (bufp, SSDATA (name), len, 0, len, 0) >= 0)
+       wanted = 1;
 
 
-      if (DIRENTRY_NONEMPTY (dp))
+      immediate_quit = 0;
+
+      if (wanted)
        {
        {
-         ptrdiff_t len;
-         int wanted = 0;
-         Lisp_Object name, finalname;
-         struct gcpro gcpro1, gcpro2;
+         if (!NILP (full))
+           {
+             Lisp_Object fullname;
+             ptrdiff_t nbytes = len + directory_nbytes + needsep;
+             ptrdiff_t nchars;
 
 
-         len = NAMLEN (dp);
-         name = finalname = make_unibyte_string (dp->d_name, len);
-         GCPRO2 (finalname, name);
+             fullname = make_uninit_multibyte_string (nbytes, nbytes);
+             memcpy (SDATA (fullname), SDATA (directory),
+                     directory_nbytes);
 
 
-         /* Note: DECODE_FILE can GC; it should protect its argument,
-            though.  */
-         name = DECODE_FILE (name);
-         len = SBYTES (name);
+             if (needsep)
+               SSET (fullname, directory_nbytes, DIRECTORY_SEP);
 
 
-         /* Now that we have unwind_protect in place, we might as well
-             allow matching to be interrupted.  */
-         immediate_quit = 1;
-         QUIT;
+             memcpy (SDATA (fullname) + directory_nbytes + needsep,
+                     SDATA (name), len);
 
 
-         if (NILP (match)
-             || (0 <= re_search (bufp, SSDATA (name), len, 0, len, 0)))
-           wanted = 1;
+             nchars = multibyte_chars_in_text (SDATA (fullname), nbytes);
 
 
-         immediate_quit = 0;
+             /* Some bug somewhere.  */
+             if (nchars > nbytes)
+               emacs_abort ();
 
 
-         if (wanted)
-           {
-             if (!NILP (full))
-               {
-                 Lisp_Object fullname;
-                 ptrdiff_t nbytes = len + directory_nbytes + needsep;
-                 ptrdiff_t nchars;
-
-                 fullname = make_uninit_multibyte_string (nbytes, nbytes);
-                 memcpy (SDATA (fullname), SDATA (directory),
-                         directory_nbytes);
-
-                 if (needsep)
-                   SSET (fullname, directory_nbytes, DIRECTORY_SEP);
-
-                 memcpy (SDATA (fullname) + directory_nbytes + needsep,
-                         SDATA (name), len);
-
-                 nchars = chars_in_text (SDATA (fullname), nbytes);
-
-                 /* Some bug somewhere.  */
-                 if (nchars > nbytes)
-                   abort ();
-
-                 STRING_SET_CHARS (fullname, nchars);
-                 if (nchars == nbytes)
-                   STRING_SET_UNIBYTE (fullname);
-
-                 finalname = fullname;
-               }
-             else
-               finalname = name;
-
-             if (attrs)
-               {
-                 /* Construct an expanded filename for the directory entry.
-                    Use the decoded names for input to Ffile_attributes.  */
-                 Lisp_Object decoded_fullname, fileattrs;
-                 struct gcpro gcpro1, gcpro2;
-
-                 decoded_fullname = fileattrs = Qnil;
-                 GCPRO2 (decoded_fullname, fileattrs);
-
-                 /* Both Fexpand_file_name and Ffile_attributes can GC.  */
-                 decoded_fullname = Fexpand_file_name (name, directory);
-                 fileattrs = Ffile_attributes (decoded_fullname, id_format);
-
-                 list = Fcons (Fcons (finalname, fileattrs), list);
-                 UNGCPRO;
-               }
-             else
-               list = Fcons (finalname, list);
+             STRING_SET_CHARS (fullname, nchars);
+             if (nchars == nbytes)
+               STRING_SET_UNIBYTE (fullname);
+
+             finalname = fullname;
            }
            }
+         else
+           finalname = name;
 
 
-         UNGCPRO;
+         if (attrs)
+           {
+             Lisp_Object fileattrs
+               = file_attributes (fd, dp->d_name, id_format);
+             list = Fcons (Fcons (finalname, fileattrs), list);
+           }
+         else
+           list = Fcons (finalname, list);
        }
        }
+
+      UNGCPRO;
     }
 
     }
 
-  BLOCK_INPUT;
+  block_input ();
   closedir (d);
   closedir (d);
-  UNBLOCK_INPUT;
+  unblock_input ();
 #ifdef WINDOWSNT
   if (attrs)
     Vw32_get_true_file_attributes = w32_save;
 #ifdef WINDOWSNT
   if (attrs)
     Vw32_get_true_file_attributes = w32_save;
@@ -381,9 +382,8 @@ which see.  */)
 }
 
 \f
 }
 
 \f
-static Lisp_Object file_name_completion
-  (Lisp_Object file, Lisp_Object dirname, int all_flag, int ver_flag,
-   Lisp_Object predicate);
+static Lisp_Object file_name_completion (Lisp_Object, Lisp_Object, bool,
+                                        Lisp_Object);
 
 DEFUN ("file-name-completion", Ffile_name_completion, Sfile_name_completion,
        2, 3, 0,
 
 DEFUN ("file-name-completion", Ffile_name_completion, Sfile_name_completion,
        2, 3, 0,
@@ -415,7 +415,7 @@ determined by the variable `completion-ignored-extensions', which see.  */)
   if (!NILP (handler))
     return call4 (handler, Qfile_name_completion, file, directory, predicate);
 
   if (!NILP (handler))
     return call4 (handler, Qfile_name_completion, file, directory, predicate);
 
-  return file_name_completion (file, directory, 0, 0, predicate);
+  return file_name_completion (file, directory, 0, predicate);
 }
 
 DEFUN ("file-name-all-completions", Ffile_name_all_completions,
 }
 
 DEFUN ("file-name-all-completions", Ffile_name_all_completions,
@@ -439,16 +439,18 @@ These are all file names in directory DIRECTORY which begin with FILE.  */)
   if (!NILP (handler))
     return call3 (handler, Qfile_name_all_completions, file, directory);
 
   if (!NILP (handler))
     return call3 (handler, Qfile_name_all_completions, file, directory);
 
-  return file_name_completion (file, directory, 1, 0, Qnil);
+  return file_name_completion (file, directory, 1, Qnil);
 }
 
 }
 
-static int file_name_completion_stat (Lisp_Object dirname, DIRENTRY *dp, struct stat *st_addr);
+static int file_name_completion_stat (int, struct dirent *, struct stat *);
 static Lisp_Object Qdefault_directory;
 
 static Lisp_Object
 static Lisp_Object Qdefault_directory;
 
 static Lisp_Object
-file_name_completion (Lisp_Object file, Lisp_Object dirname, int all_flag, int ver_flag, Lisp_Object predicate)
+file_name_completion (Lisp_Object file, Lisp_Object dirname, bool all_flag,
+                     Lisp_Object predicate)
 {
   DIR *d;
 {
   DIR *d;
+  int fd;
   ptrdiff_t bestmatchsize = 0;
   int matchcount = 0;
   /* If ALL_FLAG is 1, BESTMATCH is the list of all matches, decoded.
   ptrdiff_t bestmatchsize = 0;
   int matchcount = 0;
   /* If ALL_FLAG is 1, BESTMATCH is the list of all matches, decoded.
@@ -458,11 +460,11 @@ file_name_completion (Lisp_Object file, Lisp_Object dirname, int all_flag, int v
   Lisp_Object encoded_file;
   Lisp_Object encoded_dir;
   struct stat st;
   Lisp_Object encoded_file;
   Lisp_Object encoded_dir;
   struct stat st;
-  int directoryp;
-  /* If includeall is zero, exclude files in completion-ignored-extensions as
+  bool directoryp;
+  /* If not INCLUDEALL, exclude files in completion-ignored-extensions as
      well as "." and "..".  Until shown otherwise, assume we can't exclude
      anything.  */
      well as "." and "..".  Until shown otherwise, assume we can't exclude
      anything.  */
-  int includeall = 1;
+  bool includeall = 1;
   ptrdiff_t count = SPECPDL_INDEX ();
   struct gcpro gcpro1, gcpro2, gcpro3, gcpro4, gcpro5;
 
   ptrdiff_t count = SPECPDL_INDEX ();
   struct gcpro gcpro1, gcpro2, gcpro3, gcpro4, gcpro5;
 
@@ -470,9 +472,6 @@ file_name_completion (Lisp_Object file, Lisp_Object dirname, int all_flag, int v
 
   CHECK_STRING (file);
 
 
   CHECK_STRING (file);
 
-#ifdef FILE_SYSTEM_CASE
-  file = FILE_SYSTEM_CASE (file);
-#endif
   bestmatch = Qnil;
   encoded_file = encoded_dir = Qnil;
   GCPRO5 (file, dirname, bestmatch, encoded_file, encoded_dir);
   bestmatch = Qnil;
   encoded_file = encoded_dir = Qnil;
   GCPRO5 (file, dirname, bestmatch, encoded_file, encoded_dir);
@@ -486,52 +485,47 @@ file_name_completion (Lisp_Object file, Lisp_Object dirname, int all_flag, int v
      on the encoded file name.  */
   encoded_file = STRING_MULTIBYTE (file) ? ENCODE_FILE (file) : file;
 
      on the encoded file name.  */
   encoded_file = STRING_MULTIBYTE (file) ? ENCODE_FILE (file) : file;
 
-  encoded_dir = ENCODE_FILE (dirname);
+  encoded_dir = ENCODE_FILE (Fdirectory_file_name (dirname));
 
 
-  BLOCK_INPUT;
-  d = opendir (SSDATA (Fdirectory_file_name (encoded_dir)));
-  UNBLOCK_INPUT;
+  d = open_directory (SSDATA (encoded_dir), &fd);
   if (!d)
   if (!d)
-    report_file_error ("Opening directory", Fcons (dirname, Qnil));
+    report_file_error ("Opening directory", dirname);
 
 
-  record_unwind_protect (directory_files_internal_unwind,
-                        make_save_value (d, 0));
+  record_unwind_protect_ptr (directory_files_internal_unwind, d);
 
   /* Loop reading blocks */
   /* (att3b compiler bug requires do a null comparison this way) */
   while (1)
     {
 
   /* Loop reading blocks */
   /* (att3b compiler bug requires do a null comparison this way) */
   while (1)
     {
-      DIRENTRY *dp;
+      struct dirent *dp;
       ptrdiff_t len;
       ptrdiff_t len;
-      int canexclude = 0;
+      bool canexclude = 0;
 
       errno = 0;
       dp = readdir (d);
 
       errno = 0;
       dp = readdir (d);
-      if (dp == NULL && (0
-# ifdef EAGAIN
-                        || errno == EAGAIN
-# endif
-# ifdef EINTR
-                        || errno == EINTR
-# endif
-                        ))
-       { QUIT; continue; }
-
-      if (!dp) break;
+      if (!dp)
+       {
+         if (errno == EAGAIN || errno == EINTR)
+           {
+             QUIT;
+             continue;
+           }
+         break;
+       }
 
 
-      len = NAMLEN (dp);
+      len = dirent_namelen (dp);
 
       QUIT;
 
       QUIT;
-      if (! DIRENTRY_NONEMPTY (dp)
-         || len < SCHARS (encoded_file)
-         || 0 <= scmp (dp->d_name, SSDATA (encoded_file),
-                       SCHARS (encoded_file)))
+      if (len < SCHARS (encoded_file)
+         || (scmp (dp->d_name, SSDATA (encoded_file),
+                   SCHARS (encoded_file))
+             >= 0))
        continue;
 
        continue;
 
-      if (file_name_completion_stat (encoded_dir, dp, &st) < 0)
+      if (file_name_completion_stat (fd, dp, &st) < 0)
        continue;
 
        continue;
 
-      directoryp = S_ISDIR (st.st_mode);
+      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
       tem = Qnil;
       /* If all_flag is set, always include all.
         It would not actually be helpful to the user to ignore any possible
@@ -587,7 +581,7 @@ file_name_completion (Lisp_Object file, Lisp_Object dirname, int all_flag, int v
                    if (skip < 0)
                      continue;
 
                    if (skip < 0)
                      continue;
 
-                   if (0 <= scmp (dp->d_name + skip, p1, elt_len))
+                   if (scmp (dp->d_name + skip, p1, elt_len) >= 0)
                      continue;
                    break;
                  }
                      continue;
                    break;
                  }
@@ -609,9 +603,8 @@ file_name_completion (Lisp_Object file, Lisp_Object dirname, int all_flag, int v
                    skip = len - SCHARS (elt);
                    if (skip < 0) continue;
 
                    skip = len - SCHARS (elt);
                    if (skip < 0) continue;
 
-                   if (0 <= scmp (dp->d_name + skip,
-                                  SSDATA (elt),
-                                  SCHARS (elt)))
+                   if (scmp (dp->d_name + skip, SSDATA (elt), SCHARS (elt))
+                       >= 0)
                      continue;
                    break;
                  }
                      continue;
                    break;
                  }
@@ -705,10 +698,7 @@ file_name_completion (Lisp_Object file, Lisp_Object dirname, int all_flag, int v
                                name, zero,
                                make_number (compare),
                                completion_ignore_case ? Qt : Qnil);
                                name, zero,
                                make_number (compare),
                                completion_ignore_case ? Qt : Qnil);
-         ptrdiff_t matchsize
-           = (EQ (cmp, Qt)     ? compare
-              : XINT (cmp) < 0 ? - XINT (cmp) - 1
-              :                  XINT (cmp) - 1);
+         ptrdiff_t matchsize = EQ (cmp, Qt) ? compare : eabs (XINT (cmp)) - 1;
 
          if (completion_ignore_case)
            {
 
          if (completion_ignore_case)
            {
@@ -719,7 +709,7 @@ file_name_completion (Lisp_Object file, Lisp_Object dirname, int all_flag, int v
              /* This tests that the current file is an exact match
                 but BESTMATCH is not (it is too long).  */
              if ((matchsize == SCHARS (name)
              /* This tests that the current file is an exact match
                 but BESTMATCH is not (it is too long).  */
              if ((matchsize == SCHARS (name)
-                  && matchsize + !!directoryp < SCHARS (bestmatch))
+                  && matchsize + directoryp < SCHARS (bestmatch))
                  ||
                  /* If there is no exact match ignoring case,
                     prefer a match that does not change the case
                  ||
                  /* If there is no exact match ignoring case,
                     prefer a match that does not change the case
@@ -731,7 +721,7 @@ file_name_completion (Lisp_Object file, Lisp_Object dirname, int all_flag, int v
                     either both or neither are exact.  */
                  (((matchsize == SCHARS (name))
                    ==
                     either both or neither are exact.  */
                  (((matchsize == SCHARS (name))
                    ==
-                   (matchsize + !!directoryp == SCHARS (bestmatch)))
+                   (matchsize + directoryp == SCHARS (bestmatch)))
                   && (cmp = Fcompare_strings (name, zero,
                                               make_number (SCHARS (file)),
                                               file, zero,
                   && (cmp = Fcompare_strings (name, zero,
                                               make_number (SCHARS (file)),
                                               file, zero,
@@ -808,14 +798,9 @@ scmp (const char *s1, const char *s2, ptrdiff_t len)
 }
 
 static int
 }
 
 static int
-file_name_completion_stat (Lisp_Object dirname, DIRENTRY *dp, struct stat *st_addr)
+file_name_completion_stat (int fd, struct dirent *dp, struct stat *st_addr)
 {
 {
-  ptrdiff_t len = NAMLEN (dp);
-  ptrdiff_t pos = SCHARS (dirname);
   int value;
   int value;
-  char *fullname;
-  USE_SAFE_ALLOCA;
-  SAFE_ALLOCA (fullname, char *, len + pos + 2);
 
 #ifdef MSDOS
   /* Some fields of struct stat are *very* expensive to compute on MS-DOS,
 
 #ifdef MSDOS
   /* Some fields of struct stat are *very* expensive to compute on MS-DOS,
@@ -828,23 +813,15 @@ file_name_completion_stat (Lisp_Object dirname, DIRENTRY *dp, struct stat *st_ad
   _djstat_flags = _STAT_INODE | _STAT_EXEC_MAGIC | _STAT_DIRSIZE;
 #endif /* MSDOS */
 
   _djstat_flags = _STAT_INODE | _STAT_EXEC_MAGIC | _STAT_DIRSIZE;
 #endif /* MSDOS */
 
-  memcpy (fullname, SDATA (dirname), pos);
-  if (!IS_DIRECTORY_SEP (fullname[pos - 1]))
-    fullname[pos++] = DIRECTORY_SEP;
-
-  memcpy (fullname + pos, dp->d_name, len);
-  fullname[pos + len] = 0;
-
   /* 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.  */
   /* 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 = lstat (fullname, st_addr);
+  value = fstatat (fd, dp->d_name, st_addr, AT_SYMLINK_NOFOLLOW);
   if (value == 0 && S_ISLNK (st_addr->st_mode))
   if (value == 0 && S_ISLNK (st_addr->st_mode))
-    stat (fullname, st_addr);
+    fstatat (fd, dp->d_name, st_addr, 0);
 #ifdef MSDOS
   _djstat_flags = save_djstat_flags;
 #endif /* MSDOS */
 #ifdef MSDOS
   _djstat_flags = save_djstat_flags;
 #endif /* MSDOS */
-  SAFE_FREE ();
   return value;
 }
 \f
   return value;
 }
 \f
@@ -854,7 +831,7 @@ stat_uname (struct stat *st)
 #ifdef WINDOWSNT
   return st->st_uname;
 #else
 #ifdef WINDOWSNT
   return st->st_uname;
 #else
-  struct passwd *pw = (struct passwd *) getpwuid (st->st_uid);
+  struct passwd *pw = getpwuid (st->st_uid);
 
   if (pw)
     return pw->pw_name;
 
   if (pw)
     return pw->pw_name;
@@ -869,7 +846,7 @@ stat_gname (struct stat *st)
 #ifdef WINDOWSNT
   return st->st_gname;
 #else
 #ifdef WINDOWSNT
   return st->st_gname;
 #else
-  struct group *gr = (struct group *) getgrgid (st->st_gid);
+  struct group *gr = getgrgid (st->st_gid);
 
   if (gr)
     return gr->gr_name;
 
   if (gr)
     return gr->gr_name;
@@ -893,8 +870,8 @@ Elements of the attribute list are:
  2. File uid as a string or a number.  If a string value cannot be
   looked up, a numeric value, either an integer or a float, is returned.
  3. File gid, likewise.
  2. File uid as a string or a number.  If a string value cannot be
   looked up, a numeric value, either an integer or a float, is returned.
  3. File gid, likewise.
- 4. Last access time, as a list of two integers.
-  First integer has high-order 16 bits of time, second has low 16 bits.
+ 4. Last access time, as a list of integers (HIGH LOW USEC PSEC) in the
+  same style as (current-time).
   (See a note below about access time on FAT-based filesystems.)
  5. Last modification time, likewise.  This is the time of the last
   change to the file's contents.
   (See a note below about access time on FAT-based filesystems.)
  5. Last modification time, likewise.  This is the time of the last
   change to the file's contents.
@@ -903,7 +880,7 @@ Elements of the attribute list are:
  7. Size in bytes.
   This is a floating point number if the size is too large for an integer.
  8. File modes, as a string of ten letters or dashes as in ls -l.
  7. Size in bytes.
   This is a floating point number if the size is too large for an integer.
  8. File modes, as a string of ten letters or dashes as in ls -l.
- 9. t if file's gid would change if file were deleted and recreated.
+ 9. An unspecified value, present only for backward compatibility.
 10. inode number.  If it is larger than what an Emacs integer can hold,
   this is of the form (HIGH . LOW): first the high bits, then the low 16 bits.
   If even HIGH is too large for an Emacs integer, this is instead of the form
 10. inode number.  If it is larger than what an Emacs integer can hold,
   this is of the form (HIGH . LOW): first the high bits, then the low 16 bits.
   If even HIGH is too large for an Emacs integer, this is instead of the form
@@ -922,21 +899,8 @@ On some FAT-based filesystems, only the date of last access is recorded,
 so last access time will always be midnight of that day.  */)
   (Lisp_Object filename, Lisp_Object id_format)
 {
 so last access time will always be midnight of that day.  */)
   (Lisp_Object filename, Lisp_Object id_format)
 {
-  Lisp_Object values[12];
   Lisp_Object encoded;
   Lisp_Object encoded;
-  struct stat s;
-#ifdef BSD4_2
-  Lisp_Object dirname;
-  struct stat sdir;
-#endif /* BSD4_2 */
-
-  /* An array to hold the mode string generated by filemodestring,
-     including its terminating space and null byte.  */
-  char modes[sizeof "-rwxr-xr-x "];
-
   Lisp_Object handler;
   Lisp_Object handler;
-  struct gcpro gcpro1;
-  char *uname = NULL, *gname = NULL;
 
   filename = Fexpand_file_name (filename, Qnil);
 
 
   filename = Fexpand_file_name (filename, Qnil);
 
@@ -952,36 +916,63 @@ so last access time will always be midnight of that day.  */)
        return call3 (handler, Qfile_attributes, filename, id_format);
     }
 
        return call3 (handler, Qfile_attributes, filename, id_format);
     }
 
-  GCPRO1 (filename);
   encoded = ENCODE_FILE (filename);
   encoded = ENCODE_FILE (filename);
-  UNGCPRO;
+  return file_attributes (AT_FDCWD, SSDATA (encoded), id_format);
+}
+
+static Lisp_Object
+file_attributes (int fd, char const *name, Lisp_Object id_format)
+{
+  Lisp_Object values[12];
+  struct stat s;
+  int lstat_result;
+
+  /* An array to hold the mode string generated by filemodestring,
+     including its terminating space and null byte.  */
+  char modes[sizeof "-rwxr-xr-x "];
+
+  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
+
+  lstat_result = fstatat (fd, name, &s, AT_SYMLINK_NOFOLLOW);
+
+#ifdef WINDOWSNT
+  w32_stat_get_owner_group = 0;
+#endif
 
 
-  if (lstat (SSDATA (encoded), &s) < 0)
+  if (lstat_result < 0)
     return Qnil;
 
     return Qnil;
 
-  values[0] = (S_ISLNK (s.st_mode) ? Ffile_symlink_p (filename)
+  values[0] = (S_ISLNK (s.st_mode) ? emacs_readlinkat (fd, name)
               : S_ISDIR (s.st_mode) ? Qt : Qnil);
   values[1] = make_number (s.st_nlink);
 
   if (!(NILP (id_format) || EQ (id_format, Qinteger)))
     {
               : S_ISDIR (s.st_mode) ? Qt : Qnil);
   values[1] = make_number (s.st_nlink);
 
   if (!(NILP (id_format) || EQ (id_format, Qinteger)))
     {
-      BLOCK_INPUT;
+      block_input ();
       uname = stat_uname (&s);
       gname = stat_gname (&s);
       uname = stat_uname (&s);
       gname = stat_gname (&s);
-      UNBLOCK_INPUT;
+      unblock_input ();
     }
   if (uname)
     }
   if (uname)
-    values[2] = DECODE_SYSTEM (build_string (uname));
+    values[2] = DECODE_SYSTEM (build_unibyte_string (uname));
   else
     values[2] = make_fixnum_or_float (s.st_uid);
   if (gname)
   else
     values[2] = make_fixnum_or_float (s.st_uid);
   if (gname)
-    values[3] = DECODE_SYSTEM (build_string (gname));
+    values[3] = DECODE_SYSTEM (build_unibyte_string (gname));
   else
     values[3] = make_fixnum_or_float (s.st_gid);
 
   else
     values[3] = make_fixnum_or_float (s.st_gid);
 
-  values[4] = make_time (s.st_atime);
-  values[5] = make_time (s.st_mtime);
-  values[6] = make_time (s.st_ctime);
+  values[4] = make_lisp_time (get_stat_atime (&s));
+  values[5] = make_lisp_time (get_stat_mtime (&s));
+  values[6] = make_lisp_time (get_stat_ctime (&s));
 
   /* If the file size is a 4-byte type, assume that files of sizes in
      the 2-4 GiB range wrap around to negative values, as this is a
 
   /* If the file size is a 4-byte type, assume that files of sizes in
      the 2-4 GiB range wrap around to negative values, as this is a
@@ -993,21 +984,11 @@ so last access time will always be midnight of that day.  */)
 
   filemodestring (&s, modes);
   values[8] = make_string (modes, 10);
 
   filemodestring (&s, modes);
   values[8] = make_string (modes, 10);
-#ifdef BSD4_2 /* file gid will be dir gid */
-  dirname = Ffile_name_directory (filename);
-  if (! NILP (dirname))
-    encoded = ENCODE_FILE (dirname);
-  if (! NILP (dirname) && stat (SDATA (encoded), &sdir) == 0)
-    values[9] = (sdir.st_gid != s.st_gid) ? Qt : Qnil;
-  else                                 /* if we can't tell, assume worst */
-    values[9] = Qt;
-#else                                  /* file gid will be egid */
-  values[9] = (s.st_gid != getegid ()) ? Qt : Qnil;
-#endif /* not BSD4_2 */
+  values[9] = Qt;
   values[10] = INTEGER_TO_CONS (s.st_ino);
   values[11] = INTEGER_TO_CONS (s.st_dev);
 
   values[10] = INTEGER_TO_CONS (s.st_ino);
   values[11] = INTEGER_TO_CONS (s.st_dev);
 
-  return Flist (sizeof (values) / sizeof (values[0]), values);
+  return Flist (ARRAYELTS (values), values);
 }
 
 DEFUN ("file-attributes-lessp", Ffile_attributes_lessp, Sfile_attributes_lessp, 2, 2, 0,
 }
 
 DEFUN ("file-attributes-lessp", Ffile_attributes_lessp, Sfile_attributes_lessp, 2, 2, 0,
@@ -1036,7 +1017,7 @@ return a list with one element, taken from `user-real-login-name'.  */)
 #endif
   if (EQ (users, Qnil))
     /* At least current user is always known. */
 #endif
   if (EQ (users, Qnil))
     /* At least current user is always known. */
-    users = Fcons (Vuser_real_login_name, Qnil);
+    users = list1 (Vuser_real_login_name);
   return users;
 }
 
   return users;
 }