08d32d7bf23f96af6d74dca2e304cc5d36812570
[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
27 #include <sys/types.h>
28 #include <stdio.h>
29 #include <string.h>
30 #include <errno.h>
31 #include <limits.h>
32
33 #ifdef HAVE_UNISTD_H
34 #include <unistd.h>
35 #endif
36
37 #if defined (HAVE_SYS_PARAM_H)
38 #include <sys/param.h>
39 #endif
40
41 #ifdef WINDOWSNT
42 #include <direct.h>
43 #endif
44
45 #include <sys/stat.h>                   /* for S_IFLNK */
46
47 #if !defined (HAVE_GETCWD) && defined (HAVE_GETWD)
48 #undef getcwd
49 #define getcwd(buffer, len) getwd (buffer)
50 #endif
51
52 #ifndef PATH_MAX
53 # if defined (_POSIX_PATH_MAX)
54 #  define PATH_MAX _POSIX_PATH_MAX
55 # elif defined (MAXPATHLEN)
56 #  define PATH_MAX MAXPATHLEN
57 # else
58 #  define PATH_MAX 1024
59 # endif
60 #endif
61
62 #define MAX_READLINKS 32
63
64 char * xrealpath (const char *path, char resolved_path []);
65 char *
66 xrealpath (const char *path, char resolved_path [])
67 {
68   char copy_path[PATH_MAX];
69   char *new_path = resolved_path;
70   char *max_path;
71 #ifdef S_IFLNK
72   int readlinks = 0;
73   char link_path[PATH_MAX];
74   int n;
75 #endif
76
77   /* Make a copy of the source path since we may need to modify it. */
78   strcpy (copy_path, path);
79   path = copy_path;
80   max_path = copy_path + PATH_MAX - 2;
81 #ifdef WINDOWSNT
82   /*
83   ** In NT we have two different cases:  (1) the path name begins
84   ** with a drive letter, e.g., "C:"; and (2) the path name begins
85   ** with just a slash, which roots to the current drive. In the
86   ** first case we are going to leave things alone, in the second
87   ** case we will prepend the drive letter to the given path.
88   ** Note: So far in testing, I'm only seeing case #1, even though
89   ** I've tried to get the other cases to happen.
90   ** August Hill, 31 Aug 1997.
91   **
92   ** Check for a driver letter...C:/...
93   */
94   if (*(path + 1) == ':')
95     {
96       strncpy(new_path, path, 3);
97       new_path += 3;
98       path += 3;
99     }
100
101   /*
102   ** No drive letter, but a beginning slash? Prepend the drive
103   ** letter...
104   */
105   else if (*path == '/')
106     {
107       getcwd (new_path, PATH_MAX - 1);
108       new_path += 3;
109       path++;
110     }
111
112   /*
113   ** Just a path name, prepend the current directory
114   */
115   else
116     {
117       getcwd (new_path, PATH_MAX - 1);
118       new_path += strlen(new_path);
119       if (new_path[-1] != '/')
120         *new_path++ = '/';
121     }
122
123 #else
124   /* If it's a relative pathname use getcwd for starters. */
125   if (*path != '/')
126     {
127       getcwd (new_path, PATH_MAX - 1);
128       new_path += strlen(new_path);
129       if (new_path[-1] != '/')
130         *new_path++ = '/';
131     }
132   else
133     {
134       *new_path++ = '/';
135       path++;
136     }
137 #endif
138   /* Expand each slash-separated pathname component. */
139   while (*path != '\0')
140     {
141       /* Ignore stray "/". */
142       if (*path == '/')
143         {
144           path++;
145           continue;
146         }
147
148       if (*path == '.')
149         {
150           /* Ignore ".". */
151           if (path[1] == '\0' || path[1] == '/')
152             {
153               path++;
154               continue;
155             }
156
157           /* Handle ".." */
158           if (path[1] == '.' &&
159               (path[2] == '\0' || path[2] == '/'))
160             {
161               path += 2;
162
163               /* Ignore ".." at root. */
164               if (new_path == resolved_path + 1)
165                 continue;
166
167               /* Handle ".." by backing up. */
168               while ((--new_path)[-1] != '/')
169                 ;
170               continue;
171             }
172         }
173
174       /* Safely copy the next pathname component. */
175       while (*path != '\0' && *path != '/')
176         {
177           if (path > max_path)
178             {
179               errno = ENAMETOOLONG;
180               return NULL;
181             }
182           *new_path++ = *path++;
183         }
184
185 #ifdef S_IFLNK
186       /* See if latest pathname component is a symlink. */
187       *new_path = '\0';
188       n = readlink (resolved_path, link_path, PATH_MAX - 1);
189
190       if (n < 0)
191         {
192           /* EINVAL means the file exists but isn't a symlink. */
193           if (errno != EINVAL)
194             return NULL;
195         }
196       else
197         {
198           /* Protect against infinite loops. */
199           if (readlinks++ > MAX_READLINKS)
200             {
201               errno = ELOOP;
202               return NULL;
203             }
204
205           /* Note: readlink doesn't add the null byte. */
206           link_path[n] = '\0';
207
208           if (*link_path == '/')
209             /* Start over for an absolute symlink. */
210             new_path = resolved_path;
211           else
212             /* Otherwise back up over this component. */
213             while (*(--new_path) != '/')
214               ;
215
216           /* Safe sex check. */
217           if (strlen(path) + n >= PATH_MAX)
218             {
219               errno = ENAMETOOLONG;
220               return NULL;
221             }
222
223           /* Insert symlink contents into path. */
224           strcat(link_path, path);
225           strcpy(copy_path, link_path);
226           path = copy_path;
227         }
228 #endif /* S_IFLNK */
229       *new_path++ = '/';
230     }
231
232   /* Delete trailing slash but don't whomp a lone slash. */
233   if (new_path != resolved_path + 1 && new_path[-1] == '/')
234     new_path--;
235
236   /* Make sure it's null terminated. */
237   *new_path = '\0';
238   return resolved_path;
239 }