mirror of
git://git.sv.gnu.org/coreutils.git
synced 2026-03-06 21:12:38 +02:00
(struct file_name): Renamed from struct Path. All uses changed. (file_name_free): Renamed from path_free. All uses changed. (file_name_init): Renamed from path_init. All uses changed. (file_name_prepend): Renamed from path_prepend. All uses changed.
338 lines
9.1 KiB
C
338 lines
9.1 KiB
C
/* pwd - print current directory
|
|
Copyright (C) 1994-1997, 1999-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
|
|
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;
|
|
};
|
|
|
|
enum
|
|
{
|
|
NOT_AN_INODE_NUMBER = 0
|
|
};
|
|
|
|
#ifdef D_INO_IN_DIRENT
|
|
# define D_INO(dp) ((dp)->d_ino)
|
|
#else
|
|
/* Some systems don't have inodes, so fake them to avoid lots of ifdefs. */
|
|
# define D_INO(dp) NOT_AN_INODE_NUMBER
|
|
#endif
|
|
|
|
/* 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;
|
|
bool ent_sb_valid;
|
|
|
|
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);
|
|
|
|
ent_sb_valid = false;
|
|
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;
|
|
ent_sb_valid = true;
|
|
}
|
|
|
|
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, NLENGTH (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++);
|
|
}
|
|
|
|
if (file_name->start[0] == '\0')
|
|
file_name_prepend (file_name, "/", 1);
|
|
}
|
|
|
|
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);
|
|
}
|