canonicalize: simplify errno handling
[gnulib.git] / lib / canonicalize.c
1 /* Return the canonical absolute name of a given file.
2    Copyright (C) 1996-2009 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
19 #include "canonicalize.h"
20
21 #include <stdlib.h>
22 #include <string.h>
23
24 #if HAVE_SYS_PARAM_H
25 # include <sys/param.h>
26 #endif
27
28 #include <sys/stat.h>
29
30 #include <unistd.h>
31
32 #include <errno.h>
33 #include <stddef.h>
34
35 #include "areadlink.h"
36 #include "file-set.h"
37 #include "filenamecat.h"
38 #include "hash-triple.h"
39 #include "pathmax.h"
40 #include "xalloc.h"
41 #include "xgetcwd.h"
42
43 #if !(HAVE_CANONICALIZE_FILE_NAME || GNULIB_CANONICALIZE_LGPL)
44 /* Return the canonical absolute name of file NAME.  A canonical name
45    does not contain any `.', `..' components nor any repeated file name
46    separators ('/') or symlinks.  All components must exist.
47    The result is malloc'd.  */
48
49 char *
50 canonicalize_file_name (const char *name)
51 {
52 # if HAVE_RESOLVEPATH
53
54   char *resolved, *extra_buf = NULL;
55   size_t resolved_size;
56   ssize_t resolved_len;
57
58   if (name == NULL)
59     {
60       errno = EINVAL;
61       return NULL;
62     }
63
64   if (name[0] == '\0')
65     {
66       errno = ENOENT;
67       return NULL;
68     }
69
70   /* All known hosts with resolvepath (e.g. Solaris 7) don't turn
71      relative names into absolute ones, so prepend the working
72      directory if the file name is not absolute.  */
73   if (name[0] != '/')
74     {
75       char *wd;
76
77       if (!(wd = xgetcwd ()))
78         return NULL;
79
80       extra_buf = file_name_concat (wd, name, NULL);
81       name = extra_buf;
82       free (wd);
83     }
84
85   resolved_size = strlen (name);
86   while (1)
87     {
88       resolved_size = 2 * resolved_size + 1;
89       resolved = xmalloc (resolved_size);
90       resolved_len = resolvepath (name, resolved, resolved_size);
91       if (resolved_len < 0)
92         {
93           free (resolved);
94           free (extra_buf);
95           return NULL;
96         }
97       if (resolved_len < resolved_size)
98         break;
99       free (resolved);
100     }
101
102   free (extra_buf);
103
104   /* NUL-terminate the resulting name.  */
105   resolved[resolved_len] = '\0';
106
107   return resolved;
108
109 # else
110
111   return canonicalize_filename_mode (name, CAN_EXISTING);
112
113 # endif /* !HAVE_RESOLVEPATH */
114 }
115 #endif /* !HAVE_CANONICALIZE_FILE_NAME */
116
117 /* Return true if we've already seen the triple, <FILENAME, dev, ino>.
118    If *HT is not initialized, initialize it.  */
119 static bool
120 seen_triple (Hash_table **ht, char const *filename, struct stat const *st)
121 {
122   if (*ht == NULL)
123     {
124       size_t initial_capacity = 7;
125       *ht = hash_initialize (initial_capacity,
126                             NULL,
127                             triple_hash,
128                             triple_compare_ino_str,
129                             triple_free);
130       if (*ht == NULL)
131         xalloc_die ();
132     }
133
134   if (seen_file (*ht, filename, st))
135     return true;
136
137   record_file (*ht, filename, st);
138   return false;
139 }
140
141 /* Return the canonical absolute name of file NAME.  A canonical name
142    does not contain any `.', `..' components nor any repeated file name
143    separators ('/') or symlinks.  Whether components must exist
144    or not depends on canonicalize mode.  The result is malloc'd.  */
145
146 char *
147 canonicalize_filename_mode (const char *name, canonicalize_mode_t can_mode)
148 {
149   char *rname, *dest, *extra_buf = NULL;
150   char const *start;
151   char const *end;
152   char const *rname_limit;
153   size_t extra_len = 0;
154   Hash_table *ht = NULL;
155
156   if (name == NULL)
157     {
158       errno = EINVAL;
159       return NULL;
160     }
161
162   if (name[0] == '\0')
163     {
164       errno = ENOENT;
165       return NULL;
166     }
167
168   if (name[0] != '/')
169     {
170       rname = xgetcwd ();
171       if (!rname)
172         return NULL;
173       dest = strchr (rname, '\0');
174       if (dest - rname < PATH_MAX)
175         {
176           char *p = xrealloc (rname, PATH_MAX);
177           dest = p + (dest - rname);
178           rname = p;
179           rname_limit = rname + PATH_MAX;
180         }
181       else
182         {
183           rname_limit = dest;
184         }
185     }
186   else
187     {
188       rname = xmalloc (PATH_MAX);
189       rname_limit = rname + PATH_MAX;
190       rname[0] = '/';
191       dest = rname + 1;
192     }
193
194   for (start = name; *start; start = end)
195     {
196       /* Skip sequence of multiple file name separators.  */
197       while (*start == '/')
198         ++start;
199
200       /* Find end of component.  */
201       for (end = start; *end && *end != '/'; ++end)
202         /* Nothing.  */;
203
204       if (end - start == 0)
205         break;
206       else if (end - start == 1 && start[0] == '.')
207         /* nothing */;
208       else if (end - start == 2 && start[0] == '.' && start[1] == '.')
209         {
210           /* Back up to previous component, ignore if at root already.  */
211           if (dest > rname + 1)
212             while ((--dest)[-1] != '/');
213         }
214       else
215         {
216           struct stat st;
217
218           if (dest[-1] != '/')
219             *dest++ = '/';
220
221           if (dest + (end - start) >= rname_limit)
222             {
223               ptrdiff_t dest_offset = dest - rname;
224               size_t new_size = rname_limit - rname;
225
226               if (end - start + 1 > PATH_MAX)
227                 new_size += end - start + 1;
228               else
229                 new_size += PATH_MAX;
230               rname = xrealloc (rname, new_size);
231               rname_limit = rname + new_size;
232
233               dest = rname + dest_offset;
234             }
235
236           dest = memcpy (dest, start, end - start);
237           dest += end - start;
238           *dest = '\0';
239
240           if (lstat (rname, &st) != 0)
241             {
242               if (can_mode == CAN_EXISTING)
243                 goto error;
244               if (can_mode == CAN_ALL_BUT_LAST && *end)
245                 goto error;
246               st.st_mode = 0;
247             }
248
249           if (S_ISLNK (st.st_mode))
250             {
251               char *buf;
252               size_t n, len;
253
254               /* Detect loops.  We cannot use the cycle-check module here,
255                  since it's actually possible to encounter the same symlink
256                  more than once in a given traversal.  However, encountering
257                  the same symlink,NAME pair twice does indicate a loop.  */
258               if (seen_triple (&ht, name, &st))
259                 {
260                   if (can_mode == CAN_MISSING)
261                     continue;
262                   errno = ELOOP;
263                   goto error;
264                 }
265
266               buf = areadlink_with_size (rname, st.st_size);
267               if (!buf)
268                 {
269                   if (can_mode == CAN_MISSING && errno != ENOMEM)
270                     continue;
271                   goto error;
272                 }
273
274               n = strlen (buf);
275               len = strlen (end);
276
277               if (!extra_len)
278                 {
279                   extra_len =
280                     ((n + len + 1) > PATH_MAX) ? (n + len + 1) : PATH_MAX;
281                   extra_buf = xmalloc (extra_len);
282                 }
283               else if ((n + len + 1) > extra_len)
284                 {
285                   extra_len = n + len + 1;
286                   extra_buf = xrealloc (extra_buf, extra_len);
287                 }
288
289               /* Careful here, end may be a pointer into extra_buf... */
290               memmove (&extra_buf[n], end, len + 1);
291               name = end = memcpy (extra_buf, buf, n);
292
293               if (buf[0] == '/')
294                 dest = rname + 1;       /* It's an absolute symlink */
295               else
296                 /* Back up to previous component, ignore if at root already: */
297                 if (dest > rname + 1)
298                   while ((--dest)[-1] != '/');
299
300               free (buf);
301             }
302           else
303             {
304               if (!S_ISDIR (st.st_mode) && *end && (can_mode != CAN_MISSING))
305                 {
306                   errno = ENOTDIR;
307                   goto error;
308                 }
309             }
310         }
311     }
312   if (dest > rname + 1 && dest[-1] == '/')
313     --dest;
314   *dest = '\0';
315
316   free (extra_buf);
317   if (ht)
318     hash_free (ht);
319   return rname;
320
321 error:
322   free (extra_buf);
323   free (rname);
324   if (ht)
325     hash_free (ht);
326   return NULL;
327 }