Update.
[kopensolaris-gnu/glibc.git] / stdlib / canonicalize.c
1 /* Return the canonical absolute name of a given file.
2    Copyright (C) 1996, 1997, 1998, 1999 Free Software Foundation, Inc.
3    This file is part of the GNU C Library.
4
5    The GNU C Library is free software; you can redistribute it and/or
6    modify it under the terms of the GNU Library General Public License as
7    published by the Free Software Foundation; either version 2 of the
8    License, or (at your option) any later version.
9
10    The GNU C Library is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    Library General Public License for more details.
14
15    You should have received a copy of the GNU Library General Public
16    License along with the GNU C Library; see the file COPYING.LIB.  If not,
17    write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18    Boston, MA 02111-1307, USA.  */
19
20 #include <stdlib.h>
21 #include <string.h>
22 #include <unistd.h>
23 #include <limits.h>
24 #include <sys/param.h>
25 #include <sys/stat.h>
26 #include <errno.h>
27 #include <stddef.h>
28
29 /* Return the canonical absolute name of file NAME.  A canonical name
30    does not contain any `.', `..' components nor any repeated path
31    separators ('/') or symlinks.  All path components must exist.  If
32    RESOLVED is null, the result is malloc'd; otherwise, if the
33    canonical name is PATH_MAX chars or more, returns null with `errno'
34    set to ENAMETOOLONG; if the name fits in fewer than PATH_MAX chars,
35    returns the name in RESOLVED.  If the name cannot be resolved and
36    RESOLVED is non-NULL, it contains the path of the first component
37    that cannot be resolved.  If the path can be resolved, RESOLVED
38    holds the same value as the value returned.  */
39
40 static char *
41 canonicalize (const char *name, char *resolved)
42 {
43   char *rpath, *dest, *extra_buf = NULL;
44   const char *start, *end, *rpath_limit;
45   long int path_max;
46   int num_links = 0;
47
48   if (name == NULL)
49     {
50       /* As per Single Unix Specification V2 we must return an error if
51          either parameter is a null pointer.  We extend this to allow
52          the RESOLVED parameter to be NULL in case the we are expected to
53          allocate the room for the return value.  */
54       __set_errno (EINVAL);
55       return NULL;
56     }
57
58   if (name[0] == '\0')
59     {
60       /* As per Single Unix Specification V2 we must return an error if
61          the name argument points to an empty string.  */
62       __set_errno (ENOENT);
63       return NULL;
64     }
65
66 #ifdef PATH_MAX
67   path_max = PATH_MAX;
68 #else
69   path_max = pathconf (name, _PC_PATH_MAX);
70   if (path_max <= 0)
71     path_max = 1024;
72 #endif
73
74   rpath = resolved ? __alloca (path_max) : malloc (path_max);
75   rpath_limit = rpath + path_max;
76
77   if (name[0] != '/')
78     {
79       if (!__getcwd (rpath, path_max))
80         {
81           rpath[0] = '\0';
82           goto error;
83         }
84       dest = strchr (rpath, '\0');
85     }
86   else
87     {
88       rpath[0] = '/';
89       dest = rpath + 1;
90     }
91
92   for (start = end = name; *start; start = end)
93     {
94       struct stat st;
95       int n;
96
97       /* Skip sequence of multiple path-separators.  */
98       while (*start == '/')
99         ++start;
100
101       /* Find end of path component.  */
102       for (end = start; *end && *end != '/'; ++end)
103         /* Nothing.  */;
104
105       if (end - start == 0)
106         break;
107       else if (end - start == 1 && start[0] == '.')
108         /* nothing */;
109       else if (end - start == 2 && start[0] == '.' && start[1] == '.')
110         {
111           /* Back up to previous component, ignore if at root already.  */
112           if (dest > rpath + 1)
113             while ((--dest)[-1] != '/');
114         }
115       else
116         {
117           size_t new_size;
118
119           if (dest[-1] != '/')
120             *dest++ = '/';
121
122           if (dest + (end - start) >= rpath_limit)
123             {
124               ptrdiff_t dest_offset = dest - rpath;
125
126               if (resolved)
127                 {
128                   __set_errno (ENAMETOOLONG);
129                   if (dest > rpath + 1)
130                     dest--;
131                   *dest = '\0';
132                   goto error;
133                 }
134               new_size = rpath_limit - rpath;
135               if (end - start + 1 > path_max)
136                 new_size += end - start + 1;
137               else
138                 new_size += path_max;
139               rpath = realloc (rpath, new_size);
140               rpath_limit = rpath + new_size;
141               if (rpath == NULL)
142                 return NULL;
143
144               dest = rpath + dest_offset;
145             }
146
147           dest = __mempcpy (dest, start, end - start);
148           *dest = '\0';
149
150           if (__lxstat (_STAT_VER, rpath, &st) < 0)
151             goto error;
152
153           if (S_ISLNK (st.st_mode))
154             {
155               char *buf = __alloca (path_max);
156               size_t len;
157
158               if (++num_links > MAXSYMLINKS)
159                 {
160                   __set_errno (ELOOP);
161                   goto error;
162                 }
163
164               n = __readlink (rpath, buf, path_max);
165               if (n < 0)
166                 goto error;
167               buf[n] = '\0';
168
169               if (!extra_buf)
170                 extra_buf = __alloca (path_max);
171
172               len = strlen (end);
173               if ((long int) (n + len) >= path_max)
174                 {
175                   __set_errno (ENAMETOOLONG);
176                   goto error;
177                 }
178
179               /* Careful here, end may be a pointer into extra_buf... */
180               memmove (&extra_buf[n], end, len + 1);
181               name = end = memcpy (extra_buf, buf, n);
182
183               if (buf[0] == '/')
184                 dest = rpath + 1;       /* It's an absolute symlink */
185               else
186                 /* Back up to previous component, ignore if at root already: */
187                 if (dest > rpath + 1)
188                   while ((--dest)[-1] != '/');
189             }
190         }
191     }
192   if (dest > rpath + 1 && dest[-1] == '/')
193     --dest;
194   *dest = '\0';
195
196   return resolved ? memcpy (resolved, rpath, dest - rpath + 1) : rpath;
197
198 error:
199   if (resolved)
200     strcpy (resolved, rpath);
201   else
202     free (rpath);
203   return NULL;
204 }
205
206
207 char *
208 __realpath (const char *name, char *resolved)
209 {
210   if (resolved == NULL)
211     {
212       __set_errno (EINVAL);
213       return NULL;
214     }
215
216   return canonicalize (name, resolved);
217 }
218 weak_alias (__realpath, realpath)
219
220
221 char *
222 __canonicalize_file_name (const char *name)
223 {
224   return canonicalize (name, NULL);
225 }
226 weak_alias (__canonicalize_file_name, canonicalize_file_name)