XEmacs 21.2.28 "Hermes".
[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 #ifdef HAVE_UNISTD_H
32 #include <unistd.h>
33 #endif
34 #ifdef _POSIX_VERSION
35 #include <limits.h>                     /* for PATH_MAX */
36 #else
37 #include <sys/param.h>                  /* for MAXPATHLEN */
38 #endif
39
40 #ifdef WINDOWSNT
41 #include <direct.h>
42 #endif
43
44 #include <sys/stat.h>                   /* for S_IFLNK */
45
46 #if !defined (HAVE_GETCWD) && defined (HAVE_GETWD)
47 #undef getcwd
48 #define getcwd(buffer, len) getwd (buffer)
49 #endif
50
51 #ifndef PATH_MAX
52 # if defined (_POSIX_PATH_MAX)
53 #  define PATH_MAX _POSIX_PATH_MAX
54 # elif defined (MAXPATHLEN)
55 #  define PATH_MAX MAXPATHLEN
56 # else
57 #  define PATH_MAX 1024
58 # endif
59 #endif
60
61 #define MAX_READLINKS 32
62
63 char * xrealpath (const char *path, char resolved_path []);
64 char *
65 xrealpath (const char *path, char resolved_path [])
66 {
67   char copy_path[PATH_MAX];
68   char *new_path = resolved_path;
69   char *max_path;
70 #ifdef S_IFLNK
71   int readlinks = 0;
72   char link_path[PATH_MAX];
73   int n;
74 #endif
75
76   /* Make a copy of the source path since we may need to modify it. */
77   strcpy(copy_path, path);
78   path = copy_path;
79   max_path = copy_path + PATH_MAX - 2;
80 #ifdef WINDOWSNT
81   /*
82   ** In NT we have two different cases:  (1) the path name begins
83   ** with a drive letter, e.g., "C:"; and (2) the path name begins
84   ** with just a slash, which roots to the current drive. In the
85   ** first case we are going to leave things alone, in the second
86   ** case we will prepend the drive letter to the given path.
87   ** Note: So far in testing, I'm only seeing case #1, even though
88   ** I've tried to get the other cases to happen.
89   ** August Hill, 31 Aug 1997.
90   **
91   ** Check for a driver letter...C:/...
92   */
93   if (*(path + 1) == ':')
94     {
95       strncpy(new_path, path, 3);
96       new_path += 3;
97       path += 3;
98     }
99
100   /*
101   ** No drive letter, but a beginning slash? Prepend the drive
102   ** letter...
103   */
104   else if (*path == '/')
105     {
106       getcwd (new_path, PATH_MAX - 1);
107       new_path += 3;
108       path++;
109     }
110
111   /*
112   ** Just a path name, prepend the current directory
113   */
114   else
115     {
116       getcwd (new_path, PATH_MAX - 1);
117       new_path += strlen(new_path);
118       if (new_path[-1] != '/')
119         *new_path++ = '/';
120     }
121
122 #else
123   /* If it's a relative pathname use getcwd for starters. */
124   if (*path != '/')
125     {
126       getcwd (new_path, PATH_MAX - 1);
127       new_path += strlen(new_path);
128       if (new_path[-1] != '/')
129         *new_path++ = '/';
130     }
131   else
132     {
133       *new_path++ = '/';
134       path++;
135     }
136 #endif
137   /* Expand each slash-separated pathname component. */
138   while (*path != '\0')
139     {
140       /* Ignore stray "/". */
141       if (*path == '/')
142         {
143           path++;
144           continue;
145         }
146
147       if (*path == '.')
148         {
149           /* Ignore ".". */
150           if (path[1] == '\0' || path[1] == '/')
151             {
152               path++;
153               continue;
154             }
155
156           if (path[1] == '.')
157             {
158               if (path[2] == '\0' || path[2] == '/')
159                 {
160                   path += 2;
161
162                   /* Ignore ".." at root. */
163                   if (new_path == resolved_path + 1)
164                     continue;
165
166                   /* Handle ".." by backing up. */
167                   while ((--new_path)[-1] != '/')
168                     ;
169                   continue;
170                 }
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 }