XEmacs 21.2.38 (Peisino)
[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)
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       new_path += abslen;
212       path += abslen;
213     }
214   /* No drive letter, but a beginning slash? Prepend drive letter. */
215   else if (abslen == 1)
216     {
217       getcwd (new_path, PATH_MAX - 1);
218       new_path += 3;
219       path++;
220     }
221   /* Just a path name, prepend the current directory */
222   else
223     {
224       getcwd (new_path, PATH_MAX - 1);
225       new_path += strlen (new_path);
226       if (!IS_DIRECTORY_SEP (new_path[-1]))
227         *new_path++ = DIRECTORY_SEP;
228     }
229 #else
230   /* If it's a relative pathname use getcwd for starters. */
231   if (abslen == 0)
232     {
233       getcwd (new_path, PATH_MAX - 1);
234       new_path += strlen (new_path);
235       if (!IS_DIRECTORY_SEP (new_path[-1]))
236         *new_path++ = DIRECTORY_SEP;
237     }
238   else
239     {
240       /* Copy first directory sep. May have two on cygwin. */
241       strncpy (new_path, path, abslen);
242       new_path += abslen;
243       path += abslen;
244     }
245 #endif
246   /* Expand each slash-separated pathname component. */
247   while (*path != '\0')
248     {
249       /* Ignore stray "/". */
250       if (IS_DIRECTORY_SEP (*path))
251         {
252           path++;
253           continue;
254         }
255
256       if (*path == '.')
257         {
258           /* Ignore ".". */
259           if (path[1] == '\0' || IS_DIRECTORY_SEP (path[1]))
260             {
261               path++;
262               continue;
263             }
264
265           /* Handle ".." */
266           if (path[1] == '.' &&
267               (path[2] == '\0' || IS_DIRECTORY_SEP (path[2])))
268             {
269               path += 2;
270
271               /* Ignore ".." at root. */
272               if (new_path == ABS_START (resolved_path))
273                 continue;
274
275               /* Handle ".." by backing up. */
276               --new_path;
277               while (!IS_DIRECTORY_SEP (new_path[-1]))
278                 --new_path;
279               continue;
280             }
281         }
282
283       /* Safely copy the next pathname component. */
284       while (*path != '\0' && !IS_DIRECTORY_SEP (*path))
285         {
286           if (path > max_path)
287             {
288               errno = ENAMETOOLONG;
289               return NULL;
290             }
291           *new_path++ = *path++;
292         }
293
294 #if defined (S_IFLNK) || defined (WIN32_NATIVE)
295       /* See if latest pathname component is a symlink. */
296       *new_path = '\0';
297       n = system_readlink (resolved_path, link_path, PATH_MAX - 1);
298
299       if (n < 0)
300         {
301           /* EINVAL means the file exists but isn't a symlink. */
302           if (errno != EINVAL)
303             return NULL;
304         }
305       else
306         {
307           /* Protect against infinite loops. */
308           if (readlinks++ > MAX_READLINKS)
309             {
310               errno = ELOOP;
311               return NULL;
312             }
313
314           /* Note: readlink doesn't add the null byte. */
315           link_path[n] = '\0';
316           
317           if (ABS_LENGTH (link_path) > 0)
318             /* Start over for an absolute symlink. */
319             new_path = resolved_path + ABS_LENGTH (link_path) - 1;
320           else
321             /* Otherwise back up over this component. */
322             for (--new_path; !IS_DIRECTORY_SEP (*new_path); --new_path)
323               assert (new_path > resolved_path);
324
325           /* Safe sex check. */
326           if (strlen(path) + n >= PATH_MAX)
327             {
328               errno = ENAMETOOLONG;
329               return NULL;
330             }
331
332           /* Insert symlink contents into path. */
333           strcat(link_path, path);
334           strcpy(copy_path, link_path);
335           path = copy_path;
336         }
337 #endif /* S_IFLNK || WIN32_NATIVE */
338       *new_path++ = DIRECTORY_SEP;
339     }
340
341   /* Delete trailing slash but don't whomp a lone slash. */
342   if (new_path != ABS_START (resolved_path) && IS_DIRECTORY_SEP (new_path[-1]))
343     new_path--;
344
345   /* Make sure it's null terminated. */
346   *new_path = '\0';
347
348 #ifdef WIN32_NATIVE
349   if (ABS_LENGTH (resolved_path) == 3)
350     /* Lowercase drive letter. */
351     *resolved_path = tolower (*resolved_path);
352 #endif
353   return resolved_path;
354 }