doc: remove 'proposed' in regard to $'' descriptions
[coreutils.git] / src / pwd.c
blob2add89c8e0f7606d5eb065cb9f32e908f2753e34
1 /* pwd - print current directory
2 Copyright (C) 1994-2024 Free Software Foundation, Inc.
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.
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.
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <https://www.gnu.org/licenses/>. */
17 #include <config.h>
18 #include <getopt.h>
19 #include <stdio.h>
20 #include <sys/types.h>
22 #include "system.h"
23 #include "quote.h"
24 #include "root-dev-ino.h"
25 #include "xgetcwd.h"
27 /* The official name of this program (e.g., no 'g' prefix). */
28 #define PROGRAM_NAME "pwd"
30 #define AUTHORS proper_name ("Jim Meyering")
32 struct file_name
34 char *buf;
35 size_t n_alloc;
36 char *start;
39 static struct option const longopts[] =
41 {"logical", no_argument, nullptr, 'L'},
42 {"physical", no_argument, nullptr, 'P'},
43 {GETOPT_HELP_OPTION_DECL},
44 {GETOPT_VERSION_OPTION_DECL},
45 {nullptr, 0, nullptr, 0}
48 void
49 usage (int status)
51 if (status != EXIT_SUCCESS)
52 emit_try_help ();
53 else
55 printf (_("Usage: %s [OPTION]...\n"), program_name);
56 fputs (_("\
57 Print the full filename of the current working directory.\n\
58 \n\
59 "), stdout);
60 fputs (_("\
61 -L, --logical use PWD from environment, even if it contains symlinks\n\
62 "), stdout);
63 fputs (_("\
64 -P, --physical resolve all symlinks\n\
65 "), stdout);
66 fputs (HELP_OPTION_DESCRIPTION, stdout);
67 fputs (VERSION_OPTION_DESCRIPTION, stdout);
68 fputs (_("\n\
69 If no option is specified, -P is assumed.\n\
70 "), stdout);
71 printf (USAGE_BUILTIN_WARNING, PROGRAM_NAME);
72 emit_ancillary_info (PROGRAM_NAME);
74 exit (status);
77 static void
78 file_name_free (struct file_name *p)
80 free (p->buf);
81 free (p);
84 static struct file_name *
85 file_name_init (void)
87 struct file_name *p = xmalloc (sizeof *p);
89 /* Start with a buffer larger than PATH_MAX, but beware of systems
90 on which PATH_MAX is very large -- e.g., INT_MAX. */
91 p->n_alloc = MIN (2 * PATH_MAX, 32 * 1024);
93 p->buf = xmalloc (p->n_alloc);
94 p->start = p->buf + (p->n_alloc - 1);
95 p->start[0] = '\0';
96 return p;
99 /* Prepend the name S of length S_LEN, to the growing file_name, P. */
100 static void
101 file_name_prepend (struct file_name *p, char const *s, size_t s_len)
103 size_t n_free = p->start - p->buf;
104 if (n_free < 1 + s_len)
106 size_t half = p->n_alloc + 1 + s_len;
107 /* Use xnmalloc+free rather than xnrealloc, since with the latter
108 we'd end up copying the data twice: once via realloc, then again
109 to align it with the end of the new buffer. With xnmalloc, we
110 copy it only once. */
111 char *q = xnmalloc (2, half);
112 size_t n_used = p->n_alloc - n_free;
113 p->start = q + 2 * half - n_used;
114 memcpy (p->start, p->buf + n_free, n_used);
115 free (p->buf);
116 p->buf = q;
117 p->n_alloc = 2 * half;
120 p->start -= 1 + s_len;
121 p->start[0] = '/';
122 memcpy (p->start + 1, s, s_len);
125 /* Return a string (malloc'd) consisting of N '/'-separated ".." components. */
126 static char *
127 nth_parent (size_t n)
129 char *buf = xnmalloc (3, n);
130 char *p = buf;
132 for (size_t i = 0; i < n; i++)
134 memcpy (p, "../", 3);
135 p += 3;
137 p[-1] = '\0';
138 return buf;
141 /* Determine the basename of the current directory, where DOT_SB is the
142 result of lstat'ing "." and prepend that to the file name in *FILE_NAME.
143 Find the directory entry in '..' that matches the dev/i-node of DOT_SB.
144 Upon success, update *DOT_SB with stat information of '..', chdir to '..',
145 and prepend "/basename" to FILE_NAME.
146 Otherwise, exit with a diagnostic.
147 PARENT_HEIGHT is the number of levels '..' is above the starting directory.
148 The first time this function is called (from the initial directory),
149 PARENT_HEIGHT is 1. This is solely for diagnostics.
150 Exit nonzero upon error. */
152 static void
153 find_dir_entry (struct stat *dot_sb, struct file_name *file_name,
154 size_t parent_height)
156 DIR *dirp;
157 int fd;
158 struct stat parent_sb;
159 bool use_lstat;
160 bool found;
162 dirp = opendir ("..");
163 if (dirp == nullptr)
164 error (EXIT_FAILURE, errno, _("cannot open directory %s"),
165 quote (nth_parent (parent_height)));
167 fd = dirfd (dirp);
168 if ((0 <= fd ? fchdir (fd) : chdir ("..")) < 0)
169 error (EXIT_FAILURE, errno, _("failed to chdir to %s"),
170 quote (nth_parent (parent_height)));
172 if ((0 <= fd ? fstat (fd, &parent_sb) : stat (".", &parent_sb)) < 0)
173 error (EXIT_FAILURE, errno, _("failed to stat %s"),
174 quote (nth_parent (parent_height)));
176 /* If parent and child directory are on different devices, then we
177 can't rely on d_ino for useful i-node numbers; use lstat instead. */
178 use_lstat = (parent_sb.st_dev != dot_sb->st_dev);
180 found = false;
181 while (true)
183 struct dirent const *dp;
184 struct stat ent_sb;
185 ino_t ino;
187 errno = 0;
188 if ((dp = readdir_ignoring_dot_and_dotdot (dirp)) == nullptr)
190 if (errno)
192 /* Save/restore errno across closedir call. */
193 int e = errno;
194 closedir (dirp);
195 errno = e;
197 /* Arrange to give a diagnostic after exiting this loop. */
198 dirp = nullptr;
200 break;
203 ino = D_INO (dp);
205 if (ino == NOT_AN_INODE_NUMBER || use_lstat)
207 if (lstat (dp->d_name, &ent_sb) < 0)
209 /* Skip any entry we can't stat. */
210 continue;
212 ino = ent_sb.st_ino;
215 if (ino != dot_sb->st_ino)
216 continue;
218 /* If we're not crossing a device boundary, then a simple i-node
219 match is enough. */
220 if ( ! use_lstat || ent_sb.st_dev == dot_sb->st_dev)
222 file_name_prepend (file_name, dp->d_name, _D_EXACT_NAMLEN (dp));
223 found = true;
224 break;
228 if (dirp == nullptr || closedir (dirp) != 0)
230 /* Note that this diagnostic serves for both readdir
231 and closedir failures. */
232 error (EXIT_FAILURE, errno, _("reading directory %s"),
233 quote (nth_parent (parent_height)));
236 if ( ! found)
237 error (EXIT_FAILURE, 0,
238 _("couldn't find directory entry in %s with matching i-node"),
239 quote (nth_parent (parent_height)));
241 *dot_sb = parent_sb;
244 /* Construct the full, absolute name of the current working
245 directory and store it in *FILE_NAME.
246 The getcwd function performs nearly the same task, but is typically
247 unable to handle names longer than PATH_MAX. This function has
248 no such limitation. However, this function *can* fail due to
249 permission problems or a lack of memory, while GNU/Linux's getcwd
250 function works regardless of restricted permissions on parent
251 directories. Upon failure, give a diagnostic and exit nonzero.
253 Note: although this function is similar to getcwd, it has a fundamental
254 difference in that it gives a diagnostic and exits upon failure.
255 I would have liked a function that did not exit, and that could be
256 used as a getcwd replacement. Unfortunately, considering all of
257 the information the caller would require in order to produce good
258 diagnostics, it doesn't seem worth the added complexity.
259 In any case, any getcwd replacement must *not* exceed the PATH_MAX
260 limitation. Otherwise, functions like 'chdir' would fail with
261 ENAMETOOLONG.
263 FIXME-maybe: if find_dir_entry fails due to permissions, try getcwd,
264 in case the unreadable directory is close enough to the root that
265 getcwd works from there. */
267 static void
268 robust_getcwd (struct file_name *file_name)
270 size_t height = 1;
271 struct dev_ino dev_ino_buf;
272 struct dev_ino *root_dev_ino = get_root_dev_ino (&dev_ino_buf);
273 struct stat dot_sb;
275 if (root_dev_ino == nullptr)
276 error (EXIT_FAILURE, errno, _("failed to get attributes of %s"),
277 quoteaf ("/"));
279 if (stat (".", &dot_sb) < 0)
280 error (EXIT_FAILURE, errno, _("failed to stat %s"), quoteaf ("."));
282 while (true)
284 /* If we've reached the root, we're done. */
285 if (PSAME_INODE (&dot_sb, root_dev_ino))
286 break;
288 find_dir_entry (&dot_sb, file_name, height++);
291 /* See if a leading slash is needed; file_name_prepend adds one. */
292 if (file_name->start[0] == '\0')
293 file_name_prepend (file_name, "", 0);
297 /* Return PWD from the environment if it is acceptable for 'pwd -L'
298 output, otherwise nullptr. */
299 static char *
300 logical_getcwd (void)
302 struct stat st1;
303 struct stat st2;
304 char *wd = getenv ("PWD");
305 char *p;
307 /* Textual validation first. */
308 if (!wd || wd[0] != '/')
309 return nullptr;
310 p = wd;
311 while ((p = strstr (p, "/.")))
313 if (!p[2] || p[2] == '/'
314 || (p[2] == '.' && (!p[3] || p[3] == '/')))
315 return nullptr;
316 p++;
319 /* System call validation. */
320 if (stat (wd, &st1) == 0 && stat (".", &st2) == 0 && psame_inode (&st1, &st2))
321 return wd;
322 return nullptr;
327 main (int argc, char **argv)
329 char *wd;
330 /* POSIX requires a default of -L, but most scripts expect -P.
331 Currently shells default to -L, while stand-alone
332 pwd implementations default to -P. */
333 bool logical = (getenv ("POSIXLY_CORRECT") != nullptr);
335 initialize_main (&argc, &argv);
336 set_program_name (argv[0]);
337 setlocale (LC_ALL, "");
338 bindtextdomain (PACKAGE, LOCALEDIR);
339 textdomain (PACKAGE);
341 atexit (close_stdout);
343 while (true)
345 int c = getopt_long (argc, argv, "LP", longopts, nullptr);
346 if (c == -1)
347 break;
348 switch (c)
350 case 'L':
351 logical = true;
352 break;
353 case 'P':
354 logical = false;
355 break;
357 case_GETOPT_HELP_CHAR;
359 case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
361 default:
362 usage (EXIT_FAILURE);
366 if (optind < argc)
367 error (0, 0, _("ignoring non-option arguments"));
369 if (logical)
371 wd = logical_getcwd ();
372 if (wd)
374 puts (wd);
375 return EXIT_SUCCESS;
379 wd = xgetcwd ();
380 if (wd != nullptr)
382 puts (wd);
383 free (wd);
385 else
387 struct file_name *file_name = file_name_init ();
388 robust_getcwd (file_name);
389 puts (file_name->start);
390 file_name_free (file_name);
393 return EXIT_SUCCESS;