]> git.ipfire.org Git - thirdparty/bash.git/blame - lib/sh/pathcanon.c
bash-5.1 distribution sources and documentation
[thirdparty/bash.git] / lib / sh / pathcanon.c
CommitLineData
3185942a 1/* pathcanon.c -- canonicalize and manipulate pathnames. */
28ef6c31
JA
2
3/* Copyright (C) 2000 Free Software Foundation, Inc.
4
5 This file is part of GNU Bash, the Bourne Again SHell.
6
3185942a
JA
7 Bash is free software: you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation, either version 3 of the License, or
10 (at your option) any later version.
28ef6c31 11
3185942a
JA
12 Bash is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
28ef6c31 16
3185942a
JA
17 You should have received a copy of the GNU General Public License
18 along with Bash. If not, see <http://www.gnu.org/licenses/>.
19*/
28ef6c31 20
f73dda09 21#include <config.h>
28ef6c31 22
f73dda09 23#include <bashtypes.h>
ac50fbac 24#if defined (HAVE_SYS_PARAM_H)
28ef6c31
JA
25# include <sys/param.h>
26#endif
f73dda09 27#include <posixstat.h>
28ef6c31
JA
28
29#if defined (HAVE_UNISTD_H)
30# include <unistd.h>
31#endif
32
f73dda09
JA
33#include <filecntl.h>
34#include <bashansi.h>
28ef6c31 35#include <stdio.h>
f73dda09 36#include <chartypes.h>
b80f6443 37#include <errno.h>
28ef6c31
JA
38
39#include "shell.h"
40
b80f6443
JA
41#if !defined (errno)
42extern int errno;
43#endif
44
7117c2d2
JA
45#if defined (__CYGWIN__)
46#include <sys/cygwin.h>
47
48static int
49_is_cygdrive (path)
50 char *path;
51{
52 static char user[MAXPATHLEN];
53 static char system[MAXPATHLEN];
54 static int first_time = 1;
55
56 /* If the path is the first part of a network path, treat it as
57 existing. */
58 if (path[0] == '/' && path[1] == '/' && !strchr (path + 2, '/'))
59 return 1;
60 /* Otherwise check for /cygdrive prefix. */
61 if (first_time)
62 {
63 char user_flags[MAXPATHLEN];
64 char system_flags[MAXPATHLEN];
65 /* Get the cygdrive info */
66 cygwin_internal (CW_GET_CYGDRIVE_INFO, user, system, user_flags, system_flags);
67 first_time = 0;
68 }
69 return !strcasecmp (path, user) || !strcasecmp (path, system);
70}
71#endif /* __CYGWIN__ */
72
28ef6c31
JA
73/* Return 1 if PATH corresponds to a directory. A function for debugging. */
74static int
75_path_isdir (path)
76 char *path;
77{
95732b49 78 int l;
28ef6c31
JA
79 struct stat sb;
80
b80f6443 81 /* This should leave errno set to the correct value. */
95732b49 82 errno = 0;
28ef6c31 83 l = stat (path, &sb) == 0 && S_ISDIR (sb.st_mode);
7117c2d2
JA
84#if defined (__CYGWIN__)
85 if (l == 0)
86 l = _is_cygdrive (path);
87#endif
28ef6c31
JA
88 return l;
89}
90
91/* Canonicalize PATH, and return a new path. The new path differs from PATH
92 in that:
8868edaf 93 Multiple `/'s are collapsed to a single `/'.
28ef6c31
JA
94 Leading `./'s and trailing `/.'s are removed.
95 Trailing `/'s are removed.
96 Non-leading `../'s and trailing `..'s are handled by removing
97 portions of the path. */
98
99/* Look for ROOTEDPATH, PATHSEP, DIRSEP, and ISDIRSEP in ../../general.h */
100
101#define DOUBLE_SLASH(p) ((p[0] == '/') && (p[1] == '/') && p[2] != '/')
102
103char *
104sh_canonpath (path, flags)
105 char *path;
106 int flags;
107{
108 char stub_char;
109 char *result, *p, *q, *base, *dotdot;
110 int rooted, double_slash_path;
111
112 /* The result cannot be larger than the input PATH. */
113 result = (flags & PATH_NOALLOC) ? path : savestring (path);
114
115 /* POSIX.2 says to leave a leading `//' alone. On cygwin, we skip over any
116 leading `x:' (dos drive name). */
117 if (rooted = ROOTEDPATH(path))
118 {
119 stub_char = DIRSEP;
120#if defined (__CYGWIN__)
f73dda09 121 base = (ISALPHA((unsigned char)result[0]) && result[1] == ':') ? result + 3 : result + 1;
28ef6c31
JA
122#else
123 base = result + 1;
124#endif
125 double_slash_path = DOUBLE_SLASH (path);
126 base += double_slash_path;
127 }
128 else
129 {
130 stub_char = '.';
131#if defined (__CYGWIN__)
f73dda09 132 base = (ISALPHA((unsigned char)result[0]) && result[1] == ':') ? result + 2 : result;
28ef6c31
JA
133#else
134 base = result;
135#endif
f73dda09 136 double_slash_path = 0;
28ef6c31
JA
137 }
138
139 /*
140 * invariants:
141 * base points to the portion of the path we want to modify
142 * p points at beginning of path element we're considering.
143 * q points just past the last path element we wrote (no slash).
144 * dotdot points just past the point where .. cannot backtrack
145 * any further (no slash).
146 */
147 p = q = dotdot = base;
148
149 while (*p)
150 {
151 if (ISDIRSEP(p[0])) /* null element */
152 p++;
153 else if(p[0] == '.' && PATHSEP(p[1])) /* . and ./ */
154 p += 1; /* don't count the separator in case it is nul */
155 else if (p[0] == '.' && p[1] == '.' && PATHSEP(p[2])) /* .. and ../ */
156 {
157 p += 2; /* skip `..' */
158 if (q > dotdot) /* can backtrack */
159 {
160 if (flags & PATH_CHECKDOTDOT)
161 {
162 char c;
163
164 /* Make sure what we have so far corresponds to a valid
165 path before we chop some of it off. */
166 c = *q;
167 *q = '\0';
168 if (_path_isdir (result) == 0)
169 {
170 if ((flags & PATH_NOALLOC) == 0)
171 free (result);
172 return ((char *)NULL);
173 }
174 *q = c;
175 }
176
177 while (--q > dotdot && ISDIRSEP(*q) == 0)
178 ;
179 }
180 else if (rooted == 0)
181 {
182 /* /.. is / but ./../ is .. */
183 if (q != base)
184 *q++ = DIRSEP;
185 *q++ = '.';
186 *q++ = '.';
187 dotdot = q;
188 }
189 }
190 else /* real path element */
191 {
192 /* add separator if not at start of work portion of result */
193 if (q != base)
194 *q++ = DIRSEP;
195 while (*p && (ISDIRSEP(*p) == 0))
196 *q++ = *p++;
197 /* Check here for a valid directory with _path_isdir. */
198 if (flags & PATH_CHECKEXISTS)
199 {
200 char c;
201
202 /* Make sure what we have so far corresponds to a valid
203 path before we chop some of it off. */
204 c = *q;
205 *q = '\0';
206 if (_path_isdir (result) == 0)
207 {
208 if ((flags & PATH_NOALLOC) == 0)
209 free (result);
210 return ((char *)NULL);
211 }
212 *q = c;
213 }
214 }
215 }
216
217 /* Empty string is really ``.'' or `/', depending on what we started with. */
218 if (q == result)
219 *q++ = stub_char;
220 *q = '\0';
221
222 /* If the result starts with `//', but the original path does not, we
223 can turn the // into /. Because of how we set `base', this should never
224 be true, but it's a sanity check. */
225 if (DOUBLE_SLASH(result) && double_slash_path == 0)
226 {
227 if (result[2] == '\0') /* short-circuit for bare `//' */
228 result[1] = '\0';
229 else
d233b485 230 memmove (result, result + 1, strlen (result + 1) + 1);
28ef6c31
JA
231 }
232
233 return (result);
234}