XEmacs 21.2.20 "Yoko".
[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 #ifndef PATH_MAX
47 #ifdef _POSIX_VERSION
48 #define PATH_MAX _POSIX_PATH_MAX
49 #else
50 #ifdef MAXPATHLEN
51 #define PATH_MAX MAXPATHLEN
52 #else
53 #define PATH_MAX 1024
54 #endif
55 #endif
56 #endif
57
58 #define MAX_READLINKS 32
59
60 char *
61 xrealpath (const char *path, char resolved_path [])
62 {
63   char copy_path[PATH_MAX];
64   char *new_path = resolved_path;
65   char *max_path;
66 #ifdef S_IFLNK
67   int readlinks = 0;
68   char link_path[PATH_MAX];
69   int n;
70 #endif
71
72   /* Make a copy of the source path since we may need to modify it. */
73   strcpy(copy_path, path);
74   path = copy_path;
75   max_path = copy_path + PATH_MAX - 2;
76 #ifdef WINDOWSNT
77   /*
78   ** In NT we have two different cases:  (1) the path name begins
79   ** with a drive letter, e.g., "C:"; and (2) the path name begins
80   ** with just a slash, which roots to the current drive. In the
81   ** first case we are going to leave things alone, in the second
82   ** case we will prepend the drive letter to the given path.
83   ** Note: So far in testing, I'm only seeing case #1, even though
84   ** I've tried to get the other cases to happen.
85   ** August Hill, 31 Aug 1997.
86   **
87   ** Check for a driver letter...C:/...
88   */
89   if (*(path + 1) == ':')
90     {
91       strncpy(new_path, path, 3);
92       new_path += 3;
93       path += 3;
94     }
95
96   /*
97   ** No drive letter, but a beginning slash? Prepend the drive
98   ** letter...
99   */
100   else if (*path == '/')
101     {
102       getcwd(new_path, PATH_MAX - 1);
103       new_path += 3;
104       path++;
105     }
106
107   /*
108   ** Just a path name, prepend the current directory
109   */
110   else
111     {
112       getcwd(new_path, PATH_MAX - 1);
113       new_path += strlen(new_path);
114       if (new_path[-1] != '/')
115         *new_path++ = '/';
116     }
117
118 #else
119   /* If it's a relative pathname use getwd for starters. */
120   if (*path != '/')
121     {
122 #ifdef HAVE_GETCWD
123       getcwd(new_path, PATH_MAX - 1);
124 #else
125       getwd(new_path);
126 #endif
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 }