XEmacs 21.2.46 "Urania".
[chise/xemacs-chise.git.1] / src / realpath.c
1 /*
2  * realpath.c -- canonicalize pathname by removing symlinks
3  * Copyright (C) 1993 Rick Sladkey <jrs@world.std.com>
4  *
5
6 This file is part of XEmacs.
7
8 XEmacs is free software; you can redistribute it and/or modify it
9 under the terms of the GNU General Public License as published by the
10 Free Software Foundation; either version 2, or (at your option) any
11 later version.
12
13 XEmacs is distributed in the hope that it will be useful, but WITHOUT
14 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
15 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
16 for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with XEmacs; see the file COPYING.  If not, write to
20 the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21 Boston, MA 02111-1307, USA.  */
22
23 /* Synched up with: Not in FSF. */
24
25 #include <config.h>
26 #include "lisp.h"
27 #include <errno.h>
28
29 #ifdef HAVE_UNISTD_H
30 #include <unistd.h>
31 #endif
32
33 #if defined (HAVE_SYS_PARAM_H) && !defined (WIN32_NATIVE)
34 #include <sys/param.h>
35 #endif
36
37 #ifdef WIN32_NATIVE
38 #include <direct.h>
39 #endif
40
41 #include <sys/stat.h>                   /* for S_IFLNK */
42
43 /* First char after start of absolute filename. */
44 #define ABS_START(name) (name + ABS_LENGTH (name))
45
46 #if defined (WIN32_NATIVE)
47 /* Length of start of absolute filename. */
48 # define ABS_LENGTH(name) (win32_abs_start (name))
49 static int win32_abs_start (const char * name);
50 /* System dependent version of readlink. */
51 # define system_readlink win32_readlink
52 #else
53 # ifdef CYGWIN
54 #  define ABS_LENGTH(name) (IS_DIRECTORY_SEP (*name) ? \
55                             (IS_DIRECTORY_SEP (name[1]) ? 2 : 1) : 0)
56 #  define system_readlink cygwin_readlink
57 # else
58 #  define ABS_LENGTH(name) (IS_DIRECTORY_SEP (*name) ? 1 : 0)
59 #  define system_readlink readlink
60 # endif /* CYGWIN */
61 #endif /* WIN32_NATIVE */
62
63 #if defined (WIN32_NATIVE) || defined (CYGWIN)
64 #include "syswindows.h"
65 /* Emulate readlink on win32 - finds real name (i.e. correct case) of
66    a file. UNC servers and shares are lower-cased. Directories must be
67    given without trailing '/'. One day, this could read Win2K's
68    reparse points. */
69 static int
70 win32_readlink (const char * name, char * buf, int size)
71 {
72   WIN32_FIND_DATA find_data;
73   HANDLE dir_handle = NULL;
74   int len = 0;
75   int err = 0;
76   const char* lastname;
77   int count = 0;
78   const char* tmp;
79   char* res = NULL;
80   
81   assert (*name);
82   
83   /* Sort of check we have a valid filename. */
84   if (strpbrk (name, "*?|<>\"") || strlen (name) >= MAX_PATH)
85     {
86       errno = EIO;
87       return -1;
88     }
89   
90   /* Find start of filename */
91   lastname = name + strlen (name);
92   while (lastname > name && !IS_DIRECTORY_SEP (lastname[-1]))
93     --lastname;
94
95   /* Count slashes in unc path */
96   if (ABS_LENGTH (name) == 2)
97     for (tmp = name; *tmp; tmp++)
98       if (IS_DIRECTORY_SEP (*tmp))
99         count++;
100
101   if (count >= 2 && count < 4)
102     {
103       /* UNC server or share name: just copy lowercased name. */
104       res = find_data.cFileName;
105       for (tmp = lastname; *tmp; tmp++)
106         *res++ = tolower (*tmp);
107       *res = '\0';
108     }
109   else
110     dir_handle = FindFirstFile (name, &find_data);
111
112   if (res || dir_handle != INVALID_HANDLE_VALUE)
113     {
114       if ((len = strlen (find_data.cFileName)) < size)
115         {
116           if (strcmp (lastname, find_data.cFileName) == 0)
117             /* Signal that the name is already OK. */
118             err = EINVAL;
119           else
120             memcpy (buf, find_data.cFileName, len + 1);
121         }
122       else
123         err = ENAMETOOLONG;
124       if (!res) FindClose (dir_handle);
125     }
126   else
127     err = ENOENT;
128
129   errno = err;
130   return err ? -1 : len;
131 }
132 #endif /* WIN32_NATIVE || CYGWIN */
133
134 #ifdef CYGWIN
135 /* Call readlink and try to find out the correct case for the file. */
136 static int
137 cygwin_readlink (const char * name, char * buf, int size)
138 {
139   int n = readlink (name, buf, size);
140   if (n < 0 && errno == EINVAL)
141     {
142       /* The file may exist, but isn't a symlink. Try to find the
143          right name. */
144       char* tmp = alloca (cygwin_posix_to_win32_path_list_buf_size (name));
145       cygwin_posix_to_win32_path_list (name, tmp);
146       n = win32_readlink (tmp, buf, size);
147     }
148   return n;
149 }
150 #endif /* CYGWIN */
151
152 #ifdef WIN32_NATIVE
153 #ifndef ELOOP
154 #define ELOOP 10062 /* = WSAELOOP in winsock.h */
155 #endif
156 /* Length of start of absolute filename. */
157 static int 
158 win32_abs_start (const char * name)
159 {
160   if (isalpha (*name) && IS_DEVICE_SEP (name[1])
161       && IS_DIRECTORY_SEP (name[2]))
162     return 3;
163   else if (IS_DIRECTORY_SEP (*name))
164     return IS_DIRECTORY_SEP (name[1]) ? 2 : 1;
165   else 
166     return 0;
167 }
168 #endif /* WIN32_NATIVE */
169
170 #if !defined (HAVE_GETCWD) && defined (HAVE_GETWD)
171 #undef getcwd
172 #define getcwd(buffer, len) getwd (buffer)
173 #endif
174
175 #ifndef PATH_MAX
176 # if defined (_POSIX_PATH_MAX)
177 #  define PATH_MAX _POSIX_PATH_MAX
178 # elif defined (MAXPATHLEN)
179 #  define PATH_MAX MAXPATHLEN
180 # else
181 #  define PATH_MAX 1024
182 # endif
183 #endif
184
185 #define MAX_READLINKS 32
186
187 char * xrealpath (const char *path, char resolved_path []);
188 char *
189 xrealpath (const char *path, char resolved_path [])
190 {
191   char copy_path[PATH_MAX];
192   char *new_path = resolved_path;
193   char *max_path;
194 #if defined (S_IFLNK) || defined (WIN32_NATIVE)
195   int readlinks = 0;
196   char link_path[PATH_MAX];
197   int n;
198   int abslen = ABS_LENGTH (path);
199 #endif
200
201   /* Make a copy of the source path since we may need to modify it. */
202   strcpy (copy_path, path);
203   path = copy_path;
204   max_path = copy_path + PATH_MAX - 2;
205
206 #ifdef WIN32_NATIVE
207   /* Check for c:/... or //server/... */
208   if (abslen == 2 || abslen == 3)
209     {
210       strncpy (new_path, path, abslen);
211       /* Make sure drive letter is lowercased. */
212       if (abslen == 3)
213         *new_path = tolower (*new_path);
214       new_path += abslen;
215       path += abslen;
216     }
217   /* No drive letter, but a beginning slash? Prepend drive letter. */
218   else if (abslen == 1)
219     {
220       getcwd (new_path, PATH_MAX - 1);
221       new_path += 3;
222       path++;
223     }
224   /* Just a path name, prepend the current directory */
225   else
226     {
227       getcwd (new_path, PATH_MAX - 1);
228       new_path += strlen (new_path);
229       if (!IS_DIRECTORY_SEP (new_path[-1]))
230         *new_path++ = DIRECTORY_SEP;
231     }
232 #else
233   /* If it's a relative pathname use getcwd for starters. */
234   if (abslen == 0)
235     {
236       getcwd (new_path, PATH_MAX - 1);
237       new_path += strlen (new_path);
238       if (!IS_DIRECTORY_SEP (new_path[-1]))
239         *new_path++ = DIRECTORY_SEP;
240     }
241   else
242     {
243       /* Copy first directory sep. May have two on cygwin. */
244       strncpy (new_path, path, abslen);
245       new_path += abslen;
246       path += abslen;
247     }
248 #endif
249   /* Expand each slash-separated pathname component. */
250   while (*path != '\0')
251     {
252       /* Ignore stray "/". */
253       if (IS_DIRECTORY_SEP (*path))
254         {
255           path++;
256           continue;
257         }
258
259       if (*path == '.')
260         {
261           /* Ignore ".". */
262           if (path[1] == '\0' || IS_DIRECTORY_SEP (path[1]))
263             {
264               path++;
265               continue;
266             }
267
268           /* Handle ".." */
269           if (path[1] == '.' &&
270               (path[2] == '\0' || IS_DIRECTORY_SEP (path[2])))
271             {
272               path += 2;
273
274               /* Ignore ".." at root. */
275               if (new_path == ABS_START (resolved_path))
276                 continue;
277
278               /* Handle ".." by backing up. */
279               --new_path;
280               while (!IS_DIRECTORY_SEP (new_path[-1]))
281                 --new_path;
282               continue;
283             }
284         }
285
286       /* Safely copy the next pathname component. */
287       while (*path != '\0' && !IS_DIRECTORY_SEP (*path))
288         {
289           if (path > max_path)
290             {
291               errno = ENAMETOOLONG;
292               return NULL;
293             }
294           *new_path++ = *path++;
295         }
296
297 #if defined (S_IFLNK) || defined (WIN32_NATIVE)
298       /* See if latest pathname component is a symlink. */
299       *new_path = '\0';
300       n = system_readlink (resolved_path, link_path, PATH_MAX - 1);
301
302       if (n < 0)
303         {
304           /* EINVAL means the file exists but isn't a symlink. */
305 #ifdef CYGWIN
306           if (errno != EINVAL && errno != ENOENT)
307 #else
308           if (errno != EINVAL) 
309 #endif
310             return NULL;
311         }
312       else
313         {
314           /* Protect against infinite loops. */
315           if (readlinks++ > MAX_READLINKS)
316             {
317               errno = ELOOP;
318               return NULL;
319             }
320
321           /* Note: readlink doesn't add the null byte. */
322           link_path[n] = '\0';
323           
324           if (ABS_LENGTH (link_path) > 0)
325             /* Start over for an absolute symlink. */
326             new_path = resolved_path + ABS_LENGTH (link_path) - 1;
327           else
328             /* Otherwise back up over this component. */
329             for (--new_path; !IS_DIRECTORY_SEP (*new_path); --new_path)
330               assert (new_path > resolved_path);
331
332           /* Safe sex check. */
333           if (strlen(path) + n >= PATH_MAX)
334             {
335               errno = ENAMETOOLONG;
336               return NULL;
337             }
338
339           /* Insert symlink contents into path. */
340           strcat(link_path, path);
341           strcpy(copy_path, link_path);
342           path = copy_path;
343         }
344 #endif /* S_IFLNK || WIN32_NATIVE */
345       *new_path++ = DIRECTORY_SEP;
346     }
347
348   /* Delete trailing slash but don't whomp a lone slash. */
349   if (new_path != ABS_START (resolved_path) && IS_DIRECTORY_SEP (new_path[-1]))
350     new_path--;
351
352   /* Make sure it's null terminated. */
353   *new_path = '\0';
354
355   return resolved_path;
356 }