/* pwd - print current directory This is the pwd utility
Copyright (C) 1994-2018 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 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
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, see <https://www.gnu.org/licenses/>. */ The GNUv3 license
#include <config.h> Provides system specific information
#include <getopt.h> ...!includes auto-comment...
#include <stdio.h> Provides standard I/O capability
#include <sys/types.h> Provides system data types
#include "system.h" ...!includes auto-comment...
#include "die.h" ...!includes auto-comment...
#include "error.h" ...!includes auto-comment...
#include "quote.h" ...!includes auto-comment...
#include "root-dev-ino.h" ...!includes auto-comment......!includes auto-comment...
#include "xgetcwd.h" ...!includes auto-comment...
/* The official name of this program (e.g., no 'g' prefix). */
#define PROGRAM_NAME "pwd" Line 30
#define AUTHORS proper_name ("Jim Meyering") Line 32
struct file_name Line 34
{
char *buf; Line 36
size_t n_alloc; Line 37
char *start; Line 38
}; Block 1
static struct option const longopts[] = Line 41
{
{"logical", no_argument, NULL, 'L'}, Line 43
{"physical", no_argument, NULL, 'P'}, Line 44
{GETOPT_HELP_OPTION_DECL}, Line 45
{GETOPT_VERSION_OPTION_DECL}, Line 46
{NULL, 0, NULL, 0} Line 47
}; Block 2
void Line 50
usage (int status) Line 51
{
if (status != EXIT_SUCCESS) Line 53
emit_try_help (); ...!common auto-comment...
else Line 55
{
printf (_("Usage: %s [OPTION]...\n"), program_name); Line 57
fputs (_("\ Line 58
Print the full filename of the current working directory.\n\ Line 59
\n\
"), stdout); Line 61
fputs (_("\ Line 62
-L, --logical use PWD from environment, even if it contains symlinks\n\ Line 63
-P, --physical avoid all symlinks\n\ Line 64
"), stdout); Line 65
fputs (HELP_OPTION_DESCRIPTION, stdout); Line 66
fputs (VERSION_OPTION_DESCRIPTION, stdout); Line 67
fputs (_("\n\ Line 68
If no option is specified, -P is assumed.\n\ Line 69
"), stdout); Line 70
printf (USAGE_BUILTIN_WARNING, PROGRAM_NAME); Line 71
emit_ancillary_info (PROGRAM_NAME); Line 72
}
exit (status); Line 74
} Block 3
static void Line 77
file_name_free (struct file_name *p) Line 78
{
free (p->buf); Line 80
free (p); Line 81
} Block 4
static struct file_name * Line 84
file_name_init (void) Line 85
{
struct file_name *p = xmalloc (sizeof *p); Line 87
/* 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); Line 91
p->buf = xmalloc (p->n_alloc); Line 93
p->start = p->buf + (p->n_alloc - 1); Line 94
p->start[0] = '\0'; Line 95
return p; Line 96
} Block 5
/* Prepend the name S of length S_LEN, to the growing file_name, P. */
static void Line 100
file_name_prepend (struct file_name *p, char const *s, size_t s_len) Line 101
{
size_t n_free = p->start - p->buf; Line 103
if (n_free < 1 + s_len) Line 104
{
size_t half = p->n_alloc + 1 + s_len; Line 106
/* 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); Line 111
size_t n_used = p->n_alloc - n_free; Line 112
p->start = q + 2 * half - n_used; Line 113
memcpy (p->start, p->buf + n_free, n_used); Line 114
free (p->buf); Line 115
p->buf = q; Line 116
p->n_alloc = 2 * half; Line 117
}
p->start -= 1 + s_len; Line 120
p->start[0] = '/'; Line 121
memcpy (p->start + 1, s, s_len); Line 122
} Block 6
/* Return a string (malloc'd) consisting of N '/'-separated ".." components. */
static char * Line 126
nth_parent (size_t n) Line 127
{
char *buf = xnmalloc (3, n); Line 129
char *p = buf; Line 130
for (size_t i = 0; i < n; i++) Line 132
{
memcpy (p, "../", 3); Line 134
p += 3; Line 135
}
p[-1] = '\0'; Line 137
return buf; Line 138
} Block 7
/* 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 Line 152
find_dir_entry (struct stat *dot_sb, struct file_name *file_name, Line 153
size_t parent_height) Line 154
{
DIR *dirp; Line 156
int fd; Line 157
struct stat parent_sb; Line 158
bool use_lstat; Line 159
bool found; Line 160
dirp = opendir (".."); Line 162
if (dirp == NULL) Line 163
die (EXIT_FAILURE, errno, _("cannot open directory %s"), Line 164
quote (nth_parent (parent_height))); Line 165
fd = dirfd (dirp); Line 167
if ((0 <= fd ? fchdir (fd) : chdir ("..")) < 0) Line 168
die (EXIT_FAILURE, errno, _("failed to chdir to %s"), Line 169
quote (nth_parent (parent_height))); Line 170
if ((0 <= fd ? fstat (fd, &parent_sb) : stat (".", &parent_sb)) < 0) Line 172...!syscalls auto-comment......!syscalls auto-comment...
die (EXIT_FAILURE, errno, _("failed to stat %s"), Line 173
quote (nth_parent (parent_height))); Line 174
/* 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); Line 178
found = false; Line 180
while (1) Line 181
{
struct dirent const *dp; Line 183
struct stat ent_sb; Line 184
ino_t ino; Line 185
errno = 0; Line 187
if ((dp = readdir_ignoring_dot_and_dotdot (dirp)) == NULL) Line 188
{
if (errno) Line 190
{
/* Save/restore errno across closedir call. */
int e = errno; Line 193
closedir (dirp); Line 194
errno = e; Line 195
/* Arrange to give a diagnostic after exiting this loop. */
dirp = NULL; Line 198
}
break; Line 200
}
ino = D_INO (dp); Line 203
if (ino == NOT_AN_INODE_NUMBER || use_lstat) Line 205
{
if (lstat (dp->d_name, &ent_sb) < 0) Line 207...!syscalls auto-comment...
{
/* Skip any entry we can't stat. */
continue; Line 210
}
ino = ent_sb.st_ino; Line 212
}
if (ino != dot_sb->st_ino) Line 215
continue; Line 216
/* 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) Line 220
{
file_name_prepend (file_name, dp->d_name, _D_EXACT_NAMLEN (dp)); Line 222
found = true; Line 223
break; Line 224
}
}
if (dirp == NULL || closedir (dirp) != 0) Line 228
{
/* Note that this diagnostic serves for both readdir
and closedir failures. */
die (EXIT_FAILURE, errno, _("reading directory %s"), Line 232
quote (nth_parent (parent_height))); Line 233
}
if ( ! found) Line 236
die (EXIT_FAILURE, 0, Line 237
_("couldn't find directory entry in %s with matching i-node"), Line 238
quote (nth_parent (parent_height))); Line 239
*dot_sb = parent_sb; Line 241
} Block 8
/* 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 GNU/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 Line 267
robust_getcwd (struct file_name *file_name) Line 268
{
size_t height = 1; Line 270
struct dev_ino dev_ino_buf; Line 271
struct dev_ino *root_dev_ino = get_root_dev_ino (&dev_ino_buf); Line 272
struct stat dot_sb; Line 273
if (root_dev_ino == NULL) Line 275
die (EXIT_FAILURE, errno, _("failed to get attributes of %s"), Line 276
quoteaf ("/")); Line 277
if (stat (".", &dot_sb) < 0) Line 279...!syscalls auto-comment...
die (EXIT_FAILURE, errno, _("failed to stat %s"), quoteaf (".")); Line 280
while (1) Line 282
{
/* If we've reached the root, we're done. */
if (SAME_INODE (dot_sb, *root_dev_ino)) Line 285
break; Line 286
find_dir_entry (&dot_sb, file_name, height++); Line 288
}
/* See if a leading slash is needed; file_name_prepend adds one. */
if (file_name->start[0] == '\0') Line 292
file_name_prepend (file_name, "", 0); Line 293
} Block 9
/* Return PWD from the environment if it is acceptable for 'pwd -L'
output, otherwise NULL. */
static char * Line 299
logical_getcwd (void) Line 300
{
struct stat st1; Line 302
struct stat st2; Line 303
char *wd = getenv ("PWD"); Line 304
char *p; Line 305
/* Textual validation first. */
if (!wd || wd[0] != '/') Line 308
return NULL; Line 309
p = wd; Line 310
while ((p = strstr (p, "/."))) Line 311
{
if (!p[2] || p[2] == '/' Line 313
|| (p[2] == '.' && (!p[3] || p[3] == '/'))) Line 314
return NULL; Line 315
p++; Line 316
}
/* System call validation. */
if (stat (wd, &st1) == 0 && stat (".", &st2) == 0 && SAME_INODE (st1, st2)) Line 320...!syscalls auto-comment...
return wd; Line 321
return NULL; Line 322
} Block 10
int
main (int argc, char **argv) Line 327
{
char *wd; Line 329
/* 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); Line 333
initialize_main (&argc, &argv); VMS-specific entry point handling wildcard expansion
set_program_name (argv[0]); Retains program name and discards path
setlocale (LC_ALL, ""); Sets up internationalization (i18n)
bindtextdomain (PACKAGE, LOCALEDIR); Assigns i18n directorySets text domain for _() [gettext()] function
textdomain (PACKAGE); Sets text domain for _() [gettext()] function
atexit (close_stdout); Close stdout on exit (see gnulib)
while (1) Line 343
{
int c = getopt_long (argc, argv, "LP", longopts, NULL); Line 345
if (c == -1) Line 346
break; Line 347
switch (c) Line 348
{
case 'L': Line 350
logical = true; Line 351
break; Line 352
case 'P': Line 353
logical = false; Line 354
break; Line 355
case_GETOPT_HELP_CHAR; Line 357
case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); Line 359
default: Line 361
usage (EXIT_FAILURE); Line 362
}
}
if (optind < argc) Line 366
error (0, 0, _("ignoring non-option arguments")); Line 367
if (logical) Line 369
{
wd = logical_getcwd (); Line 371
if (wd) Line 372
{
puts (wd); Line 374
return EXIT_SUCCESS; Line 375
}
}
wd = xgetcwd (); Line 379
if (wd != NULL) Line 380
{
puts (wd); Line 382
free (wd); Line 383
}
else Line 385
{
struct file_name *file_name = file_name_init (); Line 387
robust_getcwd (file_name); Line 388
puts (file_name->start); Line 389
file_name_free (file_name); Line 390
}
return EXIT_SUCCESS; Line 393
} Block 11