2 * Copyright 1993-2002 Christopher Seiwald and Perforce Software, Inc.
4 * This file is part of Jam - see jam.c for Copyright information.
7 * pathsys.c - manipulate file names on UNIX, NT, OS2, AmigaOS
11 * path_parse() - split a file name into dir/base/suffix/member
12 * path_build() - build a filename given dir/base/suffix/member
13 * path_parent() - make a PATHNAME point to its parent dir
15 * File_parse() and path_build() just manipuate a string and a structure;
16 * they do not make system calls.
18 * 04/08/94 (seiwald) - Coherent/386 support added.
19 * 12/26/93 (seiwald) - handle dir/.suffix properly in path_build()
20 * 12/19/94 (mikem) - solaris string table insanity support
21 * 12/21/94 (wingerd) Use backslashes for pathnames - the NT way.
22 * 02/14/95 (seiwald) - parse and build /xxx properly
23 * 02/23/95 (wingerd) Compilers on NT can handle "/" in pathnames, so we
24 * should expect hdr searches to come up with strings
25 * like "thing/thing.h". So we need to test for "/" as
26 * well as "\" when parsing pathnames.
27 * 03/16/95 (seiwald) - fixed accursed typo on line 69.
28 * 05/03/96 (seiwald) - split from filent.c, fileunix.c
29 * 12/20/96 (seiwald) - when looking for the rightmost . in a file name,
30 * don't include the archive member name.
31 * 01/13/01 (seiwald) - turn off \ handling on UNIX, on by accident
32 * 11/04/02 (seiwald) - const-ing for string literals
41 * path_parse() - split a file name into dir/base/suffix/member
43 void path_parse (const char *file
, PATHNAME
*f
) {
47 memset((char *)f
, 0, sizeof(*f
));
48 /* look for <grist> */
49 if (file
[0] == '<' && (p
= strchr(file
, '>'))) {
50 f
->f_grist
.ptr
= file
;
51 f
->f_grist
.len
= p
-file
;
55 p
= strrchr(file
, '/');
56 #if PATH_DELIM == '\\'
57 /* on NT, look for dir\ as well */
59 char *p1
= strrchr(file
, '\\');
65 f
->f_dir
.len
= p
-file
;
66 /* special case for / - dirname is /, not "" */
67 if (!f
->f_dir
.len
) f
->f_dir
.len
= 1;
68 #if PATH_DELIM == '\\'
69 /* special case for D:/ - dirname is D:/, not "D:" */
70 if (f
->f_dir
.len
== 2 && file
[1] == ':') f
->f_dir
.len
= 3;
74 end
= file
+strlen(file
);
75 /* look for (member) */
76 if ((p
= strchr(file
, '(')) && end
[-1] == ')') {
77 f
->f_member
.ptr
= p
+1;
78 f
->f_member
.len
= end
-p
-2;
81 /* look for .suffix */
82 /* this would be memrchr() */
85 while ((q
= (char *)memchr(q
, '.', end
-q
))) p
= q
++;
88 f
->f_suffix
.len
= end
-p
;
93 f
->f_base
.len
= end
-file
;
98 * path_build() - build a filename given dir/base/suffix/member
100 void path_build (PATHNAME
*f
, char *file
, int binding
) {
101 /* start with the grist; if the current grist isn't surrounded by <>'s, add them */
102 if (f
->f_grist
.len
) {
103 if (f
->f_grist
.ptr
[0] != '<') *file
++ = '<';
104 memcpy(file
, f
->f_grist
.ptr
, f
->f_grist
.len
);
105 file
+= f
->f_grist
.len
;
106 if (file
[-1] != '>') *file
++ = '>';
108 /* don't prepend root if it's "." or directory is rooted */
109 #if PATH_DELIM == '/'
110 if (f
->f_root
.len
&& !(f
->f_root
.len
== 1 && f
->f_root
.ptr
[0] == '.') &&
111 !(f
->f_dir
.len
&& f
->f_dir
.ptr
[0] == '/'))
113 if (f
->f_root
.len
&& !(f
->f_root
.len
== 1 && f
->f_root
.ptr
[0] == '.') &&
114 !(f
->f_dir
.len
&& f
->f_dir
.ptr
[0] == '/') &&
115 !(f
->f_dir
.len
&& f
->f_dir
.ptr
[0] == '\\') &&
116 !(f
->f_dir
.len
&& f
->f_dir
.ptr
[1] == ':'))
119 memcpy(file
, f
->f_root
.ptr
, f
->f_root
.len
);
120 file
+= f
->f_root
.len
;
121 *file
++ = PATH_DELIM
;
124 memcpy(file
, f
->f_dir
.ptr
, f
->f_dir
.len
);
125 file
+= f
->f_dir
.len
;
127 /* UNIX: Put / between dir and file */
128 /* NT: Put \ between dir and file */
129 if (f
->f_dir
.len
&& (f
->f_base
.len
|| f
->f_suffix
.len
)) {
130 /* UNIX: Special case for dir \ : don't add another \ */
131 /* NT: Special case for dir / : don't add another / */
132 #if PATH_DELIM == '\\'
133 if (!(f
->f_dir
.len
== 3 && f
->f_dir
.ptr
[1] == ':'))
135 if (!(f
->f_dir
.len
== 1 && f
->f_dir
.ptr
[0] == PATH_DELIM
)) *file
++ = PATH_DELIM
;
138 memcpy(file
, f
->f_base
.ptr
, f
->f_base
.len
);
139 file
+= f
->f_base
.len
;
141 if (f
->f_suffix
.len
) {
142 memcpy(file
, f
->f_suffix
.ptr
, f
->f_suffix
.len
);
143 file
+= f
->f_suffix
.len
;
145 if (f
->f_member
.len
) {
147 memcpy(file
, f
->f_member
.ptr
, f
->f_member
.len
);
148 file
+= f
->f_member
.len
;
156 * path_parent() - make a PATHNAME point to its parent dir
158 void path_parent (PATHNAME
*f
) {
159 /* just set everything else to nothing */
160 f
->f_base
.ptr
= f
->f_suffix
.ptr
= f
->f_member
.ptr
= "";
161 f
->f_base
.len
= f
->f_suffix
.len
= f
->f_member
.len
= 0;
166 * normalize_path() - normalize a path
168 * It doesn't really generate a unique representation of a path to an entry,
169 * but at least reduces the number of categories that represent the same
170 * entry. On error, or if the supplied buffer is too small, NULL is returned.
172 char *normalize_path (const char *path
, char *buffer
, size_t bufferSize
) {
174 static char _cwd
[PATH_MAX
];
175 static char *cwd
= 0;
176 static size_t cwdLen
= 0;
177 int pathLen
= path
?strlen(path
):0;
179 int resolveDotDot
= !0;
182 cwd
= getcwd(_cwd
, PATH_MAX
);
184 cwdLen
= strlen(cwd
);
187 if (cwdLen
+pathLen
+2 > bufferSize
) return 0;
189 if (pathLen
> 0 && path
[0] == PATH_DELIM
) {
190 // absolute path: ignore cwd
191 buffer
[0] = PATH_DELIM
;
197 // relative path: copy cwd into result
198 memcpy(buffer
, cwd
, cwdLen
+1);
201 // append path componentwise to the result, skipping "." and empty
202 // components, and chopping off a component per ".."
203 while (pathLen
> 0) {
205 char *separator
= strchr(path
, PATH_DELIM
);
206 const char *component
= path
;
207 int componentLen
= 0;
209 componentLen
= separator
-path
;
210 pathLen
-= componentLen
+1;
213 componentLen
= pathLen
;
214 path
+= componentLen
;
217 // handle found component
218 if (componentLen
> 0) {
219 if (componentLen
== 1 && component
[0] == '.') {
220 // component is ".": skip
221 } else if (resolveDotDot
&& componentLen
== 2 && component
[0] == '.' && component
[1] == '.') {
222 // component is "..": eat the last component of the result
223 char *lastSeparator
= strrchr(buffer
, PATH_DELIM
);
225 resultLen
= lastSeparator
-buffer
;
226 if (resultLen
== 0) {
227 // always leave at least the root
228 buffer
[0] = PATH_DELIM
;
231 buffer
[resultLen
] = '\0';
234 // normal component: append
235 if (resultLen
< 1 || buffer
[resultLen
-1] != PATH_DELIM
) buffer
[resultLen
++] = PATH_DELIM
;
236 memcpy(buffer
+resultLen
, component
, componentLen
);
237 resultLen
+= componentLen
;
238 buffer
[resultLen
] = '\0';
239 // After we found the first real path component, we don't
240 // resolve ".." anymore, as it could be a (sym)link, which
241 // could break the algorithm.