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