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, either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 * pathsys.c - manipulate file names on UNIX, NT, OS2, AmigaOS
23 * path_parse() - split a file name into dir/base/suffix/member
24 * path_build() - build a filename given dir/base/suffix/member
25 * path_parent() - make a PATHNAME point to its parent dir
27 * File_parse() and path_build() just manipuate a string and a structure;
28 * they do not make system calls.
37 * path_parse() - split a file name into dir/base/suffix/member
39 void path_parse (const char *file
, PATHNAME
*f
) {
41 memset(f
, 0, sizeof(*f
));
42 /* look for <grist> */
43 if (file
[0] == '<' && (p
= strchr(file
, '>')) != NULL
) {
44 f
->f_grist
.ptr
= file
;
45 f
->f_grist
.len
= p
-file
;
46 file
= p
+1; /* 'file' moved past grist */
49 p
= strrchr(file
, '/');
50 #if PATH_DELIM == '\\'
51 /* on NT, look for dir\ as well */
53 char *p1
= strrchr(file
, '\\');
54 p
= (p1
> p
? p1
: p
);
59 f
->f_dir
.len
= p
-file
;
60 /* special case for / - dirname is /, not "" */
61 if (f
->f_dir
.len
== 0) f
->f_dir
.len
= 1;
62 #if PATH_DELIM == '\\'
63 /* special case for D:/ - dirname is D:/, not "D:" */
64 if (f
->f_dir
.len
== 2 && file
[1] == ':') f
->f_dir
.len
= 3;
66 file
= p
+1; /* 'file' moved past dir */
68 end
= file
+strlen(file
);
71 /* look for (member) */
72 if (end
[-1] == ')' && (p
= strrchr(file
, '(')) != NULL
) {
73 f
->f_member
.ptr
= p
+1;
74 f
->f_member
.len
= end
-p
-2;
77 /* look for .suffix */
78 /* this would be memrchr() */
81 while ((q
= memchr(q
, '.', end
-q
))) p
= q
++;
84 f
->f_suffix
.len
= end
-p
;
90 f
->f_base
.len
= end
-file
;
95 * path_build() - build a filename given dir/base/suffix/member
97 void path_build (char *file
, const PATHNAME
*f
) {
98 /* start with the grist; if the current grist isn't surrounded by <>'s, add them */
99 if (f
->f_grist
.len
> 0) {
100 if (f
->f_grist
.ptr
[0] != '<') *file
++ = '<';
101 memmove(file
, f
->f_grist
.ptr
, f
->f_grist
.len
);
102 file
+= f
->f_grist
.len
;
103 if (file
[-1] != '>') *file
++ = '>';
105 /* don't prepend root if it's "." or directory is rooted */
106 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] == '/')
107 #if PATH_DELIM == '\\'
108 && !(f
->f_dir
.len
> 0 && f
->f_dir
.ptr
[0] == '\\') && !(f
->f_dir
.len
> 0 && f
->f_dir
.ptr
[1] == ':')
112 memmove(file
, f
->f_root
.ptr
, f
->f_root
.len
);
113 file
+= f
->f_root
.len
;
114 *file
++ = PATH_DELIM
;
116 if (f
->f_dir
.len
> 0) {
117 memmove(file
, f
->f_dir
.ptr
, f
->f_dir
.len
);
118 file
+= f
->f_dir
.len
;
120 /* UNIX: Put / between dir and file */
121 /* NT: Put \ between dir and file */
122 if (f
->f_dir
.len
> 0 && (f
->f_base
.len
> 0 || f
->f_suffix
.len
> 0)) {
123 /* UNIX: Special case for dir \ : don't add another \ */
124 /* NT: Special case for dir / : don't add another / */
125 #if PATH_DELIM == '\\'
126 if (!(f
->f_dir
.len
== 3 && f
->f_dir
.ptr
[1] == ':'))
128 if (!(f
->f_dir
.len
== 1 && f
->f_dir
.ptr
[0] == PATH_DELIM
)) *file
++ = PATH_DELIM
;
130 if (f
->f_base
.len
> 0) {
131 memmove(file
, f
->f_base
.ptr
, f
->f_base
.len
);
132 file
+= f
->f_base
.len
;
134 if (f
->f_suffix
.len
> 0) {
135 memmove(file
, f
->f_suffix
.ptr
, f
->f_suffix
.len
);
136 file
+= f
->f_suffix
.len
;
138 if (f
->f_member
.len
> 0) {
140 memmove(file
, f
->f_member
.ptr
, f
->f_member
.len
);
141 file
+= f
->f_member
.len
;
149 * path_parent() - make a PATHNAME point to its parent dir
151 void path_parent (PATHNAME
*f
) {
152 /* just set everything else to nothing */
153 f
->f_base
.ptr
= f
->f_suffix
.ptr
= f
->f_member
.ptr
= "";
154 f
->f_base
.len
= f
->f_suffix
.len
= f
->f_member
.len
= 0;
159 * normalize_path() - normalize a path
161 * It doesn't really generate a unique representation of a path to an entry,
162 * but at least reduces the number of categories that represent the same
163 * entry. On error, or if the supplied buffer is too small, NULL is returned.
165 char *normalize_path (const char *path
, char *buffer
, size_t buf_size
, const char *pwd
) {
166 #if PATH_DELIM == '\\'
167 /* stupid windoze; convert all idiotic backslashes to normal slashes */
168 static char w2upath
[PATH_MAX
];
171 static char cwd_buf
[PATH_MAX
];
173 static size_t cwd_len
= 0;
175 int ends_with_slash
= 0;
178 if ((cwd
= getcwd(cwd_buf
, PATH_MAX
)) == NULL
) return NULL
;
180 /*FIXME: possible overflow*/
181 snprintf(cwd_buf
, sizeof(cwd_buf
), "%s", pwd
);
184 cwd_len
= strlen(cwd
);
185 if (path
== NULL
) path
= "";
186 /* start reconstructing path */
187 #if PATH_DELIM == '\\'
188 snprintf(w2upath
, sizeof(w2upath
), "%s", path
);
189 for (char *p
= w2upath
; *p
; ++p
) if (*p
== '\\') *p
= '/';
191 /* check if this path is absolute */
192 /* windoze: check if we have disk letter here */
193 if (path
[0] && path
[1] == ':') {
194 /* copy disk letter */
195 if (buf_size
< 3) return NULL
;
197 *buffer
++ = (cwd
[0] && cwd
[1] == ':' ? cwd
[0] : path
[0]);
202 if (cwd
[0] && cwd
[1] == ':') { cwd
+= 2; cwd_len
-= 2; }
203 for (char *p
= cwd
; *p
; ++p
) if (*p
== '\\') *p
= '/';
205 /* check if this path is absolute */
206 if (path
[0] != '/') {
209 if (buf_size
< cwd_len
+1) return NULL
;
210 memmove(buffer
, cwd
, cwd_len
);
214 /* put slash in output; excess slashes will be eaten by the main loop */
215 if (buf_size
< 2) return NULL
;
216 buffer
[res_len
++] = '/';
218 ends_with_slash
= (path
[0] && path
[strlen(path
)-1] == '/');
222 /* skip leading slashes */
223 while (*path
&& path
[0] == '/') ++path
;
224 if (!path
[0]) break; /* no more */
225 if ((e
= strchr(path
, '/')) == NULL
) e
= path
+strlen(path
);
226 if (e
-path
== 1 && path
[0] == '.') {
227 /* this is unnecessary dot, skip it */
231 if (e
-path
== 2 && path
[0] == '.' && path
[1] == '.') {
232 /* dotdot; go one dir up if we can */
233 /* we can't go up if we are at root or if previous dir is "." or ".." */
234 if (res_len
> 0 && !(res_len
== 1 && buffer
[0] == '/')) {
235 char *pd
= buffer
+res_len
;
238 if (pd
[-1] == '/') { --pd
; --sz
; }
243 /* last char is '.' and we have more chars */
247 /* last two chars is ".." and we have more chars */
248 do_remove
= (pd
[-3] != '/');
253 if (res_len
> 0 && buffer
[res_len
-1] == '/') --res_len
; /* remove trailing slash if any */
254 while (res_len
> 0 && buffer
[res_len
-1] != '/') --res_len
; /* remove last dir */
260 /* append this dir */
261 if (res_len
> 0 && buffer
[res_len
-1] != '/') {
262 if (res_len
+2 > buf_size
) return NULL
;
263 buffer
[res_len
++] = '/';
265 if (res_len
+(e
-path
)+1 > buf_size
) return NULL
;
266 memmove(buffer
+res_len
, path
, (e
-path
));
270 /* add trailing slash if we need it */
271 if (ends_with_slash
&& res_len
> 0 && buffer
[res_len
-1] != '/') {
272 if (res_len
+2 > buf_size
) return NULL
;
273 buffer
[res_len
++] = '/';
276 if (res_len
+1 > buf_size
) return NULL
;