summaryrefslogtreecommitdiff
path: root/src/pwd.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/pwd.c')
-rw-r--r--src/pwd.c211
1 files changed, 141 insertions, 70 deletions
diff --git a/src/pwd.c b/src/pwd.c
index 4f16b73..e004098 100644
--- a/src/pwd.c
+++ b/src/pwd.c
@@ -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;
}