xref: /netbsd-src/external/gpl2/xcvs/dist/lib/canonicalize.c (revision 5a6c14c844c4c665da5632061aebde7bb2cb5766)
1 /* Return the canonical absolute name of a given file.
2    Copyright (C) 1996-2005 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    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
18 #include <sys/cdefs.h>
19 __RCSID("$NetBSD: canonicalize.c,v 1.2 2016/05/17 14:00:09 christos Exp $");
20 
21 
22 #ifdef HAVE_CONFIG_H
23 # include <config.h>
24 #endif
25 
26 #include "canonicalize.h"
27 
28 #ifdef STDC_HEADERS
29 # include <stdlib.h>
30 #else
31 void free ();
32 #endif
33 
34 #if defined STDC_HEADERS || defined HAVE_STRING_H
35 # include <string.h>
36 #else
37 # include <strings.h>
38 #endif
39 
40 #if HAVE_SYS_PARAM_H
41 # include <sys/param.h>
42 #endif
43 
44 #include <sys/stat.h>
45 
46 #if HAVE_UNISTD_H
47 # include <unistd.h>
48 #endif
49 
50 #include <errno.h>
51 #include <stddef.h>
52 
53 #include "cycle-check.h"
54 #include "filenamecat.h"
55 #include "stat-macros.h"
56 #include "xalloc.h"
57 #include "xgetcwd.h"
58 
59 #ifndef __set_errno
60 # define __set_errno(Val) errno = (Val)
61 #endif
62 
63 #include "pathmax.h"
64 #include "xreadlink.h"
65 
66 #if !HAVE_CANONICALIZE_FILE_NAME
67 /* Return the canonical absolute name of file NAME.  A canonical name
68    does not contain any `.', `..' components nor any repeated file name
69    separators ('/') or symlinks.  All components must exist.
70    The result is malloc'd.  */
71 
72 char *
canonicalize_file_name(const char * name)73 canonicalize_file_name (const char *name)
74 {
75 # if HAVE_RESOLVEPATH
76 
77   char *resolved, *extra_buf = NULL;
78   size_t resolved_size;
79   ssize_t resolved_len;
80 
81   if (name == NULL)
82     {
83       __set_errno (EINVAL);
84       return NULL;
85     }
86 
87   if (name[0] == '\0')
88     {
89       __set_errno (ENOENT);
90       return NULL;
91     }
92 
93   /* All known hosts with resolvepath (e.g. Solaris 7) don't turn
94      relative names into absolute ones, so prepend the working
95      directory if the file name is not absolute.  */
96   if (name[0] != '/')
97     {
98       char *wd;
99 
100       if (!(wd = xgetcwd ()))
101 	return NULL;
102 
103       extra_buf = file_name_concat (wd, name, NULL);
104       name = extra_buf;
105       free (wd);
106     }
107 
108   resolved_size = strlen (name);
109   while (1)
110     {
111       resolved_size = 2 * resolved_size + 1;
112       resolved = xmalloc (resolved_size);
113       resolved_len = resolvepath (name, resolved, resolved_size);
114       if (resolved_len < 0)
115 	{
116 	  free (resolved);
117 	  free (extra_buf);
118 	  return NULL;
119 	}
120       if (resolved_len < resolved_size)
121 	break;
122       free (resolved);
123     }
124 
125   free (extra_buf);
126 
127   /* NUL-terminate the resulting name.  */
128   resolved[resolved_len] = '\0';
129 
130   return resolved;
131 
132 # else
133 
134   return canonicalize_filename_mode (name, CAN_EXISTING);
135 
136 # endif /* !HAVE_RESOLVEPATH */
137 }
138 #endif /* !HAVE_CANONICALIZE_FILE_NAME */
139 
140 /* Return the canonical absolute name of file NAME.  A canonical name
141    does not contain any `.', `..' components nor any repeated file name
142    separators ('/') or symlinks.  Whether components must exist
143    or not depends on canonicalize mode.  The result is malloc'd.  */
144 
145 char *
canonicalize_filename_mode(const char * name,canonicalize_mode_t can_mode)146 canonicalize_filename_mode (const char *name, canonicalize_mode_t can_mode)
147 {
148   char *rname, *dest, *extra_buf = NULL;
149   char const *start;
150   char const *end;
151   char const *rname_limit;
152   size_t extra_len = 0;
153   struct cycle_check_state cycle_state;
154 
155   if (name == NULL)
156     {
157       __set_errno (EINVAL);
158       return NULL;
159     }
160 
161   if (name[0] == '\0')
162     {
163       __set_errno (ENOENT);
164       return NULL;
165     }
166 
167   if (name[0] != '/')
168     {
169       rname = xgetcwd ();
170       if (!rname)
171 	return NULL;
172       dest = strchr (rname, '\0');
173       if (dest - rname < PATH_MAX)
174 	{
175 	  char *p = xrealloc (rname, PATH_MAX);
176 	  dest = p + (dest - rname);
177 	  rname = p;
178 	  rname_limit = rname + PATH_MAX;
179 	}
180       else
181 	{
182 	  rname_limit = dest;
183 	}
184     }
185   else
186     {
187       rname = xmalloc (PATH_MAX);
188       rname_limit = rname + PATH_MAX;
189       rname[0] = '/';
190       dest = rname + 1;
191     }
192 
193   cycle_check_init (&cycle_state);
194   for (start = end = 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 	      if (cycle_check (&cycle_state, &st))
255 		{
256 		  __set_errno (ELOOP);
257 		  if (can_mode == CAN_MISSING)
258 		    continue;
259 		  else
260 		    goto error;
261 		}
262 
263 	      buf = xreadlink (rname, st.st_size);
264 	      if (!buf)
265 		{
266 		  if (can_mode == CAN_MISSING)
267 		    continue;
268 		  else
269 		    goto error;
270 		}
271 
272 	      n = strlen (buf);
273 	      len = strlen (end);
274 
275 	      if (!extra_len)
276 		{
277 		  extra_len =
278 		    ((n + len + 1) > PATH_MAX) ? (n + len + 1) : PATH_MAX;
279 		  extra_buf = xmalloc (extra_len);
280 		}
281 	      else if ((n + len + 1) > extra_len)
282 		{
283 		  extra_len = n + len + 1;
284 		  extra_buf = xrealloc (extra_buf, extra_len);
285 		}
286 
287 	      /* Careful here, end may be a pointer into extra_buf... */
288 	      memmove (&extra_buf[n], end, len + 1);
289 	      name = end = memcpy (extra_buf, buf, n);
290 
291 	      if (buf[0] == '/')
292 		dest = rname + 1;	/* It's an absolute symlink */
293 	      else
294 		/* Back up to previous component, ignore if at root already: */
295 		if (dest > rname + 1)
296 		  while ((--dest)[-1] != '/');
297 
298 	      free (buf);
299 	    }
300 	  else
301 	    {
302 	      if (!S_ISDIR (st.st_mode) && *end && (can_mode != CAN_MISSING))
303 		{
304 		  errno = ENOTDIR;
305 		  goto error;
306 		}
307 	    }
308 	}
309     }
310   if (dest > rname + 1 && dest[-1] == '/')
311     --dest;
312   *dest = '\0';
313 
314   free (extra_buf);
315   return rname;
316 
317 error:
318   free (extra_buf);
319   free (rname);
320   return NULL;
321 }
322