2 * Copyright 1993-2002 Christopher Seiwald and Perforce Software, Inc.
3 * This file is part of Jam - see jam.c for Copyright information.
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, version 3 of the License ONLY.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 * pathsys.c - manipulate file names on UNIX, NT, OS2, AmigaOS
22 * path_parse() - split a file name into dir/base/suffix/member
23 * path_build() - build a filename given dir/base/suffix/member
24 * path_parent() - make a PATHNAME point to its parent dir
26 * File_parse() and path_build() just manipuate a string and a structure;
27 * they do not make system calls.
36 * path_parse() - split a file name into dir/base/suffix/member
38 void path_parse (const char *file
, PATHNAME
*f
) {
40 memset(f
, 0, sizeof(*f
));
41 /* look for <grist> */
42 if (file
[0] == '<' && (p
= strchr(file
, '>')) != NULL
) {
43 f
->f_grist
.ptr
= file
;
44 f
->f_grist
.len
= p
-file
;
45 file
= p
+1; /* 'file' moved past grist */
48 p
= strrchr(file
, '/');
49 #if PATH_DELIM == '\\'
50 /* on NT, look for dir\ as well */
52 char *p1
= strrchr(file
, '\\');
53 p
= (p1
> p
? p1
: p
);
58 f
->f_dir
.len
= p
-file
;
59 /* special case for / - dirname is /, not "" */
60 if (f
->f_dir
.len
== 0) f
->f_dir
.len
= 1;
61 #if PATH_DELIM == '\\'
62 /* special case for D:/ - dirname is D:/, not "D:" */
63 if (f
->f_dir
.len
== 2 && file
[1] == ':') f
->f_dir
.len
= 3;
65 file
= p
+1; /* 'file' moved past dir */
67 end
= file
+strlen(file
);
70 /* look for (member) */
71 if (end
[-1] == ')' && (p
= strrchr(file
, '(')) != NULL
) {
72 f
->f_member
.ptr
= p
+1;
73 f
->f_member
.len
= end
-p
-2;
76 /* look for .suffix */
77 /* this would be memrchr() */
80 while ((q
= memchr(q
, '.', end
-q
))) p
= q
++;
83 f
->f_suffix
.len
= end
-p
;
89 f
->f_base
.len
= end
-file
;
94 * path_build() - build a filename given dir/base/suffix/member
96 void path_build (char *file
, const PATHNAME
*f
) {
97 /* start with the grist; if the current grist isn't surrounded by <>'s, add them */
98 if (f
->f_grist
.len
> 0) {
99 if (f
->f_grist
.ptr
[0] != '<') *file
++ = '<';
100 memmove(file
, f
->f_grist
.ptr
, f
->f_grist
.len
);
101 file
+= f
->f_grist
.len
;
102 if (file
[-1] != '>') *file
++ = '>';
104 /* don't prepend root if it's "." or directory is rooted */
105 if (f
->f_root
.len
> 0 && !(f
->f_root
.len
== 1 && f
->f_root
.ptr
[0] == '.') && !(f
->f_dir
.len
> 0 && f
->f_dir
.ptr
[0] == '/')
106 #if PATH_DELIM == '\\'
107 && !(f
->f_dir
.len
> 0 && f
->f_dir
.ptr
[0] == '\\') && !(f
->f_dir
.len
> 0 && f
->f_dir
.ptr
[1] == ':')
111 memmove(file
, f
->f_root
.ptr
, f
->f_root
.len
);
112 file
+= f
->f_root
.len
;
113 *file
++ = PATH_DELIM
;
115 if (f
->f_dir
.len
> 0) {
116 memmove(file
, f
->f_dir
.ptr
, f
->f_dir
.len
);
117 file
+= f
->f_dir
.len
;
119 /* UNIX: Put / between dir and file */
120 /* NT: Put \ between dir and file */
121 if (f
->f_dir
.len
> 0 && (f
->f_base
.len
> 0 || f
->f_suffix
.len
> 0)) {
122 /* UNIX: Special case for dir \ : don't add another \ */
123 /* NT: Special case for dir / : don't add another / */
124 #if PATH_DELIM == '\\'
125 if (!(f
->f_dir
.len
== 3 && f
->f_dir
.ptr
[1] == ':'))
127 if (!(f
->f_dir
.len
== 1 && f
->f_dir
.ptr
[0] == PATH_DELIM
)) *file
++ = PATH_DELIM
;
129 if (f
->f_base
.len
> 0) {
130 memmove(file
, f
->f_base
.ptr
, f
->f_base
.len
);
131 file
+= f
->f_base
.len
;
133 if (f
->f_suffix
.len
> 0) {
134 memmove(file
, f
->f_suffix
.ptr
, f
->f_suffix
.len
);
135 file
+= f
->f_suffix
.len
;
137 if (f
->f_member
.len
> 0) {
139 memmove(file
, f
->f_member
.ptr
, f
->f_member
.len
);
140 file
+= f
->f_member
.len
;
148 * path_parent() - make a PATHNAME point to its parent dir
150 void path_parent (PATHNAME
*f
) {
151 /* just set everything else to nothing */
152 f
->f_base
.ptr
= f
->f_suffix
.ptr
= f
->f_member
.ptr
= "";
153 f
->f_base
.len
= f
->f_suffix
.len
= f
->f_member
.len
= 0;
158 * normalize_path() - normalize a path
160 * It doesn't really generate a unique representation of a path to an entry,
161 * but at least reduces the number of categories that represent the same
162 * entry. On error, or if the supplied buffer is too small, NULL is returned.
164 char *normalize_path (const char *path
, char *buffer
, size_t buf_size
, const char *pwd
) {
165 #if PATH_DELIM == '\\'
166 /* stupid windoze; convert all idiotic backslashes to normal slashes */
167 static char w2upath
[PATH_MAX
];
170 static char cwd_buf
[PATH_MAX
];
172 static size_t cwd_len
= 0;
174 int ends_with_slash
= 0;
177 if ((cwd
= getcwd(cwd_buf
, PATH_MAX
)) == NULL
) return NULL
;
179 /*FIXME: possible overflow*/
180 snprintf(cwd_buf
, sizeof(cwd_buf
), "%s", pwd
);
183 cwd_len
= strlen(cwd
);
184 if (path
== NULL
) path
= "";
185 /* start reconstructing path */
186 #if PATH_DELIM == '\\'
187 snprintf(w2upath
, sizeof(w2upath
), "%s", path
);
188 for (char *p
= w2upath
; *p
; ++p
) if (*p
== '\\') *p
= '/';
190 /* check if this path is absolute */
191 /* windoze: check if we have disk letter here */
192 if (path
[0] && path
[1] == ':') {
193 /* copy disk letter */
194 if (buf_size
< 3) return NULL
;
196 *buffer
++ = (cwd
[0] && cwd
[1] == ':' ? cwd
[0] : path
[0]);
201 if (cwd
[0] && cwd
[1] == ':') { cwd
+= 2; cwd_len
-= 2; }
202 for (char *p
= cwd
; *p
; ++p
) if (*p
== '\\') *p
= '/';
204 /* check if this path is absolute */
205 if (path
[0] != '/') {
208 if (buf_size
< cwd_len
+1) return NULL
;
209 memmove(buffer
, cwd
, cwd_len
);
213 /* put slash in output; excess slashes will be eaten by the main loop */
214 if (buf_size
< 2) return NULL
;
215 buffer
[res_len
++] = '/';
217 ends_with_slash
= (path
[0] && path
[strlen(path
)-1] == '/');
221 /* skip leading slashes */
222 while (*path
&& path
[0] == '/') ++path
;
223 if (!path
[0]) break; /* no more */
224 if ((e
= strchr(path
, '/')) == NULL
) e
= path
+strlen(path
);
225 if (e
-path
== 1 && path
[0] == '.') {
226 /* this is unnecessary dot, skip it */
230 if (e
-path
== 2 && path
[0] == '.' && path
[1] == '.') {
231 /* dotdot; go one dir up if we can */
232 /* we can't go up if we are at root or if previous dir is "." or ".." */
233 if (res_len
> 0 && !(res_len
== 1 && buffer
[0] == '/')) {
234 char *pd
= buffer
+res_len
;
237 if (pd
[-1] == '/') { --pd
; --sz
; }
242 /* last char is '.' and we have more chars */
246 /* last two chars is ".." and we have more chars */
247 do_remove
= (pd
[-3] != '/');
252 if (res_len
> 0 && buffer
[res_len
-1] == '/') --res_len
; /* remove trailing slash if any */
253 while (res_len
> 0 && buffer
[res_len
-1] != '/') --res_len
; /* remove last dir */
259 /* append this dir */
260 if (res_len
> 0 && buffer
[res_len
-1] != '/') {
261 if (res_len
+2 > buf_size
) return NULL
;
262 buffer
[res_len
++] = '/';
264 if (res_len
+(e
-path
)+1 > buf_size
) return NULL
;
265 memmove(buffer
+res_len
, path
, (e
-path
));
269 /* add trailing slash if we need it */
270 if (ends_with_slash
&& res_len
> 0 && buffer
[res_len
-1] != '/') {
271 if (res_len
+2 > buf_size
) return NULL
;
272 buffer
[res_len
++] = '/';
275 if (res_len
+1 > buf_size
) return NULL
;