Line data Source code
1 : /* pwd - print current directory
2 : Copyright (C) 1994-1997, 1999-2007 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 "dirfd.h"
24 : #include "error.h"
25 : #include "long-options.h"
26 : #include "quote.h"
27 : #include "root-dev-ino.h"
28 : #include "xgetcwd.h"
29 :
30 : /* The official name of this program (e.g., no `g' prefix). */
31 : #define PROGRAM_NAME "pwd"
32 :
33 : #define AUTHORS "Jim Meyering"
34 :
35 : struct file_name
36 : {
37 : char *buf;
38 : size_t n_alloc;
39 : char *start;
40 : };
41 :
42 : /* The name this program was run with. */
43 : char *program_name;
44 :
45 : void
46 8 : usage (int status)
47 : {
48 8 : if (status != EXIT_SUCCESS)
49 6 : fprintf (stderr, _("Try `%s --help' for more information.\n"),
50 : program_name);
51 : else
52 : {
53 2 : printf (_("Usage: %s [OPTION]\n"), program_name);
54 2 : fputs (_("\
55 : Print the full filename of the current working directory.\n\
56 : \n\
57 : "), stdout);
58 2 : fputs (HELP_OPTION_DESCRIPTION, stdout);
59 2 : fputs (VERSION_OPTION_DESCRIPTION, stdout);
60 2 : printf (USAGE_BUILTIN_WARNING, PROGRAM_NAME);
61 2 : emit_bug_reporting_address ();
62 : }
63 8 : exit (status);
64 : }
65 :
66 : static void
67 0 : file_name_free (struct file_name *p)
68 : {
69 0 : free (p->buf);
70 0 : free (p);
71 0 : }
72 :
73 : static struct file_name *
74 0 : file_name_init (void)
75 : {
76 0 : struct file_name *p = xmalloc (sizeof *p);
77 :
78 : /* Start with a buffer larger than PATH_MAX, but beware of systems
79 : on which PATH_MAX is very large -- e.g., INT_MAX. */
80 0 : p->n_alloc = MIN (2 * PATH_MAX, 32 * 1024);
81 :
82 0 : p->buf = xmalloc (p->n_alloc);
83 0 : p->start = p->buf + (p->n_alloc - 1);
84 0 : p->start[0] = '\0';
85 0 : return p;
86 : }
87 :
88 : /* Prepend the name S of length S_LEN, to the growing file_name, P. */
89 : static void
90 0 : file_name_prepend (struct file_name *p, char const *s, size_t s_len)
91 : {
92 0 : size_t n_free = p->start - p->buf;
93 0 : if (n_free < 1 + s_len)
94 : {
95 0 : size_t half = p->n_alloc + 1 + s_len;
96 : /* Use xnmalloc+free rather than xnrealloc, since with the latter
97 : we'd end up copying the data twice: once via realloc, then again
98 : to align it with the end of the new buffer. With xnmalloc, we
99 : copy it only once. */
100 0 : char *q = xnmalloc (2, half);
101 0 : size_t n_used = p->n_alloc - n_free;
102 0 : p->start = q + 2 * half - n_used;
103 0 : memcpy (p->start, p->buf + n_free, n_used);
104 0 : free (p->buf);
105 0 : p->buf = q;
106 0 : p->n_alloc = 2 * half;
107 : }
108 :
109 0 : p->start -= 1 + s_len;
110 0 : p->start[0] = '/';
111 0 : memcpy (p->start + 1, s, s_len);
112 0 : }
113 :
114 : /* Return a string (malloc'd) consisting of N `/'-separated ".." components. */
115 : static char *
116 0 : nth_parent (size_t n)
117 : {
118 0 : char *buf = xnmalloc (3, n);
119 0 : char *p = buf;
120 : size_t i;
121 :
122 0 : for (i = 0; i < n; i++)
123 : {
124 0 : memcpy (p, "../", 3);
125 0 : p += 3;
126 : }
127 0 : p[-1] = '\0';
128 0 : return buf;
129 : }
130 :
131 : /* Determine the basename of the current directory, where DOT_SB is the
132 : result of lstat'ing "." and prepend that to the file name in *FILE_NAME.
133 : Find the directory entry in `..' that matches the dev/i-node of DOT_SB.
134 : Upon success, update *DOT_SB with stat information of `..', chdir to `..',
135 : and prepend "/basename" to FILE_NAME.
136 : Otherwise, exit with a diagnostic.
137 : PARENT_HEIGHT is the number of levels `..' is above the starting directory.
138 : The first time this function is called (from the initial directory),
139 : PARENT_HEIGHT is 1. This is solely for diagnostics.
140 : Exit nonzero upon error. */
141 :
142 : static void
143 0 : find_dir_entry (struct stat *dot_sb, struct file_name *file_name,
144 : size_t parent_height)
145 : {
146 : DIR *dirp;
147 : int fd;
148 : struct stat parent_sb;
149 : bool use_lstat;
150 : bool found;
151 :
152 0 : dirp = opendir ("..");
153 0 : if (dirp == NULL)
154 0 : error (EXIT_FAILURE, errno, _("cannot open directory %s"),
155 0 : quote (nth_parent (parent_height)));
156 :
157 0 : fd = dirfd (dirp);
158 0 : if ((0 <= fd ? fchdir (fd) : chdir ("..")) < 0)
159 0 : error (EXIT_FAILURE, errno, _("failed to chdir to %s"),
160 0 : quote (nth_parent (parent_height)));
161 :
162 0 : if ((0 <= fd ? fstat (fd, &parent_sb) : stat (".", &parent_sb)) < 0)
163 0 : error (EXIT_FAILURE, errno, _("failed to stat %s"),
164 0 : quote (nth_parent (parent_height)));
165 :
166 : /* If parent and child directory are on different devices, then we
167 : can't rely on d_ino for useful i-node numbers; use lstat instead. */
168 0 : use_lstat = (parent_sb.st_dev != dot_sb->st_dev);
169 :
170 0 : found = false;
171 : while (1)
172 0 : {
173 : struct dirent const *dp;
174 : struct stat ent_sb;
175 : ino_t ino;
176 :
177 0 : errno = 0;
178 0 : if ((dp = readdir_ignoring_dot_and_dotdot (dirp)) == NULL)
179 : {
180 0 : if (errno)
181 : {
182 : /* Save/restore errno across closedir call. */
183 0 : int e = errno;
184 0 : closedir (dirp);
185 0 : errno = e;
186 :
187 : /* Arrange to give a diagnostic after exiting this loop. */
188 0 : dirp = NULL;
189 : }
190 0 : break;
191 : }
192 :
193 0 : ino = D_INO (dp);
194 :
195 0 : if (ino == NOT_AN_INODE_NUMBER || use_lstat)
196 : {
197 0 : if (lstat (dp->d_name, &ent_sb) < 0)
198 : {
199 : /* Skip any entry we can't stat. */
200 0 : continue;
201 : }
202 0 : ino = ent_sb.st_ino;
203 : }
204 :
205 0 : if (ino != dot_sb->st_ino)
206 0 : continue;
207 :
208 : /* If we're not crossing a device boundary, then a simple i-node
209 : match is enough. */
210 0 : if ( ! use_lstat || ent_sb.st_dev == dot_sb->st_dev)
211 : {
212 0 : file_name_prepend (file_name, dp->d_name, _D_EXACT_NAMLEN (dp));
213 0 : found = true;
214 0 : break;
215 : }
216 : }
217 :
218 0 : if (dirp == NULL || closedir (dirp) != 0)
219 : {
220 : /* Note that this diagnostic serves for both readdir
221 : and closedir failures. */
222 0 : error (EXIT_FAILURE, errno, _("reading directory %s"),
223 0 : quote (nth_parent (parent_height)));
224 : }
225 :
226 0 : if ( ! found)
227 0 : error (EXIT_FAILURE, 0,
228 : _("couldn't find directory entry in %s with matching i-node"),
229 0 : quote (nth_parent (parent_height)));
230 :
231 0 : *dot_sb = parent_sb;
232 0 : }
233 :
234 : /* Construct the full, absolute name of the current working
235 : directory and store it in *FILE_NAME.
236 : The getcwd function performs nearly the same task, but is typically
237 : unable to handle names longer than PATH_MAX. This function has
238 : no such limitation. However, this function *can* fail due to
239 : permission problems or a lack of memory, while Linux's getcwd
240 : function works regardless of restricted permissions on parent
241 : directories. Upon failure, give a diagnostic and exit nonzero.
242 :
243 : Note: although this function is similar to getcwd, it has a fundamental
244 : difference in that it gives a diagnostic and exits upon failure.
245 : I would have liked a function that did not exit, and that could be
246 : used as a getcwd replacement. Unfortunately, considering all of
247 : the information the caller would require in order to produce good
248 : diagnostics, it doesn't seem worth the added complexity.
249 : In any case, any getcwd replacement must *not* exceed the PATH_MAX
250 : limitation. Otherwise, functions like `chdir' would fail with
251 : ENAMETOOLONG.
252 :
253 : FIXME-maybe: if find_dir_entry fails due to permissions, try getcwd,
254 : in case the unreadable directory is close enough to the root that
255 : getcwd works from there. */
256 :
257 : static void
258 0 : robust_getcwd (struct file_name *file_name)
259 : {
260 0 : size_t height = 1;
261 : struct dev_ino dev_ino_buf;
262 0 : struct dev_ino *root_dev_ino = get_root_dev_ino (&dev_ino_buf);
263 : struct stat dot_sb;
264 :
265 0 : if (root_dev_ino == NULL)
266 0 : error (EXIT_FAILURE, errno, _("failed to get attributes of %s"),
267 : quote ("/"));
268 :
269 0 : if (stat (".", &dot_sb) < 0)
270 0 : error (EXIT_FAILURE, errno, _("failed to stat %s"), quote ("."));
271 :
272 : while (1)
273 : {
274 : /* If we've reached the root, we're done. */
275 0 : if (SAME_INODE (dot_sb, *root_dev_ino))
276 0 : break;
277 :
278 0 : find_dir_entry (&dot_sb, file_name, height++);
279 : }
280 :
281 : /* See if a leading slash is needed; file_name_prepend adds one. */
282 0 : if (file_name->start[0] == '\0')
283 0 : file_name_prepend (file_name, "", 0);
284 0 : }
285 :
286 : int
287 19 : main (int argc, char **argv)
288 : {
289 : char *wd;
290 :
291 : initialize_main (&argc, &argv);
292 19 : program_name = argv[0];
293 19 : setlocale (LC_ALL, "");
294 : bindtextdomain (PACKAGE, LOCALEDIR);
295 : textdomain (PACKAGE);
296 :
297 19 : atexit (close_stdout);
298 :
299 19 : parse_long_options (argc, argv, PROGRAM_NAME, PACKAGE_NAME, VERSION,
300 : usage, AUTHORS, (char const *) NULL);
301 16 : if (getopt_long (argc, argv, "", NULL, NULL) != -1)
302 6 : usage (EXIT_FAILURE);
303 :
304 10 : if (optind < argc)
305 8 : error (0, 0, _("ignoring non-option arguments"));
306 :
307 10 : wd = xgetcwd ();
308 10 : if (wd != NULL)
309 : {
310 10 : puts (wd);
311 10 : free (wd);
312 : }
313 : else
314 : {
315 0 : struct file_name *file_name = file_name_init ();
316 0 : robust_getcwd (file_name);
317 0 : puts (file_name->start);
318 0 : file_name_free (file_name);
319 : }
320 :
321 10 : exit (EXIT_SUCCESS);
322 : }
|