From d9203c96c70e64ae24a2f2d36e1ba80d79628dc5 Mon Sep 17 00:00:00 2001 From: Paul Eggert Date: Tue, 18 Jan 2005 21:58:11 +0000 Subject: [PATCH] * modules/chdir-long, modules/openat: New files. * modules/save-cwd (Depends-on): Depend on chdir-long. (Makefile.am): Remove lib_SOURCES; now handled by AC_LIBSOURCES. * lib/save-cwd.c: Include "save-cwd.h" before other include files. (O_DIRECTORY): Remove; not needed here, since "." must be a directory. All uses removed. (save_cwd): Use __sgi || __sun, not sun || __sun. __sun is universal on Suns, and we also need to test for IRIX. Revamp code to use 'if' rather than '#if'. Avoid unnecessary comparison of cwd->desc to 0. Change the name of the robust chdir function from chdir to chdir_long. * lib/save-cwd.c: Include chdir-long.h rather than chdir.h. (restore_cwd): Use chdir_long, not chdir. * lib/chdir-long.c: Renamed from chdir.c. * lib/chdir-long.h: Renamed from chdir.h. [!defined PATH_MAX]: Define chdir_long to chdir on systems like the Hurd. * m4/chdir-long.m4, openat.m4: New files. * m4/save-cwd.m4 (gl_SAVE_CWD): Add AC_LIBSOURCES for save-cwd.c, save-cwd.h. Add AC_LIBOBJ for save-cwd. --- lib/chdir-long.c | 338 +++++++++++++++++++++++++++++++++++++++++++++++++++++ lib/chdir-long.h | 35 ++++++ lib/openat.c | 91 +++++++++++++++ lib/openat.h | 35 ++++++ lib/save-cwd.c | 57 +++++---- m4/chdir-long.m4 | 41 +++++++ m4/openat.m4 | 28 +++++ m4/save-cwd.m4 | 6 +- modules/chdir-long | 25 ++++ modules/openat | 27 +++++ modules/save-cwd | 2 +- 11 files changed, 653 insertions(+), 32 deletions(-) create mode 100644 lib/chdir-long.c create mode 100644 lib/chdir-long.h create mode 100644 lib/openat.c create mode 100644 lib/openat.h create mode 100644 m4/chdir-long.m4 create mode 100644 m4/openat.m4 create mode 100644 modules/chdir-long create mode 100644 modules/openat diff --git a/lib/chdir-long.c b/lib/chdir-long.c new file mode 100644 index 000000000..7e7fd9718 --- /dev/null +++ b/lib/chdir-long.c @@ -0,0 +1,338 @@ +/* provide a chdir function that tries not to fail due to ENAMETOOLONG + Copyright (C) 2004 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 2, 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, write to the Free Software Foundation, + Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + +/* written by Jim Meyering */ + +#include + +#include "chdir-long.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mempcpy.h" +#include "openat.h" + +#ifndef O_DIRECTORY +# define O_DIRECTORY 0 +#endif + +#ifndef MIN +# define MIN(a, b) ((a) < (b) ? (a) : (b)) +#endif + +#ifndef PATH_MAX +# error "compile this file only if your system defines PATH_MAX" +#endif + +/* FIXME: this use of `MIN' is our sole concession to arbitrary limitations. + If, for some system, PATH_MAX is larger than 8191 and you call + chdir_long with a directory name that is longer than PATH_MAX, + yet that contains a single component that is more than 8191 bytes + long, then this function will fail. */ +#define MAX_COMPONENT_LENGTH MIN (PATH_MAX - 1, 8 * 1024) + +struct cd_buf +{ + /* FIXME maybe allocate this via malloc, rather than using the stack. + But that would be the sole use of malloc. Is it worth it to + let chdir_long fail due to a low-memory condition? + But when using malloc, and assuming we remove the `concession' + above, we'll still have to avoid allocating 2^31 bytes on + systems that define PATH_MAX to very large number. + Ideally, we'd allocate enough to deal with most names, and + dynamically increase the buffer size only when necessary. */ + char buffer[MAX_COMPONENT_LENGTH + 1]; + char *avail; + int fd; +}; + +/* Like memchr, but return the number of bytes from MEM + to the first occurrence of C thereafter. Search only + LEN bytes. Return LEN if C is not found. */ +static inline size_t +memchrcspn (char const *mem, int c, size_t len) +{ + char const *found = memchr (mem, c, len); + if (!found) + return len; + + len = found - mem; + return len; +} + +static void +cdb_init (struct cd_buf *cdb) +{ + cdb->avail = cdb->buffer; + cdb->fd = AT_FDCWD; +} + +static inline bool +cdb_empty (struct cd_buf const *cdb) +{ + return cdb->avail == cdb->buffer; +} + +static inline int +cdb_fchdir (struct cd_buf const *cdb) +{ + return fchdir (cdb->fd); +} + +static int +cdb_advance_fd (struct cd_buf *cdb, char const *dir) +{ + int new_fd = openat (cdb->fd, dir, O_RDONLY | O_DIRECTORY); + if (new_fd < 0) + { + new_fd = openat (cdb->fd, dir, O_WRONLY | O_DIRECTORY); + if (new_fd < 0) + return -1; + } + + if (cdb->fd != AT_FDCWD) + close (cdb->fd); + cdb->fd = new_fd; + + return 0; +} + +static int +cdb_flush (struct cd_buf *cdb) +{ + if (cdb_empty (cdb)) + return 0; + + cdb->avail[0] = '\0'; + if (cdb_advance_fd (cdb, cdb->buffer) != 0) + return -1; + + cdb->avail = cdb->buffer; + + return 0; +} + +static void +cdb_free (struct cd_buf *cdb) +{ + if (0 <= cdb->fd && close (cdb->fd) != 0) + abort (); +} + +static int +cdb_append (struct cd_buf *cdb, char const *s, size_t len) +{ + char const *end = cdb->buffer + sizeof cdb->buffer; + + /* Insert a slash separator if there is a preceding byte + and it's not a slash. */ + bool need_slash = (cdb->buffer < cdb->avail && cdb->avail[-1] != '/'); + size_t n_free; + + if (sizeof cdb->buffer < len + 1) + { + /* This single component is too long. */ + errno = ENAMETOOLONG; + return -1; + } + + /* See if there's enough room for the `/', the new component and + a trailing NUL. */ + n_free = end - cdb->avail; + if (n_free < need_slash + len + 1) + { + if (cdb_flush (cdb) != 0) + return -1; + need_slash = false; + } + + if (need_slash) + *(cdb->avail)++ = '/'; + + cdb->avail = mempcpy (cdb->avail, s, len); + return 0; +} + +/* This is a wrapper around chdir that works even on PATH_MAX-limited + systems. It handles an arbitrarily long directory name by extracting + and processing manageable portions of the name. On systems without + the openat syscall, this means changing the working directory to + more and more `distant' points along the long directory name and + then restoring the working directory. + If any of those attempts to change or restore the working directory + fails, this function exits nonzero. + + Note that this function may still fail with errno == ENAMETOOLONG, + but only if the specified directory name contains a component that + is long enough to provoke such a failure all by itself (e.g. if the + component is longer than PATH_MAX on systems that define PATH_MAX). */ + +int +chdir_long (char const *dir) +{ + int e = chdir (dir); + if (e == 0 || errno != ENAMETOOLONG) + return e; + + { + size_t len = strlen (dir); + char const *dir_end = dir + len; + char const *d; + struct cd_buf cdb; + + cdb_init (&cdb); + + /* If DIR is the empty string, then the chdir above + must have failed and set errno to ENOENT. */ + assert (0 < len); + + if (*dir == '/') + { + /* Names starting with exactly two slashes followed by at least + one non-slash are special -- + for example, in some environments //Hostname/file may + denote a file on a different host. + Preserve those two leading slashes. Treat all other + sequences of slashes like a single one. */ + if (3 <= len && dir[1] == '/' && dir[2] != '/') + { + size_t name_len = 1 + strcspn (dir + 3, "/"); + if (cdb_append (&cdb, dir, 2 + name_len) != 0) + goto Fail; + /* Advance D to next slash or to end of string. */ + d = dir + 2 + name_len; + assert (*d == '/' || *d == '\0'); + } + else + { + if (cdb_append (&cdb, "/", 1) != 0) + goto Fail; + d = dir + 1; + } + } + else + { + d = dir; + } + + while (1) + { + /* Skip any slashes to find start of next component -- + or the end of DIR. */ + char const *start = d + strspn (d, "/"); + if (*start == '\0') + { + if (cdb_flush (&cdb) != 0) + goto Fail; + break; + } + /* If the remaining portion is no longer than PATH_MAX, then + flush anything that is buffered and do the rest in one chunk. */ + if (dir_end - start <= PATH_MAX) + { + if (cdb_flush (&cdb) != 0 + || cdb_advance_fd (&cdb, start) != 0) + goto Fail; + break; + } + + len = memchrcspn (start, '/', dir_end - start); + assert (len == strcspn (start, "/")); + d = start + len; + if (cdb_append (&cdb, start, len) != 0) + goto Fail; + } + + if (cdb_fchdir (&cdb) != 0) + goto Fail; + + cdb_free (&cdb); + return 0; + + Fail: + { + int saved_errno = errno; + cdb_free (&cdb); + errno = saved_errno; + return -1; + } + } +} + +#if TEST_CHDIR + +# include +# include "closeout.h" +# include "error.h" + +char *program_name; + +int +main (int argc, char *argv[]) +{ + char *line = NULL; + size_t n = 0; + int len; + + program_name = argv[0]; + atexit (close_stdout); + + len = getline (&line, &n, stdin); + if (len < 0) + { + int saved_errno = errno; + if (feof (stdin)) + exit (0); + + error (EXIT_FAILURE, saved_errno, + "reading standard input"); + } + else if (len == 0) + exit (0); + + if (line[len-1] == '\n') + line[len-1] = '\0'; + + if (chdir_long (line) != 0) + error (EXIT_FAILURE, errno, + "chdir_long failed: %s", line); + + { + /* Using `pwd' here makes sense only if it is a robust implementation, + like the one in coreutils after the 2004-04-19 changes. */ + char const *cmd = "pwd"; + execlp (cmd, (char *) NULL); + error (EXIT_FAILURE, errno, "%s", cmd); + } + + /* not reached */ + abort (); +} +#endif + +/* +Local Variables: +compile-command: "gcc -DTEST_CHDIR=1 -DHAVE_CONFIG_H -I.. -g -O -W -Wall chdir-long.c libfetish.a" +End: +*/ diff --git a/lib/chdir-long.h b/lib/chdir-long.h new file mode 100644 index 000000000..47fa79160 --- /dev/null +++ b/lib/chdir-long.h @@ -0,0 +1,35 @@ +/* provide a chdir function that tries not to fail due to ENAMETOOLONG + Copyright (C) 2004 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 2, 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, write to the Free Software Foundation, + Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + +/* Written by Jim Meyering. */ + +#include +#include + +#ifndef PATH_MAX +# ifdef MAXPATHLEN +# define PATH_MAX MAXPATHLEN +# endif +#endif + +/* On systems without PATH_MAX, presume that chdir accepts + arbitrarily long directory names. */ +#ifndef PATH_MAX +# define chdir_long(Dir) chdir (Dir) +#else +int chdir_long (char const *dir); +#endif diff --git a/lib/openat.c b/lib/openat.c new file mode 100644 index 000000000..fa617b807 --- /dev/null +++ b/lib/openat.c @@ -0,0 +1,91 @@ +/* provide a replacement openat function + Copyright (C) 2004 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 2, 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, write to the Free Software Foundation, + Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + +/* written by Jim Meyering */ + +#include + +#include "openat.h" + +#include +#include +#include +#include +#include + +#include "error.h" +#include "exitfail.h" +#include "save-cwd.h" + +#include "gettext.h" +#define _(msgid) gettext (msgid) + +/* Replacement for Solaris' openat function. + + Simulate it by doing save_cwd/fchdir/open/restore_cwd. + If either the save_cwd or the restore_cwd fails (relatively unlikely, + and usually indicative of a problem that deserves close attention), + then give a diagnostic and exit nonzero. + Otherwise, upon failure, set errno and return -1, as openat does. + Upon successful completion, return a file descriptor. */ +int +rpl_openat (int fd, char const *filename, int flags, ...) +{ + struct saved_cwd saved_cwd; + int saved_errno; + int new_fd; + mode_t mode = 0; + + if (flags & O_CREAT) + { + va_list arg; + va_start (arg, flags); + + /* Assume that mode_t is passed compatibly with mode_t's type + after argument promotion. */ + mode = va_arg (arg, mode_t); + + va_end (arg); + } + + if (fd == AT_FDCWD || *filename == '/') + return open (filename, flags, mode); + + if (save_cwd (&saved_cwd) != 0) + error (exit_failure, errno, + _("openat: unable to record current working directory")); + + if (fchdir (fd) != 0) + { + saved_errno = errno; + free_cwd (&saved_cwd); + errno = saved_errno; + return -1; + } + + new_fd = open (filename, flags, mode); + saved_errno = errno; + + if (restore_cwd (&saved_cwd) != 0) + error (exit_failure, errno, + _("openat: unable to restore working directory")); + + free_cwd (&saved_cwd); + + errno = saved_errno; + return new_fd; +} diff --git a/lib/openat.h b/lib/openat.h new file mode 100644 index 000000000..74f5508db --- /dev/null +++ b/lib/openat.h @@ -0,0 +1,35 @@ +/* provide a replacement openat function + Copyright (C) 2004 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 2, 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, write to the Free Software Foundation, + Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + +/* written by Jim Meyering */ + +#if HAVE_FCNTL_H +# include +#endif + +#ifndef AT_FDCWD +# define AT_FDCWD (-3041965) /* same value as Solaris 9 */ + +# ifdef __OPENAT_PREFIX +# undef openat +# define __OPENAT_CONCAT(x, y) x ## y +# define __OPENAT_XCONCAT(x, y) __OPENAT_CONCAT (x, y) +# define __OPENAT_ID(y) __OPENAT_XCONCAT (__OPENAT_PREFIX, y) +# define openat __OPENAT_ID (openat) +int openat (int fd, char const *filename, int flags, /* mode_t mode */ ...); +# endif +#endif diff --git a/lib/save-cwd.c b/lib/save-cwd.c index 93b1fe85c..272f7421e 100644 --- a/lib/save-cwd.c +++ b/lib/save-cwd.c @@ -21,6 +21,8 @@ # include "config.h" #endif +#include "save-cwd.h" + #include #include #include @@ -37,11 +39,7 @@ #include -#ifndef O_DIRECTORY -# define O_DIRECTORY 0 -#endif - -#include "save-cwd.h" +#include "chdir-long.h" #include "xgetcwd.h" /* Record the location of the current working directory in CWD so that @@ -64,48 +62,49 @@ int save_cwd (struct saved_cwd *cwd) { +#if !HAVE_FCHDIR +# undef fchdir +# define fchdir(x) (abort (), 0) + bool have_working_fchdir = false; + bool fchdir_needs_testing = false; +#elif (__sgi || __sun) static bool have_working_fchdir = true; + bool fchdir_needs_testing = true; +#else + bool have_working_fchdir = true; + bool fchdir_needs_testing = false; +#endif cwd->desc = -1; cwd->name = NULL; if (have_working_fchdir) { -#if HAVE_FCHDIR - cwd->desc = open (".", O_RDONLY | O_DIRECTORY); - if (cwd->desc < 0) - cwd->desc = open (".", O_WRONLY | O_DIRECTORY); + cwd->desc = open (".", O_RDONLY); if (cwd->desc < 0) { - cwd->name = xgetcwd (); - return cwd->name ? 0 : -1; + cwd->desc = open (".", O_WRONLY); + if (cwd->desc < 0) + { + cwd->name = xgetcwd (); + return cwd->name ? 0 : -1; + } } -# if __sun__ || sun /* On SunOS 4 and IRIX 5.3, fchdir returns EINVAL when auditing is enabled, so we have to fall back to chdir. */ - if (fchdir (cwd->desc)) + if (fchdir_needs_testing && fchdir (cwd->desc) != 0) { - if (errno == EINVAL) - { - close (cwd->desc); - cwd->desc = -1; - have_working_fchdir = false; - } - else + int saved_errno = errno; + close (cwd->desc); + cwd->desc = -1; + if (saved_errno != EINVAL) { - int saved_errno = errno; - close (cwd->desc); - cwd->desc = -1; errno = saved_errno; return -1; } + have_working_fchdir = false; } -# endif /* __sun__ || sun */ -#else -# define fchdir(x) (abort (), 0) - have_working_fchdir = false; -#endif } if (!have_working_fchdir) @@ -127,7 +126,7 @@ restore_cwd (const struct saved_cwd *cwd) if (0 <= cwd->desc) return fchdir (cwd->desc); else - return chdir (cwd->name); + return chdir_long (cwd->name); } void diff --git a/m4/chdir-long.m4 b/m4/chdir-long.m4 new file mode 100644 index 000000000..eef75d767 --- /dev/null +++ b/m4/chdir-long.m4 @@ -0,0 +1,41 @@ +#serial 4 + +# Use Gnulib's robust chdir function. +# It can handle arbitrarily long directory names, which means +# that when it is given the name of an existing directory, it +# never fails with ENAMETOOLONG. +# Arrange to compile chdir-long.c only on systems that define PATH_MAX. + +dnl Copyright (C) 2004 Free Software Foundation, Inc. +dnl This file is free software; the Free Software Foundation +dnl gives unlimited permission to copy and/or distribute it, +dnl with or without modifications, as long as this notice is preserved. + +# Written by Jim Meyering. + +AC_DEFUN([gl_FUNC_CHDIR_LONG], +[ + AC_LIBSOURCES([chdir-long.c, chdir-long.h]) + AC_CACHE_CHECK([whether this system has a definition of PATH_MAX], + gl_have_path_max_definition, + [AC_EGREP_CPP([have_path_max_definition], + [#include +#include +#ifdef PATH_MAX +have_path_max_definition +#endif], + gl_have_path_max_definition=yes, + gl_have_path_max_definition=no)]) + + if test $gl_have_path_max_definition; then + AC_LIBOBJ([chdir-long]) + gl_PREREQ_CHDIR_LONG + fi +]) + +AC_DEFUN([gl_PREREQ_CHDIR_LONG], +[ + AM_STDBOOL_H + gl_FUNC_MEMPCPY + gl_FUNC_OPENAT +]) diff --git a/m4/openat.m4 b/m4/openat.m4 new file mode 100644 index 000000000..27b0e59e6 --- /dev/null +++ b/m4/openat.m4 @@ -0,0 +1,28 @@ +#serial 3 +# See if we need to use our replacement for Solaris' openat function. + +dnl Copyright (C) 2004 Free Software Foundation, Inc. +dnl This file is free software; the Free Software Foundation +dnl gives unlimited permission to copy and/or distribute it, +dnl with or without modifications, as long as this notice is preserved. + +# Written by Jim Meyering. + +AC_DEFUN([gl_FUNC_OPENAT], +[ + AC_LIBSOURCES([openat.c, openat.h]) + AC_REQUIRE([gl_USE_SYSTEM_EXTENSIONS]) + AC_REPLACE_FUNCS(openat) + case $ac_cv_func_openat in + yes) ;; + *) + AC_DEFINE([__OPENAT_PREFIX], [[rpl_]], + [Define to rpl_ if the openat replacement function should be used.]) + gl_PREREQ_OPENAT;; + esac +]) + +AC_DEFUN([gl_PREREQ_OPENAT], +[ + AC_REQUIRE([gl_SAVE_CWD]) +]) diff --git a/m4/save-cwd.m4 b/m4/save-cwd.m4 index f12722281..79173c212 100644 --- a/m4/save-cwd.m4 +++ b/m4/save-cwd.m4 @@ -1,5 +1,5 @@ -# save-cwd.m4 serial 2 -dnl Copyright (C) 2002, 2003 Free Software Foundation, Inc. +# save-cwd.m4 serial 3 +dnl Copyright (C) 2002, 2003, 2004 Free Software Foundation, Inc. dnl This file is free software, distributed under the terms of the GNU dnl General Public License. As a special exception to the GNU General dnl Public License, this file may be distributed as part of a program @@ -8,6 +8,8 @@ dnl the same distribution terms as the rest of that program. AC_DEFUN([gl_SAVE_CWD], [ + AC_LIBSOURCES([save-cwd.c, save-cwd.h]) + AC_LIBOBJ([save-cwd]) dnl Prerequisites for lib/save-cwd.c. AC_CHECK_HEADERS_ONCE(fcntl.h unistd.h) AC_CHECK_FUNCS(fchdir) diff --git a/modules/chdir-long b/modules/chdir-long new file mode 100644 index 000000000..2e6d823dd --- /dev/null +++ b/modules/chdir-long @@ -0,0 +1,25 @@ +Description: +chdir-like function that tries not to fail due to ENAMETOOLONG + +Files: +lib/chdir-long.h +lib/chdir-long.c +m4/chdir-long.m4 + +Depends-on: +openat +mempcpy + +configure.ac: +gl_FUNC_CHDIR_LONG + +Makefile.am: + +Include: +"chdir-long.h" + +License: +GPL + +Maintainer: +Jim Meyering diff --git a/modules/openat b/modules/openat new file mode 100644 index 000000000..7366aec5d --- /dev/null +++ b/modules/openat @@ -0,0 +1,27 @@ +Description: +Open a file at a directory. + +Files: +lib/openat.c +lib/openat.h +m4/openat.m4 + +Depends-on: +save-cwd +gettext +error +exitfail + +configure.ac: +gl_FUNC_OPENAT + +Makefile.am: + +Include: +"openat.h" + +License: +GPL + +Maintainer: +Jim Meyering diff --git a/modules/save-cwd b/modules/save-cwd index 67f226df3..405ead8ed 100644 --- a/modules/save-cwd +++ b/modules/save-cwd @@ -7,13 +7,13 @@ lib/save-cwd.c m4/save-cwd.m4 Depends-on: +chdir-long xgetcwd configure.ac: gl_SAVE_CWD Makefile.am: -lib_SOURCES += save-cwd.h save-cwd.c Include: "save-cwd.h" -- 2.11.0