Commit 8654f9d7 authored by Paul Eggert's avatar Paul Eggert

Use fdopendir, fstatat and readlinkat, for efficiency.

On my host, this speeds up directory-files-and-attributes by a
factor of 3, when applied to Emacs's src directory.
These functions are standardized by POSIX and are common these
days; fall back on a (slower) gnulib implementation if the host
is too old to supply them.
* .bzrignore: Add lib/dirent.h.
* lib/Makefile.am (libgnu_a_SOURCES): Add openat-die.c, save-cwd.c.
* lib/careadlinkat.c, lib/careadlinkat.h: Merge from gnulib,
incorporating: 2013-01-29 careadlinkat: do not provide careadlinkatcwd.
* lib/gnulib.mk, m4/gnulib-comp.m4: Regenerate.
* lib/dirent.in.h, lib/fdopendir.c, lib/fstatat.c, lib/openat-priv.h:
* lib/openat-proc.c, lib/openat.h, m4/dirent_h.m4, m4/fdopendir.m4:
* m4/fstatat.m4: New files, from gnulib.
* lib/openat-die.c, lib/save-cwd.c, lib/save-cwd.h: New files.
These last three are specific to Emacs and are not copied from gnulib.
They are simpler than the gnulib versions and are tuned for Emacs.
* admin/merge-gnulib (GNULIB_MODULES): Add fdopendir, fstatat, readlinkat.
(GNULIB_TOOL_FLAGS): Do not avoid at-internal, openat-h.
Avoid dup, open, opendir.
* nt/inc/sys/stat.h (fstatat):
* nt/inc/unistd.h (readlinkat): New decls.
* src/conf_post.h (GNULIB_SUPPORT_ONLY_AT_FDCWD): Remove.
* src/dired.c: Include <fcntl.h>.
(open_directory): New function, which uses open and fdopendir
rather than opendir.  DOS_NT platforms still use opendir, though.
(directory_files_internal, file_name_completion): Use it.
(file_attributes): New function, with most of the old Ffile_attributes.
(directory_files_internal, Ffile_attributes): Use it.
(file_attributes, file_name_completion_stat): First arg is now fd,
not dir name.  All uses changed.  Use fstatat rather than lstat +
stat.
(file_attributes): Use emacs_readlinkat rather than Ffile_symlink_p.
* src/fileio.c: Include <allocator.h>, <careadlinkat.h>.
(emacs_readlinkat): New function, with much of the old
Ffile_symlink_p, but with an fd argument for speed.
It uses readlinkat rather than careadlinkatcwd, so that it
need not assume the working directory.
(Ffile_symlink_p): Use it.
* src/filelock.c (current_lock_owner): Use emacs_readlinkat
rather than emacs_readlink.
* src/lisp.h (emacs_readlinkat): New decl.
(READLINK_BUFSIZE, emacs_readlink): Remove.
* src/sysdep.c: Do not include <allocator.h>, <careadlinkat.h>.
(emacs_norealloc_allocator, emacs_readlink): Remove.
This stuff is moved to fileio.c.
* src/w32.c (fstatat, readlinkat): New functions.
(careadlinkat): Don't check that fd == AT_FDCWD.
(careadlinkatcwd): Remove; no longer needed.

Fixes: debbugs:13539
parent 44b12dd6
2013-02-01 Paul Eggert <eggert@cs.ucla.edu>
Use fdopendir, fstatat and readlinkat, for efficiency (Bug#13539).
On my host, this speeds up directory-files-and-attributes by a
factor of 3, when applied to Emacs's src directory.
These functions are standardized by POSIX and are common these
days; fall back on a (slower) gnulib implementation if the host
is too old to supply them.
* .bzrignore: Add lib/dirent.h.
* lib/Makefile.am (libgnu_a_SOURCES): Add openat-die.c, save-cwd.c.
* lib/careadlinkat.c, lib/careadlinkat.h: Merge from gnulib,
incorporating: 2013-01-29 careadlinkat: do not provide careadlinkatcwd.
* lib/gnulib.mk, m4/gnulib-comp.m4: Regenerate.
* lib/dirent.in.h, lib/fdopendir.c, lib/fstatat.c, lib/openat-priv.h:
* lib/openat-proc.c, lib/openat.h, m4/dirent_h.m4, m4/fdopendir.m4:
* m4/fstatat.m4: New files, from gnulib.
* lib/openat-die.c, lib/save-cwd.c, lib/save-cwd.h: New files.
These last three are specific to Emacs and are not copied from gnulib.
They are simpler than the gnulib versions and are tuned for Emacs.
2013-02-01 Glenn Morris <rgm@gnu.org>
* make-dist: Only README files exist in lisp/ now, not README*.
......
2013-02-01 Paul Eggert <eggert@cs.ucla.edu>
Use fdopendir, fstatat and readlinkat, for efficiency (Bug#13539).
* merge-gnulib (GNULIB_MODULES): Add fdopendir, fstatat, readlinkat.
(GNULIB_TOOL_FLAGS): Do not avoid at-internal, openat-h.
Avoid dup, open, opendir.
2013-01-15 Dmitry Antipov <dmantipov@yandex.ru>
* coccinelle/xsave.cocci: Semantic patch to adjust users of
......
......@@ -29,9 +29,9 @@ GNULIB_MODULES='
alloca-opt c-ctype c-strcase
careadlinkat close-stream crypto/md5 crypto/sha1 crypto/sha256 crypto/sha512
dtoastr dtotimespec dup2 environ execinfo faccessat
fcntl-h filemode getloadavg getopt-gnu gettime gettimeofday
fcntl-h fdopendir filemode fstatat getloadavg getopt-gnu gettime gettimeofday
ignore-value intprops largefile lstat
manywarnings mktime pselect pthread_sigmask putenv readlink
manywarnings mktime pselect pthread_sigmask putenv readlink readlinkat
sig2str socklen stat-time stdalign stdarg stdbool stdio
strftime strtoimax strtoumax symlink sys_stat
sys_time time timer-time timespec-add timespec-sub unsetenv utimens
......@@ -39,10 +39,10 @@ GNULIB_MODULES='
'
GNULIB_TOOL_FLAGS='
--avoid=at-internal
--avoid=dup
--avoid=errno --avoid=fchdir --avoid=fcntl --avoid=fstat
--avoid=malloc-posix --avoid=msvc-inval --avoid=msvc-nothrow
--avoid=openat-die --avoid=openat-h
--avoid=open --avoid=openat-die --avoid=opendir
--avoid=raise
--avoid=save-cwd --avoid=select --avoid=sigprocmask --avoid=sys_types
--avoid=threadlib
......
......@@ -8,3 +8,5 @@ AM_CFLAGS = $(GNULIB_WARN_CFLAGS) $(WERROR_CFLAGS)
DEFAULT_INCLUDES = -I. -I$(top_srcdir)/lib -I../src -I$(top_srcdir)/src
include gnulib.mk
libgnu_a_SOURCES += openat-die.c save-cwd.c
......@@ -24,9 +24,7 @@
#include <errno.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
/* Define this independently so that stdint.h is not a prerequisite. */
#ifndef SIZE_MAX
......@@ -39,20 +37,6 @@
#include "allocator.h"
/* Get the symbolic link value of FILENAME and put it into BUFFER, with
size BUFFER_SIZE. This function acts like readlink but has
readlinkat's signature. */
ssize_t
careadlinkatcwd (int fd, char const *filename, char *buffer,
size_t buffer_size)
{
/* FD must be AT_FDCWD here, otherwise the caller is using this
function in contexts for which it was not meant for. */
if (fd != AT_FDCWD)
abort ();
return readlink (filename, buffer, buffer_size);
}
/* Assuming the current directory is FD, get the symbolic link value
of FILENAME as a null-terminated string and put it into a buffer.
If FD is AT_FDCWD, FILENAME is interpreted relative to the current
......
......@@ -52,9 +52,9 @@ char *careadlinkat (int fd, char const *filename,
ssize_t (*preadlinkat) (int, char const *,
char *, size_t));
/* Suitable values for careadlinkat's FD and PREADLINKAT arguments,
/* Suitable value for careadlinkat's FD argument,
when doing a plain readlink:
Pass FD = AT_FDCWD and PREADLINKAT = careadlinkatcwd. */
Pass FD = AT_FDCWD. */
#if HAVE_READLINKAT
/* AT_FDCWD is declared in <fcntl.h>. */
#else
......@@ -66,7 +66,5 @@ char *careadlinkat (int fd, char const *filename,
# define AT_FDCWD (-3041965)
# endif
#endif
ssize_t careadlinkatcwd (int fd, char const *filename,
char *buffer, size_t buffer_size);
#endif /* _GL_CAREADLINKAT_H */
/* A GNU-like <dirent.h>.
Copyright (C) 2006-2013 Free Software Foundation, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
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 this program. If not, see <http://www.gnu.org/licenses/>. */
#ifndef _@GUARD_PREFIX@_DIRENT_H
#if __GNUC__ >= 3
@PRAGMA_SYSTEM_HEADER@
#endif
@PRAGMA_COLUMNS@
/* The include_next requires a split double-inclusion guard. */
#if @HAVE_DIRENT_H@
# @INCLUDE_NEXT@ @NEXT_DIRENT_H@
#endif
#ifndef _@GUARD_PREFIX@_DIRENT_H
#define _@GUARD_PREFIX@_DIRENT_H
/* Get ino_t. Needed on some systems, including glibc 2.8. */
#include <sys/types.h>
#if !@HAVE_DIRENT_H@
/* Define types DIR and 'struct dirent'. */
# if !GNULIB_defined_struct_dirent
struct dirent
{
char d_type;
char d_name[1];
};
/* Possible values for 'd_type'. */
# define DT_UNKNOWN 0
# define DT_FIFO 1 /* FIFO */
# define DT_CHR 2 /* character device */
# define DT_DIR 4 /* directory */
# define DT_BLK 6 /* block device */
# define DT_REG 8 /* regular file */
# define DT_LNK 10 /* symbolic link */
# define DT_SOCK 12 /* socket */
# define DT_WHT 14 /* whiteout */
typedef struct gl_directory DIR;
# define GNULIB_defined_struct_dirent 1
# endif
#endif
/* The __attribute__ feature is available in gcc versions 2.5 and later.
The attribute __pure__ was added in gcc 2.96. */
#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 96)
# define _GL_ATTRIBUTE_PURE __attribute__ ((__pure__))
#else
# define _GL_ATTRIBUTE_PURE /* empty */
#endif
/* The definitions of _GL_FUNCDECL_RPL etc. are copied here. */
/* The definition of _GL_ARG_NONNULL is copied here. */
/* The definition of _GL_WARN_ON_USE is copied here. */
/* Declare overridden functions. */
#if @GNULIB_OPENDIR@
# if @REPLACE_OPENDIR@
# if !(defined __cplusplus && defined GNULIB_NAMESPACE)
# undef opendir
# define opendir rpl_opendir
# endif
_GL_FUNCDECL_RPL (opendir, DIR *, (const char *dir_name) _GL_ARG_NONNULL ((1)));
_GL_CXXALIAS_RPL (opendir, DIR *, (const char *dir_name));
# else
# if !@HAVE_OPENDIR@
_GL_FUNCDECL_SYS (opendir, DIR *, (const char *dir_name) _GL_ARG_NONNULL ((1)));
# endif
_GL_CXXALIAS_SYS (opendir, DIR *, (const char *dir_name));
# endif
_GL_CXXALIASWARN (opendir);
#elif defined GNULIB_POSIXCHECK
# undef opendir
# if HAVE_RAW_DECL_OPENDIR
_GL_WARN_ON_USE (opendir, "opendir is not portable - "
"use gnulib module opendir for portability");
# endif
#endif
#if @GNULIB_READDIR@
# if !@HAVE_READDIR@
_GL_FUNCDECL_SYS (readdir, struct dirent *, (DIR *dirp) _GL_ARG_NONNULL ((1)));
# endif
_GL_CXXALIAS_SYS (readdir, struct dirent *, (DIR *dirp));
_GL_CXXALIASWARN (readdir);
#elif defined GNULIB_POSIXCHECK
# undef readdir
# if HAVE_RAW_DECL_READDIR
_GL_WARN_ON_USE (readdir, "readdir is not portable - "
"use gnulib module readdir for portability");
# endif
#endif
#if @GNULIB_REWINDDIR@
# if !@HAVE_REWINDDIR@
_GL_FUNCDECL_SYS (rewinddir, void, (DIR *dirp) _GL_ARG_NONNULL ((1)));
# endif
_GL_CXXALIAS_SYS (rewinddir, void, (DIR *dirp));
_GL_CXXALIASWARN (rewinddir);
#elif defined GNULIB_POSIXCHECK
# undef rewinddir
# if HAVE_RAW_DECL_REWINDDIR
_GL_WARN_ON_USE (rewinddir, "rewinddir is not portable - "
"use gnulib module rewinddir for portability");
# endif
#endif
#if @GNULIB_CLOSEDIR@
# if @REPLACE_CLOSEDIR@
# if !(defined __cplusplus && defined GNULIB_NAMESPACE)
# undef closedir
# define closedir rpl_closedir
# endif
_GL_FUNCDECL_RPL (closedir, int, (DIR *dirp) _GL_ARG_NONNULL ((1)));
_GL_CXXALIAS_RPL (closedir, int, (DIR *dirp));
# else
# if !@HAVE_CLOSEDIR@
_GL_FUNCDECL_SYS (closedir, int, (DIR *dirp) _GL_ARG_NONNULL ((1)));
# endif
_GL_CXXALIAS_SYS (closedir, int, (DIR *dirp));
# endif
_GL_CXXALIASWARN (closedir);
#elif defined GNULIB_POSIXCHECK
# undef closedir
# if HAVE_RAW_DECL_CLOSEDIR
_GL_WARN_ON_USE (closedir, "closedir is not portable - "
"use gnulib module closedir for portability");
# endif
#endif
#if @GNULIB_DIRFD@
/* Return the file descriptor associated with the given directory stream,
or -1 if none exists. */
# if @REPLACE_DIRFD@
# if !(defined __cplusplus && defined GNULIB_NAMESPACE)
# undef dirfd
# define dirfd rpl_dirfd
# endif
_GL_FUNCDECL_RPL (dirfd, int, (DIR *) _GL_ARG_NONNULL ((1)));
_GL_CXXALIAS_RPL (dirfd, int, (DIR *));
# else
# if defined __cplusplus && defined GNULIB_NAMESPACE && defined dirfd
/* dirfd is defined as a macro and not as a function.
Turn it into a function and get rid of the macro. */
static inline int (dirfd) (DIR *dp) { return dirfd (dp); }
# undef dirfd
# endif
# if !(@HAVE_DECL_DIRFD@ || defined dirfd)
_GL_FUNCDECL_SYS (dirfd, int, (DIR *) _GL_ARG_NONNULL ((1)));
# endif
_GL_CXXALIAS_SYS (dirfd, int, (DIR *));
# endif
_GL_CXXALIASWARN (dirfd);
#elif defined GNULIB_POSIXCHECK
# undef dirfd
# if HAVE_RAW_DECL_DIRFD
_GL_WARN_ON_USE (dirfd, "dirfd is unportable - "
"use gnulib module dirfd for portability");
# endif
#endif
#if @GNULIB_FDOPENDIR@
/* Open a directory stream visiting the given directory file
descriptor. Return NULL and set errno if fd is not visiting a
directory. On success, this function consumes fd (it will be
implicitly closed either by this function or by a subsequent
closedir). */
# if @REPLACE_FDOPENDIR@
# if !(defined __cplusplus && defined GNULIB_NAMESPACE)
# undef fdopendir
# define fdopendir rpl_fdopendir
# endif
_GL_FUNCDECL_RPL (fdopendir, DIR *, (int fd));
_GL_CXXALIAS_RPL (fdopendir, DIR *, (int fd));
# else
# if !@HAVE_FDOPENDIR@ || !@HAVE_DECL_FDOPENDIR@
_GL_FUNCDECL_SYS (fdopendir, DIR *, (int fd));
# endif
_GL_CXXALIAS_SYS (fdopendir, DIR *, (int fd));
# endif
_GL_CXXALIASWARN (fdopendir);
#elif defined GNULIB_POSIXCHECK
# undef fdopendir
# if HAVE_RAW_DECL_FDOPENDIR
_GL_WARN_ON_USE (fdopendir, "fdopendir is unportable - "
"use gnulib module fdopendir for portability");
# endif
#endif
#if @GNULIB_SCANDIR@
/* Scan the directory DIR, calling FILTER on each directory entry.
Entries for which FILTER returns nonzero are individually malloc'd,
sorted using qsort with CMP, and collected in a malloc'd array in
*NAMELIST. Returns the number of entries selected, or -1 on error. */
# if !@HAVE_SCANDIR@
_GL_FUNCDECL_SYS (scandir, int,
(const char *dir, struct dirent ***namelist,
int (*filter) (const struct dirent *),
int (*cmp) (const struct dirent **, const struct dirent **))
_GL_ARG_NONNULL ((1, 2, 4)));
# endif
/* Need to cast, because on glibc systems, the fourth parameter is
int (*cmp) (const void *, const void *). */
_GL_CXXALIAS_SYS_CAST (scandir, int,
(const char *dir, struct dirent ***namelist,
int (*filter) (const struct dirent *),
int (*cmp) (const struct dirent **, const struct dirent **)));
_GL_CXXALIASWARN (scandir);
#elif defined GNULIB_POSIXCHECK
# undef scandir
# if HAVE_RAW_DECL_SCANDIR
_GL_WARN_ON_USE (scandir, "scandir is unportable - "
"use gnulib module scandir for portability");
# endif
#endif
#if @GNULIB_ALPHASORT@
/* Compare two 'struct dirent' entries alphabetically. */
# if !@HAVE_ALPHASORT@
_GL_FUNCDECL_SYS (alphasort, int,
(const struct dirent **, const struct dirent **)
_GL_ATTRIBUTE_PURE
_GL_ARG_NONNULL ((1, 2)));
# endif
/* Need to cast, because on glibc systems, the parameters are
(const void *, const void *). */
_GL_CXXALIAS_SYS_CAST (alphasort, int,
(const struct dirent **, const struct dirent **));
_GL_CXXALIASWARN (alphasort);
#elif defined GNULIB_POSIXCHECK
# undef alphasort
# if HAVE_RAW_DECL_ALPHASORT
_GL_WARN_ON_USE (alphasort, "alphasort is unportable - "
"use gnulib module alphasort for portability");
# endif
#endif
#endif /* _@GUARD_PREFIX@_DIRENT_H */
#endif /* _@GUARD_PREFIX@_DIRENT_H */
/* provide a replacement fdopendir function
Copyright (C) 2004-2013 Free Software Foundation, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
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 this program. If not, see <http://www.gnu.org/licenses/>. */
/* written by Jim Meyering */
#include <config.h>
#include <dirent.h>
#include <stdlib.h>
#include <unistd.h>
#if !HAVE_FDOPENDIR
# include "openat.h"
# include "openat-priv.h"
# include "save-cwd.h"
# if GNULIB_DIRENT_SAFER
# include "dirent--.h"
# endif
# ifndef REPLACE_FCHDIR
# define REPLACE_FCHDIR 0
# endif
static DIR *fdopendir_with_dup (int, int, struct saved_cwd const *);
static DIR *fd_clone_opendir (int, struct saved_cwd const *);
/* Replacement for POSIX fdopendir.
First, try to simulate it via opendir ("/proc/self/fd/..."). Failing
that, simulate it by using fchdir metadata, or by doing
save_cwd/fchdir/opendir(".")/restore_cwd.
If either the save_cwd or the restore_cwd fails (relatively unlikely),
then give a diagnostic and exit nonzero.
If successful, the resulting stream is based on FD in
implementations where streams are based on file descriptors and in
applications where no other thread or signal handler allocates or
frees file descriptors. In other cases, consult dirfd on the result
to find out whether FD is still being used.
Otherwise, this function works just like POSIX fdopendir.
W A R N I N G:
Unlike other fd-related functions, this one places constraints on FD.
If this function returns successfully, FD is under control of the
dirent.h system, and the caller should not close or modify the state of
FD other than by the dirent.h functions. */
DIR *
fdopendir (int fd)
{
DIR *dir = fdopendir_with_dup (fd, -1, NULL);
if (! REPLACE_FCHDIR && ! dir)
{
int saved_errno = errno;
if (EXPECTED_ERRNO (saved_errno))
{
struct saved_cwd cwd;
if (save_cwd (&cwd) != 0)
openat_save_fail (errno);
dir = fdopendir_with_dup (fd, -1, &cwd);
saved_errno = errno;
free_cwd (&cwd);
errno = saved_errno;
}
}
return dir;
}
/* Like fdopendir, except that if OLDER_DUPFD is not -1, it is known
to be a dup of FD which is less than FD - 1 and which will be
closed by the caller and not otherwise used by the caller. This
function makes sure that FD is closed and all file descriptors less
than FD are open, and then calls fd_clone_opendir on a dup of FD.
That way, barring race conditions, fd_clone_opendir returns a
stream whose file descriptor is FD.
If REPLACE_CHDIR or CWD is null, use opendir ("/proc/self/fd/...",
falling back on fchdir metadata. Otherwise, CWD is a saved version
of the working directory; use fchdir/opendir(".")/restore_cwd(CWD). */
static DIR *
fdopendir_with_dup (int fd, int older_dupfd, struct saved_cwd const *cwd)
{
int dupfd = dup (fd);
if (dupfd < 0 && errno == EMFILE)
dupfd = older_dupfd;
if (dupfd < 0)
return NULL;
else
{
DIR *dir;
int saved_errno;
if (dupfd < fd - 1 && dupfd != older_dupfd)
{
dir = fdopendir_with_dup (fd, dupfd, cwd);
saved_errno = errno;
}
else
{
close (fd);
dir = fd_clone_opendir (dupfd, cwd);
saved_errno = errno;
if (! dir)
{
int fd1 = dup (dupfd);
if (fd1 != fd)
openat_save_fail (fd1 < 0 ? errno : EBADF);
}
}
if (dupfd != older_dupfd)
close (dupfd);
errno = saved_errno;
return dir;
}
}
/* Like fdopendir, except the result controls a clone of FD. It is
the caller's responsibility both to close FD and (if the result is
not null) to closedir the result. */
static DIR *
fd_clone_opendir (int fd, struct saved_cwd const *cwd)
{
if (REPLACE_FCHDIR || ! cwd)
{
DIR *dir = NULL;
int saved_errno = EOPNOTSUPP;
char buf[OPENAT_BUFFER_SIZE];
char *proc_file = openat_proc_name (buf, fd, ".");
if (proc_file)
{
dir = opendir (proc_file);
saved_errno = errno;
if (proc_file != buf)
free (proc_file);
}
# if REPLACE_FCHDIR
if (! dir && EXPECTED_ERRNO (saved_errno))
{
char const *name = _gl_directory_name (fd);
return (name ? opendir (name) : NULL);
}
# endif
errno = saved_errno;
return dir;
}
else
{
if (fchdir (fd) != 0)
return NULL;
else
{
DIR *dir = opendir (".");
int saved_errno = errno;
if (restore_cwd (cwd) != 0)
openat_restore_fail (errno);
errno = saved_errno;
return dir;
}
}
}
#else /* HAVE_FDOPENDIR */
# include <errno.h>
# include <sys/stat.h>
# undef fdopendir
/* Like fdopendir, but work around GNU/Hurd bug by validating FD. */
DIR *
rpl_fdopendir (int fd)
{
struct stat st;
if (fstat (fd, &st))
return NULL;
if (!S_ISDIR (st.st_mode))
{
errno = ENOTDIR;
return NULL;
}
return fdopendir (fd);
}
#endif /* HAVE_FDOPENDIR */
/* Work around an fstatat bug on Solaris 9.
Copyright (C) 2006, 2009-2013 Free Software Foundation, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
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 this program. If not, see <http://www.gnu.org/licenses/>. */
/* Written by Paul Eggert and Jim Meyering. */
/* If the user's config.h happens to include <sys/stat.h>, let it include only
the system's <sys/stat.h> here, so that orig_fstatat doesn't recurse to
rpl_fstatat. */
#define __need_system_sys_stat_h
#include <config.h>
/* Get the original definition of fstatat. It might be defined as a macro. */
#include <sys/types.h>
#include <sys/stat.h>
#undef __need_system_sys_stat_h
#if HAVE_FSTATAT
static int
orig_fstatat (int fd, char const *filename, struct stat *buf, int flags)
{
return fstatat (fd, filename, buf, flags);
}
#endif
/* Write "sys/stat.h" here, not <sys/stat.h>, otherwise OSF/1 5.1 DTK cc
eliminates this include because of the preliminary #include <sys/stat.h>
above. */
#include "sys/stat.h"
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#if HAVE_FSTATAT && HAVE_WORKING_FSTATAT_ZERO_FLAG
# ifndef LSTAT_FOLLOWS_SLASHED_SYMLINK
# define LSTAT_FOLLOWS_SLASHED_SYMLINK 0
# endif
/* fstatat should always follow symbolic links that end in /, but on
Solaris 9 it doesn't if AT_SYMLINK_NOFOLLOW is specified.
Likewise, trailing slash on a non-directory should be an error.
These are the same problems that lstat.c and stat.c address, so
solve it in a similar way.
AIX 7.1 fstatat (AT_FDCWD, ..., 0) always fails, which is a bug.
Work around this bug if FSTATAT_AT_FDCWD_0_BROKEN is nonzero. */
int
rpl_fstatat (int fd, char const *file, struct stat *st, int flag)
{
int result = orig_fstatat (fd, file, st, flag);
size_t len;
if (LSTAT_FOLLOWS_SLASHED_SYMLINK || result != 0)
return result;
len = strlen (file);
if (flag & AT_SYMLINK_NOFOLLOW)
{
/* Fix lstat behavior. */
if (file[len - 1] != '/' || S_ISDIR (st->st_mode))
return 0;