xgethostname simplification
[gnulib.git] / lib / canonicalize.c
1 /* Return the canonical absolute name of a given file.
2    Copyright (C) 1996-2004 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; see the file COPYING.
16    If not, write to the Free Software Foundation,
17    59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
18
19 #ifdef HAVE_CONFIG_H
20 # include <config.h>
21 #endif
22
23 #ifdef STDC_HEADERS
24 # include <stdlib.h>
25 #else
26 void free ();
27 #endif
28
29 #if defined STDC_HEADERS || defined HAVE_STRING_H
30 # include <string.h>
31 #else
32 # include <strings.h>
33 #endif
34
35 #if HAVE_SYS_PARAM_H
36 # include <sys/param.h>
37 #endif
38
39 #include <sys/stat.h>
40
41 #if HAVE_UNISTD_H
42 # include <unistd.h>
43 #endif
44
45 #include <errno.h>
46
47 #include "cycle-check.h"
48 #include "path-concat.h"
49 #include "stat-macros.h"
50 #include "xalloc.h"
51 #include "xgetcwd.h"
52
53 #ifndef __set_errno
54 # define __set_errno(Val) errno = (Val)
55 #endif
56
57 /* If __PTRDIFF_TYPE__ is
58    defined, as with GNU C, use that; that way we don't pollute the
59    namespace with <stddef.h>'s symbols.  Otherwise, if <stddef.h> is
60    available, include it and use ptrdiff_t.  In traditional C, long is
61    the best that we can do.  */
62
63 #ifdef __PTRDIFF_TYPE__
64 # define PTR_INT_TYPE __PTRDIFF_TYPE__
65 #else
66 # ifdef HAVE_STDDEF_H
67 #  include <stddef.h>
68 #  define PTR_INT_TYPE ptrdiff_t
69 # else
70 #  define PTR_INT_TYPE long
71 # endif
72 #endif
73
74 #include "canonicalize.h"
75 #include "pathmax.h"
76 #include "stat-macros.h"
77 #include "xreadlink.h"
78
79 #if !HAVE_CANONICALIZE_FILE_NAME
80 /* Return the canonical absolute name of file NAME.  A canonical name
81    does not contain any `.', `..' components nor any repeated path
82    separators ('/') or symlinks.  All path components must exist.
83    The result is malloc'd.  */
84
85 char *
86 canonicalize_file_name (const char *name)
87 {
88 # if HAVE_RESOLVEPATH
89
90   char *resolved, *extra_buf = NULL;
91   size_t resolved_size;
92   ssize_t resolved_len;
93
94   if (name == NULL)
95     {
96       __set_errno (EINVAL);
97       return NULL;
98     }
99
100   if (name[0] == '\0')
101     {
102       __set_errno (ENOENT);
103       return NULL;
104     }
105
106   /* All known hosts with resolvepath (e.g. Solaris 7) don't turn
107      relative names into absolute ones, so prepend the working
108      directory if the path is not absolute.  */
109   if (name[0] != '/')
110     {
111       char *wd;
112
113       if (!(wd = xgetcwd ()))
114         return NULL;
115
116       extra_buf = path_concat (wd, name, NULL);
117       name = extra_buf;
118       free (wd);
119     }
120
121   resolved_size = strlen (name);
122   while (1)
123     {
124       resolved_size = 2 * resolved_size + 1;
125       resolved = xmalloc (resolved_size);
126       resolved_len = resolvepath (name, resolved, resolved_size);
127       if (resolved_len < 0)
128         {
129           free (resolved);
130           free (extra_buf);
131           return NULL;
132         }
133       if (resolved_len < resolved_size)
134         break;
135       free (resolved);
136     }
137
138   free (extra_buf);
139
140   /* NUL-terminate the resulting name.  */
141   resolved[resolved_len] = '\0';
142
143   return resolved;
144
145 # else
146
147   return canonicalize_filename_mode (name, CAN_EXISTING);
148
149 # endif /* !HAVE_RESOLVEPATH */
150 }
151 #endif /* !HAVE_CANONICALIZE_FILE_NAME */
152
153 /* Return the canonical absolute name of file NAME.  A canonical name
154    does not contain any `.', `..' components nor any repeated path
155    separators ('/') or symlinks.  Whether path components must exist
156    or not depends on canonicalize mode.  The result is malloc'd.  */
157
158 char *
159 canonicalize_filename_mode (const char *name, canonicalize_mode_t can_mode)
160 {
161   char *rpath, *dest, *extra_buf = NULL;
162   const char *start, *end, *rpath_limit;
163   size_t extra_len = 0;
164   struct cycle_check_state cycle_state;
165
166   if (name == NULL)
167     {
168       __set_errno (EINVAL);
169       return NULL;
170     }
171
172   if (name[0] == '\0')
173     {
174       __set_errno (ENOENT);
175       return NULL;
176     }
177
178   if (name[0] != '/')
179     {
180       rpath = xgetcwd ();
181       if (!rpath)
182         return NULL;
183       dest = strchr (rpath, '\0');
184       if (dest - rpath < PATH_MAX)
185         {
186           char *p = xrealloc (rpath, PATH_MAX);
187           dest = p + (dest - rpath);
188           rpath = p;
189           rpath_limit = rpath + PATH_MAX;
190         }
191       else
192         {
193           rpath_limit = dest;
194         }
195     }
196   else
197     {
198       rpath = xmalloc (PATH_MAX);
199       rpath_limit = rpath + PATH_MAX;
200       rpath[0] = '/';
201       dest = rpath + 1;
202     }
203
204   cycle_check_init (&cycle_state);
205   for (start = end = name; *start; start = end)
206     {
207       /* Skip sequence of multiple path-separators.  */
208       while (*start == '/')
209         ++start;
210
211       /* Find end of path component.  */
212       for (end = start; *end && *end != '/'; ++end)
213         /* Nothing.  */;
214
215       if (end - start == 0)
216         break;
217       else if (end - start == 1 && start[0] == '.')
218         /* nothing */;
219       else if (end - start == 2 && start[0] == '.' && start[1] == '.')
220         {
221           /* Back up to previous component, ignore if at root already.  */
222           if (dest > rpath + 1)
223             while ((--dest)[-1] != '/');
224         }
225       else
226         {
227           struct stat st;
228
229           if (dest[-1] != '/')
230             *dest++ = '/';
231
232           if (dest + (end - start) >= rpath_limit)
233             {
234               PTR_INT_TYPE dest_offset = dest - rpath;
235               size_t new_size = rpath_limit - rpath;
236
237               if (end - start + 1 > PATH_MAX)
238                 new_size += end - start + 1;
239               else
240                 new_size += PATH_MAX;
241               rpath = xrealloc (rpath, new_size);
242               rpath_limit = rpath + new_size;
243
244               dest = rpath + dest_offset;
245             }
246
247           dest = memcpy (dest, start, end - start);
248           dest += end - start;
249           *dest = '\0';
250
251           if (lstat (rpath, &st) < 0)
252             {
253               if (can_mode == CAN_EXISTING)
254                 goto error;
255               if (can_mode == CAN_ALL_BUT_LAST && *end)
256                 goto error;
257               st.st_mode = 0;
258             }
259
260           if (S_ISLNK (st.st_mode))
261             {
262               char *buf;
263               size_t n, len;
264
265               if (cycle_check (&cycle_state, &st))
266                 {
267                   __set_errno (ELOOP);
268                   if (can_mode == CAN_MISSING)
269                     continue;
270                   else
271                     goto error;
272                 }
273
274               buf = xreadlink (rpath, st.st_size);
275               if (!buf)
276                 {
277                   if (can_mode == CAN_MISSING)
278                     continue;
279                   else
280                     goto error;
281                 }
282
283               n = strlen (buf);
284               len = strlen (end);
285
286               if (!extra_len)
287                 {
288                   extra_len =
289                     ((n + len + 1) > PATH_MAX) ? (n + len + 1) : PATH_MAX;
290                   extra_buf = xmalloc (extra_len);
291                 }
292               else if ((n + len + 1) > extra_len)
293                 {
294                   extra_len = n + len + 1;
295                   extra_buf = xrealloc (extra_buf, extra_len);
296                 }
297
298               /* Careful here, end may be a pointer into extra_buf... */
299               memmove (&extra_buf[n], end, len + 1);
300               name = end = memcpy (extra_buf, buf, n);
301
302               if (buf[0] == '/')
303                 dest = rpath + 1;       /* It's an absolute symlink */
304               else
305                 /* Back up to previous component, ignore if at root already: */
306                 if (dest > rpath + 1)
307                   while ((--dest)[-1] != '/');
308
309               free (buf);
310             }
311           else
312             {
313               if (!S_ISDIR (st.st_mode) && *end && (can_mode != CAN_MISSING))
314                 {
315                   errno = ENOTDIR;
316                   goto error;
317                 }
318             }
319         }
320     }
321   if (dest > rpath + 1 && dest[-1] == '/')
322     --dest;
323   *dest = '\0';
324
325   free (extra_buf);
326   return rpath;
327
328 error:
329   free (extra_buf);
330   free (rpath);
331   return NULL;
332 }