Merge remote-tracking branch 'origin/master' into for-steve
[sxemacs] / 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 SXEmacs
7
8 SXEmacs is free software: you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation, either version 3 of the License, or
11 (at your option) any later version.
12
13 SXEmacs is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with this program.  If not, see <http://www.gnu.org/licenses/>. */
20
21
22 /* Synched up with: Not in FSF. */
23
24 #include <config.h>
25 #include "lisp.h"
26 #include <errno.h>
27
28 #ifdef HAVE_UNISTD_H
29 #include <unistd.h>
30 #endif
31
32 #if defined (HAVE_SYS_PARAM_H)
33 #include <sys/param.h>
34 #endif
35
36 #include <sys/stat.h>           /* for S_IFLNK */
37
38 /* First char after start of absolute filename. */
39 #define ABS_START(name) (name + ABS_LENGTH (name))
40
41 #define ABS_LENGTH(name) (IS_DIRECTORY_SEP (*name) ? 1 : 0)
42 #define system_readlink readlink
43
44
45 #if !defined (HAVE_GETCWD) && defined (HAVE_GETWD)
46 #undef getcwd
47 #define getcwd(buffer, len) getwd (buffer)
48 #endif
49
50 #ifndef PATH_MAX
51 # if defined (_POSIX_PATH_MAX)
52 #  define PATH_MAX _POSIX_PATH_MAX
53 # elif defined (MAXPATHLEN)
54 #  define PATH_MAX MAXPATHLEN
55 # else
56 #  define PATH_MAX 1024
57 # endif
58 #endif
59
60 #define MAX_READLINKS 32
61
62 char *xrealpath(const char *path, char *restrict resolved_path);
63 char *xrealpath(const char *path, char *restrict resolved_path)
64 {
65         char copy_path[PATH_MAX];
66         char *new_path = resolved_path;
67         char *max_path;
68 #if defined (S_IFLNK)
69         int readlinks = 0;
70         char link_path[PATH_MAX];
71         int n;
72         int abslen = ABS_LENGTH(path);
73 #endif
74
75         /* Make a copy of the source path since we may need to modify it. */
76         strncpy(copy_path, path, sizeof(copy_path)-1);
77         copy_path[sizeof(copy_path)-1]='\0';
78         path = copy_path;
79         max_path = copy_path + PATH_MAX - 2;
80
81         /* If it's a relative pathname use getcwd for starters. */
82         if (abslen == 0) {
83                 getcwd(new_path, PATH_MAX - 1);
84                 new_path += strlen(new_path);
85                 if (!IS_DIRECTORY_SEP(new_path[-1]))
86                         *new_path++ = DIRECTORY_SEP;
87         } else {
88                 /* Copy first directory sep. */
89                 strncpy(new_path, path, abslen);
90                 new_path += abslen;
91                 path += abslen;
92         }
93
94         /* Expand each slash-separated pathname component. */
95         while (*path != '\0') {
96                 /* Ignore stray "/". */
97                 if (IS_DIRECTORY_SEP(*path)) {
98                         path++;
99                         continue;
100                 }
101
102                 if (*path == '.') {
103                         /* Ignore ".". */
104                         if (path[1] == '\0' || IS_DIRECTORY_SEP(path[1])) {
105                                 path++;
106                                 continue;
107                         }
108
109                         /* Handle ".." */
110                         if (path[1] == '.' &&
111                             (path[2] == '\0' || IS_DIRECTORY_SEP(path[2]))) {
112                                 path += 2;
113
114                                 /* Ignore ".." at root. */
115                                 if (new_path == ABS_START(resolved_path))
116                                         continue;
117
118                                 /* Handle ".." by backing up. */
119                                 --new_path;
120                                 while (!IS_DIRECTORY_SEP(new_path[-1]))
121                                         --new_path;
122                                 continue;
123                         }
124                 }
125
126                 /* Safely copy the next pathname component. */
127                 while (*path != '\0' && !IS_DIRECTORY_SEP(*path)) {
128                         if (path > max_path) {
129                                 errno = ENAMETOOLONG;
130                                 return NULL;
131                         }
132                         *new_path++ = *path++;
133                 }
134
135 #if defined (S_IFLNK)
136                 /* See if latest pathname component is a symlink. */
137                 *new_path = '\0';
138                 n = system_readlink(resolved_path, link_path, PATH_MAX - 1);
139
140                 if (n < 0) {
141                         /* EINVAL means the file exists but isn't a symlink. */
142                         if (errno != EINVAL)
143                                 return NULL;
144                 } else {
145                         /* Protect against infinite loops. */
146                         if (readlinks++ > MAX_READLINKS) {
147                                 errno = ELOOP;
148                                 return NULL;
149                         }
150
151                         /* Note: readlink doesn't add the null byte. */
152                         link_path[n] = '\0';
153
154                         if (ABS_LENGTH(link_path) > 0)
155                                 /* Start over for an absolute symlink. */
156                                 new_path =
157                                     resolved_path + ABS_LENGTH(link_path) - 1;
158                         else
159                                 /* Otherwise back up over this component. */
160                                 for (--new_path; !IS_DIRECTORY_SEP(*new_path);
161                                      --new_path)
162                                         assert(new_path > resolved_path);
163
164                         /* Safe sex check. */
165                         if (strlen(path) + n >= PATH_MAX) {
166                                 errno = ENAMETOOLONG;
167                                 return NULL;
168                         }
169
170                         /* Insert symlink contents into path. */
171                         strcat(link_path, path);
172                         strcpy(copy_path, link_path);
173                         path = copy_path;
174                 }
175 #endif                          /* S_IFLNK */
176                 *new_path++ = DIRECTORY_SEP;
177         }
178
179         /* Delete trailing slash but don't whomp a lone slash. */
180         if (new_path != ABS_START(resolved_path)
181             && IS_DIRECTORY_SEP(new_path[-1]))
182                 new_path--;
183
184         /* Make sure it's null terminated. */
185         *new_path = '\0';
186
187         return resolved_path;
188 }