summaryrefslogtreecommitdiff
path: root/src/pwd.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/pwd.c')
-rw-r--r--src/pwd.c323
1 files changed, 323 insertions, 0 deletions
diff --git a/src/pwd.c b/src/pwd.c
new file mode 100644
index 0000000..4f16b73
--- /dev/null
+++ b/src/pwd.c
@@ -0,0 +1,323 @@
+/* pwd - print current directory
+ Copyright (C) 1994-1997, 1999-2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
+
+#include <config.h>
+#include <getopt.h>
+#include <stdio.h>
+#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). */
+#define PROGRAM_NAME "pwd"
+
+#define AUTHORS "Jim Meyering"
+
+struct file_name
+{
+ char *buf;
+ size_t n_alloc;
+ char *start;
+};
+
+/* The name this program was run with. */
+char *program_name;
+
+void
+usage (int status)
+{
+ if (status != EXIT_SUCCESS)
+ fprintf (stderr, _("Try `%s --help' for more information.\n"),
+ program_name);
+ else
+ {
+ printf (_("Usage: %s [OPTION]\n"), program_name);
+ fputs (_("\
+Print the full filename of the current working directory.\n\
+\n\
+"), stdout);
+ fputs (HELP_OPTION_DESCRIPTION, stdout);
+ fputs (VERSION_OPTION_DESCRIPTION, stdout);
+ printf (USAGE_BUILTIN_WARNING, PROGRAM_NAME);
+ printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
+ }
+ exit (status);
+}
+
+static void
+file_name_free (struct file_name *p)
+{
+ free (p->buf);
+ free (p);
+}
+
+static struct file_name *
+file_name_init (void)
+{
+ struct file_name *p = xmalloc (sizeof *p);
+
+ /* Start with a buffer larger than PATH_MAX, but beware of systems
+ on which PATH_MAX is very large -- e.g., INT_MAX. */
+ p->n_alloc = MIN (2 * PATH_MAX, 32 * 1024);
+
+ p->buf = xmalloc (p->n_alloc);
+ p->start = p->buf + (p->n_alloc - 1);
+ p->start[0] = '\0';
+ return p;
+}
+
+/* Prepend the name S of length S_LEN, to the growing file_name, P. */
+static void
+file_name_prepend (struct file_name *p, char const *s, size_t s_len)
+{
+ size_t n_free = p->start - p->buf;
+ if (n_free < 1 + 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. */
+ char *q = xnmalloc (2, half);
+ size_t n_used = p->n_alloc - n_free;
+ p->start = q + 2 * half - n_used;
+ memcpy (p->start, p->buf + n_free, n_used);
+ free (p->buf);
+ p->buf = q;
+ p->n_alloc = 2 * half;
+ }
+
+ p->start -= 1 + s_len;
+ p->start[0] = '/';
+ memcpy (p->start + 1, s, s_len);
+}
+
+/* Return a string (malloc'd) consisting of N `/'-separated ".." components. */
+static char *
+nth_parent (size_t n)
+{
+ char *buf = xnmalloc (3, n);
+ char *p = buf;
+ size_t i;
+
+ for (i = 0; i < n; i++)
+ {
+ memcpy (p, "../", 3);
+ p += 3;
+ }
+ p[-1] = '\0';
+ return buf;
+}
+
+/* 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 `..',
+ and prepend "/basename" to FILE_NAME.
+ Otherwise, exit with a diagnostic.
+ 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)
+{
+ DIR *dirp;
+ int fd;
+ struct stat parent_sb;
+ bool use_lstat;
+ bool found;
+
+ dirp = opendir ("..");
+ if (dirp == NULL)
+ error (EXIT_FAILURE, errno, _("cannot open directory %s"),
+ 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)));
+
+ if ((0 <= fd ? fstat (fd, &parent_sb) : stat (".", &parent_sb)) < 0)
+ error (EXIT_FAILURE, errno, _("failed to stat %s"),
+ 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. */
+ use_lstat = (parent_sb.st_dev != dot_sb->st_dev);
+
+ found = false;
+ while (1)
+ {
+ struct dirent const *dp;
+ struct stat ent_sb;
+ ino_t ino;
+
+ 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;
+ }
+
+ 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 (ino != dot_sb->st_ino)
+ continue;
+
+ /* If we're not crossing a device boundary, then a simple i-node
+ 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;
+ }
+ }
+
+ if (dirp == NULL || closedir (dirp) != 0)
+ {
+ /* Note that this diagnostic serves for both readdir
+ and closedir failures. */
+ error (EXIT_FAILURE, errno, _("reading directory %s"),
+ 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)));
+
+ *dot_sb = parent_sb;
+}
+
+/* Construct the full, absolute name of the current working
+ directory and store it in *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
+ function works regardless of restricted permissions on parent
+ directories. Upon failure, give a diagnostic and exit nonzero.
+
+ Note: although this function is similar to getcwd, it has a fundamental
+ difference in that it gives a diagnostic and exits upon failure.
+ I would have liked a function that did not exit, and that could be
+ used as a getcwd replacement. Unfortunately, considering all of
+ 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
+ ENAMETOOLONG.
+
+ FIXME-maybe: if find_dir_entry fails due to permissions, try getcwd,
+ in case the unreadable directory is close enough to the root that
+ getcwd works from there. */
+
+static void
+robust_getcwd (struct file_name *file_name)
+{
+ size_t height = 1;
+ struct dev_ino dev_ino_buf;
+ struct dev_ino *root_dev_ino = get_root_dev_ino (&dev_ino_buf);
+ struct stat dot_sb;
+
+ if (root_dev_ino == NULL)
+ error (EXIT_FAILURE, errno, _("failed to get attributes of %s"),
+ quote ("/"));
+
+ if (stat (".", &dot_sb) < 0)
+ error (EXIT_FAILURE, errno, _("failed to stat %s"), quote ("."));
+
+ while (1)
+ {
+ /* If we've reached the root, we're done. */
+ if (SAME_INODE (dot_sb, *root_dev_ino))
+ break;
+
+ find_dir_entry (&dot_sb, file_name, height++);
+ }
+
+ /* See if a leading slash is needed; file_name_prepend adds one. */
+ if (file_name->start[0] == '\0')
+ file_name_prepend (file_name, "", 0);
+}
+
+int
+main (int argc, char **argv)
+{
+ char *wd;
+
+ initialize_main (&argc, &argv);
+ 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);
+
+ if (optind < argc)
+ error (0, 0, _("ignoring non-option arguments"));
+
+ wd = xgetcwd ();
+ if (wd != NULL)
+ {
+ puts (wd);
+ free (wd);
+ }
+ else
+ {
+ struct file_name *file_name = file_name_init ();
+ robust_getcwd (file_name);
+ puts (file_name->start);
+ file_name_free (file_name);
+ }
+
+ exit (EXIT_SUCCESS);
+}