diff options
Diffstat (limited to 'src/pwd.c')
-rw-r--r-- | src/pwd.c | 211 |
1 files changed, 141 insertions, 70 deletions
@@ -1,10 +1,10 @@ /* pwd - print current directory - Copyright (C) 1994-1997, 1999-2006 Free Software Foundation, Inc. + Copyright (C) 1994-2016 Free Software Foundation, Inc. - This program is free software; you can redistribute it and/or modify + 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. + 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 @@ -12,8 +12,7 @@ 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + along with this program. If not, see <http://www.gnu.org/licenses/>. */ #include <config.h> #include <getopt.h> @@ -21,17 +20,15 @@ #include <sys/types.h> #include "system.h" -#include "dirfd.h" #include "error.h" -#include "long-options.h" #include "quote.h" #include "root-dev-ino.h" #include "xgetcwd.h" -/* The official name of this program (e.g., no `g' prefix). */ +/* The official name of this program (e.g., no 'g' prefix). */ #define PROGRAM_NAME "pwd" -#define AUTHORS "Jim Meyering" +#define AUTHORS proper_name ("Jim Meyering") struct file_name { @@ -40,26 +37,38 @@ struct file_name char *start; }; -/* The name this program was run with. */ -char *program_name; +static struct option const longopts[] = +{ + {"logical", no_argument, NULL, 'L'}, + {"physical", no_argument, NULL, 'P'}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {NULL, 0, NULL, 0} +}; void usage (int status) { if (status != EXIT_SUCCESS) - fprintf (stderr, _("Try `%s --help' for more information.\n"), - program_name); + emit_try_help (); else { - printf (_("Usage: %s [OPTION]\n"), program_name); + printf (_("Usage: %s [OPTION]...\n"), program_name); fputs (_("\ Print the full filename of the current working directory.\n\ \n\ "), stdout); + fputs (_("\ + -L, --logical use PWD from environment, even if it contains symlinks\n\ + -P, --physical avoid all symlinks\n\ +"), stdout); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); + fputs (_("\n\ +If no option is specified, -P is assumed.\n\ +"), stdout); printf (USAGE_BUILTIN_WARNING, PROGRAM_NAME); - printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + emit_ancillary_info (PROGRAM_NAME); } exit (status); } @@ -95,9 +104,9 @@ file_name_prepend (struct file_name *p, char const *s, size_t s_len) { size_t half = p->n_alloc + 1 + s_len; /* Use xnmalloc+free rather than xnrealloc, since with the latter - we'd end up copying the data twice: once via realloc, then again - to align it with the end of the new buffer. With xnmalloc, we - copy it only once. */ + we'd end up copying the data twice: once via realloc, then again + to align it with the end of the new buffer. With xnmalloc, we + copy it only once. */ char *q = xnmalloc (2, half); size_t n_used = p->n_alloc - n_free; p->start = q + 2 * half - n_used; @@ -112,7 +121,7 @@ file_name_prepend (struct file_name *p, char const *s, size_t s_len) memcpy (p->start + 1, s, s_len); } -/* Return a string (malloc'd) consisting of N `/'-separated ".." components. */ +/* Return a string (malloc'd) consisting of N '/'-separated ".." components. */ static char * nth_parent (size_t n) { @@ -131,18 +140,18 @@ nth_parent (size_t n) /* Determine the basename of the current directory, where DOT_SB is the result of lstat'ing "." and prepend that to the file name in *FILE_NAME. - Find the directory entry in `..' that matches the dev/i-node of DOT_SB. - Upon success, update *DOT_SB with stat information of `..', chdir to `..', + Find the directory entry in '..' that matches the dev/i-node of DOT_SB. + Upon success, update *DOT_SB with stat information of '..', chdir to '..', and prepend "/basename" to FILE_NAME. Otherwise, exit with a diagnostic. - PARENT_HEIGHT is the number of levels `..' is above the starting directory. + PARENT_HEIGHT is the number of levels '..' is above the starting directory. The first time this function is called (from the initial directory), PARENT_HEIGHT is 1. This is solely for diagnostics. Exit nonzero upon error. */ static void find_dir_entry (struct stat *dot_sb, struct file_name *file_name, - size_t parent_height) + size_t parent_height) { DIR *dirp; int fd; @@ -153,16 +162,16 @@ find_dir_entry (struct stat *dot_sb, struct file_name *file_name, dirp = opendir (".."); if (dirp == NULL) error (EXIT_FAILURE, errno, _("cannot open directory %s"), - quote (nth_parent (parent_height))); + quote (nth_parent (parent_height))); fd = dirfd (dirp); if ((0 <= fd ? fchdir (fd) : chdir ("..")) < 0) error (EXIT_FAILURE, errno, _("failed to chdir to %s"), - quote (nth_parent (parent_height))); + quote (nth_parent (parent_height))); if ((0 <= fd ? fstat (fd, &parent_sb) : stat (".", &parent_sb)) < 0) error (EXIT_FAILURE, errno, _("failed to stat %s"), - quote (nth_parent (parent_height))); + quote (nth_parent (parent_height))); /* If parent and child directory are on different devices, then we can't rely on d_ino for useful i-node numbers; use lstat instead. */ @@ -177,57 +186,57 @@ find_dir_entry (struct stat *dot_sb, struct file_name *file_name, errno = 0; if ((dp = readdir_ignoring_dot_and_dotdot (dirp)) == NULL) - { - if (errno) - { - /* Save/restore errno across closedir call. */ - int e = errno; - closedir (dirp); - errno = e; - - /* Arrange to give a diagnostic after exiting this loop. */ - dirp = NULL; - } - break; - } + { + if (errno) + { + /* Save/restore errno across closedir call. */ + int e = errno; + closedir (dirp); + errno = e; + + /* Arrange to give a diagnostic after exiting this loop. */ + dirp = NULL; + } + break; + } ino = D_INO (dp); if (ino == NOT_AN_INODE_NUMBER || use_lstat) - { - if (lstat (dp->d_name, &ent_sb) < 0) - { - /* Skip any entry we can't stat. */ - continue; - } - ino = ent_sb.st_ino; - } + { + if (lstat (dp->d_name, &ent_sb) < 0) + { + /* Skip any entry we can't stat. */ + continue; + } + ino = ent_sb.st_ino; + } if (ino != dot_sb->st_ino) - continue; + continue; /* If we're not crossing a device boundary, then a simple i-node - match is enough. */ + match is enough. */ if ( ! use_lstat || ent_sb.st_dev == dot_sb->st_dev) - { - file_name_prepend (file_name, dp->d_name, _D_EXACT_NAMLEN (dp)); - found = true; - break; - } + { + file_name_prepend (file_name, dp->d_name, _D_EXACT_NAMLEN (dp)); + found = true; + break; + } } if (dirp == NULL || closedir (dirp) != 0) { /* Note that this diagnostic serves for both readdir - and closedir failures. */ + and closedir failures. */ error (EXIT_FAILURE, errno, _("reading directory %s"), - quote (nth_parent (parent_height))); + quote (nth_parent (parent_height))); } if ( ! found) error (EXIT_FAILURE, 0, - _("couldn't find directory entry in %s with matching i-node"), - quote (nth_parent (parent_height))); + _("couldn't find directory entry in %s with matching i-node"), + quote (nth_parent (parent_height))); *dot_sb = parent_sb; } @@ -237,7 +246,7 @@ find_dir_entry (struct stat *dot_sb, struct file_name *file_name, The getcwd function performs nearly the same task, but is typically unable to handle names longer than PATH_MAX. This function has no such limitation. However, this function *can* fail due to - permission problems or a lack of memory, while Linux's getcwd + permission problems or a lack of memory, while GNU/Linux's getcwd function works regardless of restricted permissions on parent directories. Upon failure, give a diagnostic and exit nonzero. @@ -248,7 +257,7 @@ find_dir_entry (struct stat *dot_sb, struct file_name *file_name, the information the caller would require in order to produce good diagnostics, it doesn't seem worth the added complexity. In any case, any getcwd replacement must *not* exceed the PATH_MAX - limitation. Otherwise, functions like `chdir' would fail with + limitation. Otherwise, functions like 'chdir' would fail with ENAMETOOLONG. FIXME-maybe: if find_dir_entry fails due to permissions, try getcwd, @@ -265,16 +274,16 @@ robust_getcwd (struct file_name *file_name) if (root_dev_ino == NULL) error (EXIT_FAILURE, errno, _("failed to get attributes of %s"), - quote ("/")); + quoteaf ("/")); if (stat (".", &dot_sb) < 0) - error (EXIT_FAILURE, errno, _("failed to stat %s"), quote (".")); + error (EXIT_FAILURE, errno, _("failed to stat %s"), quoteaf (".")); while (1) { /* If we've reached the root, we're done. */ if (SAME_INODE (dot_sb, *root_dev_ino)) - break; + break; find_dir_entry (&dot_sb, file_name, height++); } @@ -284,27 +293,89 @@ robust_getcwd (struct file_name *file_name) file_name_prepend (file_name, "", 0); } + +/* Return PWD from the environment if it is acceptable for 'pwd -L' + output, otherwise NULL. */ +static char * +logical_getcwd (void) +{ + struct stat st1; + struct stat st2; + char *wd = getenv ("PWD"); + char *p; + + /* Textual validation first. */ + if (!wd || wd[0] != '/') + return NULL; + p = wd; + while ((p = strstr (p, "/."))) + { + if (!p[2] || p[2] == '/' + || (p[2] == '.' && (!p[3] || p[3] == '/'))) + return NULL; + p++; + } + + /* System call validation. */ + if (stat (wd, &st1) == 0 && stat (".", &st2) == 0 && SAME_INODE (st1, st2)) + return wd; + return NULL; +} + + int main (int argc, char **argv) { char *wd; + /* POSIX requires a default of -L, but most scripts expect -P. + Currently shells default to -L, while stand-alone + pwd implementations default to -P. */ + bool logical = (getenv ("POSIXLY_CORRECT") != NULL); initialize_main (&argc, &argv); - program_name = argv[0]; + set_program_name (argv[0]); setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); atexit (close_stdout); - parse_long_options (argc, argv, PROGRAM_NAME, GNU_PACKAGE, VERSION, - usage, AUTHORS, (char const *) NULL); - if (getopt_long (argc, argv, "", NULL, NULL) != -1) - usage (EXIT_FAILURE); + while (1) + { + int c = getopt_long (argc, argv, "LP", longopts, NULL); + if (c == -1) + break; + switch (c) + { + case 'L': + logical = true; + break; + case 'P': + logical = false; + break; + + case_GETOPT_HELP_CHAR; + + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + + default: + usage (EXIT_FAILURE); + } + } if (optind < argc) error (0, 0, _("ignoring non-option arguments")); + if (logical) + { + wd = logical_getcwd (); + if (wd) + { + puts (wd); + return EXIT_SUCCESS; + } + } + wd = xgetcwd (); if (wd != NULL) { @@ -319,5 +390,5 @@ main (int argc, char **argv) file_name_free (file_name); } - exit (EXIT_SUCCESS); + return EXIT_SUCCESS; } |