1/* pwd - print current directory
2   Copyright (C) 1994-1997, 1999-2010 Free Software Foundation, Inc.
3
4   This program is free software: you can redistribute it and/or modify
5   it under the terms of the GNU General Public License as published by
6   the Free Software Foundation, either version 3 of the License, or
7   (at your option) any later version.
8
9   This program is distributed in the hope that it will be useful,
10   but WITHOUT ANY WARRANTY; without even the implied warranty of
11   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12   GNU General Public License for more details.
13
14   You should have received a copy of the GNU General Public License
15   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
16
17#include <config.h>
18#include <getopt.h>
19#include <stdio.h>
20#include <sys/types.h>
21
22#include "system.h"
23#include "error.h"
24#include "quote.h"
25#include "root-dev-ino.h"
26#include "xgetcwd.h"
27
28/* The official name of this program (e.g., no `g' prefix).  */
29#define PROGRAM_NAME "pwd"
30
31#define AUTHORS proper_name ("Jim Meyering")
32
33struct file_name
34{
35  char *buf;
36  size_t n_alloc;
37  char *start;
38};
39
40static struct option const longopts[] =
41{
42  {"logical", no_argument, NULL, 'L'},
43  {"physical", no_argument, NULL, 'P'},
44  {GETOPT_HELP_OPTION_DECL},
45  {GETOPT_VERSION_OPTION_DECL},
46  {NULL, 0, NULL, 0}
47};
48
49void
50usage (int status)
51{
52  if (status != EXIT_SUCCESS)
53    fprintf (stderr, _("Try `%s --help' for more information.\n"),
54             program_name);
55  else
56    {
57      printf (_("Usage: %s [OPTION]...\n"), program_name);
58      fputs (_("\
59Print the full filename of the current working directory.\n\
60\n\
61"), stdout);
62      fputs (_("\
63  -L, --logical   use PWD from environment, even if it contains symlinks\n\
64  -P, --physical  avoid all symlinks\n\
65"), stdout);
66      fputs (HELP_OPTION_DESCRIPTION, stdout);
67      fputs (VERSION_OPTION_DESCRIPTION, stdout);
68      printf (USAGE_BUILTIN_WARNING, PROGRAM_NAME);
69      emit_ancillary_info ();
70    }
71  exit (status);
72}
73
74static void
75file_name_free (struct file_name *p)
76{
77  free (p->buf);
78  free (p);
79}
80
81static struct file_name *
82file_name_init (void)
83{
84  struct file_name *p = xmalloc (sizeof *p);
85
86  /* Start with a buffer larger than PATH_MAX, but beware of systems
87     on which PATH_MAX is very large -- e.g., INT_MAX.  */
88  p->n_alloc = MIN (2 * PATH_MAX, 32 * 1024);
89
90  p->buf = xmalloc (p->n_alloc);
91  p->start = p->buf + (p->n_alloc - 1);
92  p->start[0] = '\0';
93  return p;
94}
95
96/* Prepend the name S of length S_LEN, to the growing file_name, P.  */
97static void
98file_name_prepend (struct file_name *p, char const *s, size_t s_len)
99{
100  size_t n_free = p->start - p->buf;
101  if (n_free < 1 + s_len)
102    {
103      size_t half = p->n_alloc + 1 + s_len;
104      /* Use xnmalloc+free rather than xnrealloc, since with the latter
105         we'd end up copying the data twice: once via realloc, then again
106         to align it with the end of the new buffer.  With xnmalloc, we
107         copy it only once.  */
108      char *q = xnmalloc (2, half);
109      size_t n_used = p->n_alloc - n_free;
110      p->start = q + 2 * half - n_used;
111      memcpy (p->start, p->buf + n_free, n_used);
112      free (p->buf);
113      p->buf = q;
114      p->n_alloc = 2 * half;
115    }
116
117  p->start -= 1 + s_len;
118  p->start[0] = '/';
119  memcpy (p->start + 1, s, s_len);
120}
121
122/* Return a string (malloc'd) consisting of N `/'-separated ".." components.  */
123static char *
124nth_parent (size_t n)
125{
126  char *buf = xnmalloc (3, n);
127  char *p = buf;
128  size_t i;
129
130  for (i = 0; i < n; i++)
131    {
132      memcpy (p, "../", 3);
133      p += 3;
134    }
135  p[-1] = '\0';
136  return buf;
137}
138
139/* Determine the basename of the current directory, where DOT_SB is the
140   result of lstat'ing "." and prepend that to the file name in *FILE_NAME.
141   Find the directory entry in `..' that matches the dev/i-node of DOT_SB.
142   Upon success, update *DOT_SB with stat information of `..', chdir to `..',
143   and prepend "/basename" to FILE_NAME.
144   Otherwise, exit with a diagnostic.
145   PARENT_HEIGHT is the number of levels `..' is above the starting directory.
146   The first time this function is called (from the initial directory),
147   PARENT_HEIGHT is 1.  This is solely for diagnostics.
148   Exit nonzero upon error.  */
149
150static void
151find_dir_entry (struct stat *dot_sb, struct file_name *file_name,
152                size_t parent_height)
153{
154  DIR *dirp;
155  int fd;
156  struct stat parent_sb;
157  bool use_lstat;
158  bool found;
159
160  dirp = opendir ("..");
161  if (dirp == NULL)
162    error (EXIT_FAILURE, errno, _("cannot open directory %s"),
163           quote (nth_parent (parent_height)));
164
165  fd = dirfd (dirp);
166  if ((0 <= fd ? fchdir (fd) : chdir ("..")) < 0)
167    error (EXIT_FAILURE, errno, _("failed to chdir to %s"),
168           quote (nth_parent (parent_height)));
169
170  if ((0 <= fd ? fstat (fd, &parent_sb) : stat (".", &parent_sb)) < 0)
171    error (EXIT_FAILURE, errno, _("failed to stat %s"),
172           quote (nth_parent (parent_height)));
173
174  /* If parent and child directory are on different devices, then we
175     can't rely on d_ino for useful i-node numbers; use lstat instead.  */
176  use_lstat = (parent_sb.st_dev != dot_sb->st_dev);
177
178  found = false;
179  while (1)
180    {
181      struct dirent const *dp;
182      struct stat ent_sb;
183      ino_t ino;
184
185      errno = 0;
186      if ((dp = readdir_ignoring_dot_and_dotdot (dirp)) == NULL)
187        {
188          if (errno)
189            {
190              /* Save/restore errno across closedir call.  */
191              int e = errno;
192              closedir (dirp);
193              errno = e;
194
195              /* Arrange to give a diagnostic after exiting this loop.  */
196              dirp = NULL;
197            }
198          break;
199        }
200
201      ino = D_INO (dp);
202
203      if (ino == NOT_AN_INODE_NUMBER || use_lstat)
204        {
205          if (lstat (dp->d_name, &ent_sb) < 0)
206            {
207              /* Skip any entry we can't stat.  */
208              continue;
209            }
210          ino = ent_sb.st_ino;
211        }
212
213      if (ino != dot_sb->st_ino)
214        continue;
215
216      /* If we're not crossing a device boundary, then a simple i-node
217         match is enough.  */
218      if ( ! use_lstat || ent_sb.st_dev == dot_sb->st_dev)
219        {
220          file_name_prepend (file_name, dp->d_name, _D_EXACT_NAMLEN (dp));
221          found = true;
222          break;
223        }
224    }
225
226  if (dirp == NULL || closedir (dirp) != 0)
227    {
228      /* Note that this diagnostic serves for both readdir
229         and closedir failures.  */
230      error (EXIT_FAILURE, errno, _("reading directory %s"),
231             quote (nth_parent (parent_height)));
232    }
233
234  if ( ! found)
235    error (EXIT_FAILURE, 0,
236           _("couldn't find directory entry in %s with matching i-node"),
237             quote (nth_parent (parent_height)));
238
239  *dot_sb = parent_sb;
240}
241
242/* Construct the full, absolute name of the current working
243   directory and store it in *FILE_NAME.
244   The getcwd function performs nearly the same task, but is typically
245   unable to handle names longer than PATH_MAX.  This function has
246   no such limitation.  However, this function *can* fail due to
247   permission problems or a lack of memory, while GNU/Linux's getcwd
248   function works regardless of restricted permissions on parent
249   directories.  Upon failure, give a diagnostic and exit nonzero.
250
251   Note: although this function is similar to getcwd, it has a fundamental
252   difference in that it gives a diagnostic and exits upon failure.
253   I would have liked a function that did not exit, and that could be
254   used as a getcwd replacement.  Unfortunately, considering all of
255   the information the caller would require in order to produce good
256   diagnostics, it doesn't seem worth the added complexity.
257   In any case, any getcwd replacement must *not* exceed the PATH_MAX
258   limitation.  Otherwise, functions like `chdir' would fail with
259   ENAMETOOLONG.
260
261   FIXME-maybe: if find_dir_entry fails due to permissions, try getcwd,
262   in case the unreadable directory is close enough to the root that
263   getcwd works from there.  */
264
265static void
266robust_getcwd (struct file_name *file_name)
267{
268  size_t height = 1;
269  struct dev_ino dev_ino_buf;
270  struct dev_ino *root_dev_ino = get_root_dev_ino (&dev_ino_buf);
271  struct stat dot_sb;
272
273  if (root_dev_ino == NULL)
274    error (EXIT_FAILURE, errno, _("failed to get attributes of %s"),
275           quote ("/"));
276
277  if (stat (".", &dot_sb) < 0)
278    error (EXIT_FAILURE, errno, _("failed to stat %s"), quote ("."));
279
280  while (1)
281    {
282      /* If we've reached the root, we're done.  */
283      if (SAME_INODE (dot_sb, *root_dev_ino))
284        break;
285
286      find_dir_entry (&dot_sb, file_name, height++);
287    }
288
289  /* See if a leading slash is needed; file_name_prepend adds one.  */
290  if (file_name->start[0] == '\0')
291    file_name_prepend (file_name, "", 0);
292}
293
294
295/* Return PWD from the environment if it is acceptable for 'pwd -L'
296   output, otherwise NULL.  */
297static char *
298logical_getcwd (void)
299{
300  struct stat st1;
301  struct stat st2;
302  char *wd = getenv ("PWD");
303  char *p;
304
305  /* Textual validation first.  */
306  if (!wd || wd[0] != '/')
307    return NULL;
308  p = wd;
309  while ((p = strstr (p, "/.")))
310    {
311      if (!p[2] || p[2] == '/'
312          || (p[2] == '.' && (!p[3] || p[3] == '/')))
313        return NULL;
314      p++;
315    }
316
317  /* System call validation.  */
318  if (stat (wd, &st1) == 0 && stat (".", &st2) == 0 && SAME_INODE(st1, st2))
319    return wd;
320  return NULL;
321}
322
323
324int
325main (int argc, char **argv)
326{
327  char *wd;
328  /* POSIX requires a default of -L, but most scripts expect -P.  */
329  bool logical = (getenv ("POSIXLY_CORRECT") != NULL);
330
331  initialize_main (&argc, &argv);
332  set_program_name (argv[0]);
333  setlocale (LC_ALL, "");
334  bindtextdomain (PACKAGE, LOCALEDIR);
335  textdomain (PACKAGE);
336
337  atexit (close_stdout);
338
339  while (1)
340    {
341      int c = getopt_long (argc, argv, "LP", longopts, NULL);
342      if (c == -1)
343        break;
344      switch (c)
345        {
346        case 'L':
347          logical = true;
348          break;
349        case 'P':
350          logical = false;
351          break;
352
353        case_GETOPT_HELP_CHAR;
354
355        case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
356
357        default:
358          usage (EXIT_FAILURE);
359        }
360    }
361
362  if (optind < argc)
363    error (0, 0, _("ignoring non-option arguments"));
364
365  if (logical)
366    {
367      wd = logical_getcwd ();
368      if (wd)
369        {
370          puts (wd);
371          exit (EXIT_SUCCESS);
372        }
373    }
374
375  wd = xgetcwd ();
376  if (wd != NULL)
377    {
378      puts (wd);
379      free (wd);
380    }
381  else
382    {
383      struct file_name *file_name = file_name_init ();
384      robust_getcwd (file_name);
385      puts (file_name->start);
386      file_name_free (file_name);
387    }
388
389  exit (EXIT_SUCCESS);
390}
391