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