From 6528cac57d999d924da1a4fdf64a487361164668 Mon Sep 17 00:00:00 2001 From: Paul Eggert Date: Sat, 22 Jan 2005 05:37:06 +0000 Subject: [PATCH] * modules/chdir-long (Depends-on): Remove mempcpy. * lib/openat.h (AT_SYMLINK_NOFOLLOW): Define to 4096, so it's the same value as for Solaris 9. * lib/chdir-long.c (chdir_long): Rewrite to remove limitation on component length. This included changing the parameter to be of type `char *' rather than `char const *'. * lib/chdir-long.h (chdir_long): Update prototype. * lib/openat.c (fdopendir, fstatat): New functions. * lib/openat.h: Include headers required for use of DIR and struct stat. [AT_SYMLINK_NOFOLLOW]: Define. (fdopendir, fstatat): Add prototypes. --- ChangeLog | 4 + lib/ChangeLog | 15 +++ lib/chdir-long.c | 271 ++++++++++++++++++++--------------------------------- lib/chdir-long.h | 4 +- lib/openat.c | 90 +++++++++++++++++- lib/openat.h | 13 ++- modules/chdir-long | 1 - 7 files changed, 225 insertions(+), 173 deletions(-) diff --git a/ChangeLog b/ChangeLog index c0501a112..ff8fc9b15 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ +2005-01-21 Paul Eggert + + * modules/chdir-long (Depends-on): Remove mempcpy. + 2005-01-21 Bruno Haible * modules/classpath: New file. diff --git a/lib/ChangeLog b/lib/ChangeLog index 05858c2fd..2bf1e93b7 100644 --- a/lib/ChangeLog +++ b/lib/ChangeLog @@ -1,3 +1,18 @@ +2005-01-19 Jim Meyering + + * openat.h (AT_SYMLINK_NOFOLLOW): Define to 4096, so it's the + same value as for Solaris 9. + + * chdir-long.c (chdir_long): Rewrite to remove limitation on + component length. This included changing the parameter to be + of type `char *' rather than `char const *'. + * chdir-long.h (chdir_long): Update prototype. + + * openat.c (fdopendir, fstatat): New functions. + * openat.h: Include headers required for use of DIR and struct stat. + [AT_SYMLINK_NOFOLLOW]: Define. + (fdopendir, fstatat): Add prototypes. + 2005-01-21 Bruno Haible * classpath.h: New file, from GNU gettext. diff --git a/lib/chdir-long.c b/lib/chdir-long.c index 7e7fd9718..6c8d79f7a 100644 --- a/lib/chdir-long.c +++ b/lib/chdir-long.c @@ -1,5 +1,5 @@ /* provide a chdir function that tries not to fail due to ENAMETOOLONG - Copyright (C) 2004 Free Software Foundation, Inc. + Copyright (C) 2004, 2005 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 @@ -30,76 +30,47 @@ #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 +static inline 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 inline void +cdb_free (struct cd_buf const *cdb) +{ + if (0 <= cdb->fd) + { + bool close_fail = close (cdb->fd); + assert (! close_fail); + } +} + +/* Given a file descriptor of an open directory (or AT_FDCWD), CDB->fd, + try to open the CDB->fd-relative directory, DIR. If the open succeeds, + update CDB->fd with the resulting descriptor, close the incoming file + descriptor, and return zero. Upon failure, return -1 and set errno. */ static int cdb_advance_fd (struct cd_buf *cdb, char const *dir) { @@ -111,85 +82,37 @@ cdb_advance_fd (struct cd_buf *cdb, char const *dir) return -1; } - if (cdb->fd != AT_FDCWD) - close (cdb->fd); + cdb_free (cdb); cdb->fd = new_fd; return 0; } -static int -cdb_flush (struct cd_buf *cdb) +/* Return a pointer to the first non-slash in S. */ +static inline char * +find_non_slash (char const *s) { - 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; + size_t n_slash = strspn (s, "/"); + return (char *) s + n_slash; } -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). */ +/* This is a function much like chdir, but without the PATH_MAX limitation + on the length of the directory name. A significant difference is that + it must be able to modify (albeit only temporarily) the directory + name. It handles an arbitrarily long directory name by operating + on 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 save 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 + has length PATH_MAX or greater on systems that define PATH_MAX). */ int -chdir_long (char const *dir) +chdir_long (char *dir) { int e = chdir (dir); if (e == 0 || errno != ENAMETOOLONG) @@ -197,70 +120,79 @@ chdir_long (char const *dir) { size_t len = strlen (dir); - char const *dir_end = dir + len; - char const *d; + char *dir_end = dir + len; struct cd_buf cdb; + size_t n_leading_slash; cdb_init (&cdb); /* If DIR is the empty string, then the chdir above must have failed and set errno to ENOENT. */ assert (0 < len); + assert (PATH_MAX <= len); + + /* Count leading slashes. */ + n_leading_slash = strspn (dir, "/"); - if (*dir == '/') + /* Handle any leading slashes as well as any name that matches + the regular expression, m!^//hostname[/]*! . Handling this + prefix separately usually results in a single additional + cdb_advance_fd call, but it's worthwhile, since it makes the + code in the following loop cleaner. */ + if (n_leading_slash == 2) { - /* 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] != '/') + int err; + /* Find next slash. + We already know that dir[2] is neither a slash nor '\0'. */ + char *slash = memchr (dir + 3, '/', dir_end - (dir + 3)); + if (slash == NULL) { - 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; + errno = ENAMETOOLONG; + return -1; } + *slash = '\0'; + err = cdb_advance_fd (&cdb, dir); + *slash = '/'; + if (err != 0) + goto Fail; + dir = find_non_slash (slash + 1); } - else + else if (n_leading_slash) { - d = dir; + if (cdb_advance_fd (&cdb, "/") != 0) + goto Fail; + dir += n_leading_slash; } - while (1) + assert (*dir != '/'); + assert (dir <= dir_end); + + while (PATH_MAX <= dir_end - dir) { - /* 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) + int err; + /* Find a slash that is PATH_MAX or fewer bytes away from dir. + I.e. see if there is a slash that will give us a name of + length PATH_MAX-1 or less. */ + char *slash = memrchr (dir, '/', PATH_MAX); + if (slash == NULL) { - if (cdb_flush (&cdb) != 0 - || cdb_advance_fd (&cdb, start) != 0) - goto Fail; - break; + errno = ENAMETOOLONG; + return -1; } - len = memchrcspn (start, '/', dir_end - start); - assert (len == strcspn (start, "/")); - d = start + len; - if (cdb_append (&cdb, start, len) != 0) + *slash = '\0'; + assert (slash - dir < PATH_MAX); + err = cdb_advance_fd (&cdb, dir); + *slash = '/'; + if (err != 0) + goto Fail; + + dir = find_non_slash (slash + 1); + } + + if (dir < dir_end) + { + if (cdb_advance_fd (&cdb, dir) != 0) goto Fail; } @@ -318,16 +250,19 @@ main (int argc, char *argv[]) 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); - } + if (argc <= 1) + { + /* 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); + } + + fclose (stdin); + fclose (stderr); - /* not reached */ - abort (); + exit (EXIT_SUCCESS); } #endif diff --git a/lib/chdir-long.h b/lib/chdir-long.h index 47fa79160..56c7bfdc6 100644 --- a/lib/chdir-long.h +++ b/lib/chdir-long.h @@ -1,5 +1,5 @@ /* provide a chdir function that tries not to fail due to ENAMETOOLONG - Copyright (C) 2004 Free Software Foundation, Inc. + Copyright (C) 2004, 2005 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 @@ -31,5 +31,5 @@ #ifndef PATH_MAX # define chdir_long(Dir) chdir (Dir) #else -int chdir_long (char const *dir); +int chdir_long (char *dir); #endif diff --git a/lib/openat.c b/lib/openat.c index fa617b807..14d138ced 100644 --- a/lib/openat.c +++ b/lib/openat.c @@ -1,5 +1,5 @@ /* provide a replacement openat function - Copyright (C) 2004 Free Software Foundation, Inc. + Copyright (C) 2004, 2005 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 @@ -89,3 +89,91 @@ rpl_openat (int fd, char const *filename, int flags, ...) errno = saved_errno; return new_fd; } + +/* Replacement for Solaris' function by the same name. + + Simulate it by doing save_cwd/fchdir/opendir(".")/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, this function works just like Solaris' fdopendir. */ +DIR * +fdopendir (int fd) +{ + struct saved_cwd saved_cwd; + int saved_errno; + DIR *dir; + + if (fd == AT_FDCWD) + return opendir ("."); + + if (save_cwd (&saved_cwd) != 0) + error (exit_failure, errno, + _("fdopendir: unable to record current working directory")); + + if (fchdir (fd) != 0) + { + saved_errno = errno; + free_cwd (&saved_cwd); + errno = saved_errno; + return NULL; + } + + dir = opendir ("."); + saved_errno = errno; + + if (restore_cwd (&saved_cwd) != 0) + error (exit_failure, errno, + _("fdopendir: unable to restore working directory")); + + free_cwd (&saved_cwd); + + errno = saved_errno; + return dir; +} + +/* Replacement for Solaris' function by the same name. + + Simulate it by doing save_cwd/fchdir/(stat|lstat)/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, this function works just like Solaris' fstatat. */ +int +fstatat (int fd, char const *filename, struct stat *st, int flag) +{ + struct saved_cwd saved_cwd; + int saved_errno; + int err; + + if (fd == AT_FDCWD) + return (flag == AT_SYMLINK_NOFOLLOW + ? lstat (filename, st) + : stat (filename, st)); + + if (save_cwd (&saved_cwd) != 0) + error (exit_failure, errno, + _("fstatat: unable to record current working directory")); + + if (fchdir (fd) != 0) + { + saved_errno = errno; + free_cwd (&saved_cwd); + errno = saved_errno; + return -1; + } + + err = (flag == AT_SYMLINK_NOFOLLOW + ? lstat (filename, st) + : stat (filename, st)); + saved_errno = errno; + + if (restore_cwd (&saved_cwd) != 0) + error (exit_failure, errno, + _("fstatat: unable to restore working directory")); + + free_cwd (&saved_cwd); + + errno = saved_errno; + return err; +} diff --git a/lib/openat.h b/lib/openat.h index 74f5508db..2c9366343 100644 --- a/lib/openat.h +++ b/lib/openat.h @@ -1,5 +1,5 @@ /* provide a replacement openat function - Copyright (C) 2004 Free Software Foundation, Inc. + Copyright (C) 2004, 2005 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 @@ -21,8 +21,14 @@ # include #endif +#include +#include +#include +#include + #ifndef AT_FDCWD # define AT_FDCWD (-3041965) /* same value as Solaris 9 */ +# define AT_SYMLINK_NOFOLLOW 4096 /* same value as Solaris 9 */ # ifdef __OPENAT_PREFIX # undef openat @@ -31,5 +37,10 @@ # 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 */ ...); +# define fdopendir __OPENAT_ID (fdopendir) +DIR *fdopendir (int fd); +# define fstatat __OPENAT_ID (fstatat) +int fstatat (int fd, char const *filename, struct stat *st, int flag); # endif + #endif diff --git a/modules/chdir-long b/modules/chdir-long index 2e6d823dd..c82f5fcbe 100644 --- a/modules/chdir-long +++ b/modules/chdir-long @@ -8,7 +8,6 @@ m4/chdir-long.m4 Depends-on: openat -mempcpy configure.ac: gl_FUNC_CHDIR_LONG -- 2.11.0