option.c: fixed warnings
[k8jam.git] / src / pathsys.c
blob02119a207f0e96805b229b1ccc3e41f68a8eb867
1 /*
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
20 * External routines:
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.
29 #include <limits.h>
31 #include "jam.h"
32 #include "pathsys.h"
36 * path_parse() - split a file name into dir/base/suffix/member
38 void path_parse (const char *file, PATHNAME *f) {
39 const char *p, *end;
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 */
47 /* look for dir/ */
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);
55 #endif
56 if (p != NULL) {
57 f->f_dir.ptr = file;
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;
64 #endif
65 file = p+1; /* 'file' moved past dir */
67 end = file+strlen(file);
68 if (end > file) {
69 const char *q;
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;
74 end = p;
76 /* look for .suffix */
77 /* this would be memrchr() */
78 p = NULL;
79 q = file;
80 while ((q = memchr(q, '.', end-q))) p = q++;
81 if (p != NULL) {
82 f->f_suffix.ptr = p;
83 f->f_suffix.len = end-p;
84 end = p;
87 /* leaves base */
88 f->f_base.ptr = file;
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] == ':')
108 #endif
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] == ':'))
126 #endif
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) {
138 *file++ = '(';
139 memmove(file, f->f_member.ptr, f->f_member.len);
140 file += f->f_member.len;
141 *file++ = ')';
143 *file = 0;
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];
168 #endif
169 char *res = buffer;
170 static char cwd_buf[PATH_MAX];
171 char *cwd = NULL;
172 static size_t cwd_len = 0;
173 int res_len = 0;
174 int ends_with_slash = 0;
175 /* init cwd */
176 if (pwd == NULL) {
177 if ((cwd = getcwd(cwd_buf, PATH_MAX)) == NULL) return NULL;
178 } else {
179 /*FIXME: possible overflow*/
180 snprintf(cwd_buf, sizeof(cwd_buf), "%s", pwd);
181 cwd = cwd_buf;
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 = '/';
189 path = w2upath;
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;
195 buf_size -= 2;
196 *buffer++ = (cwd[0] && cwd[1] == ':' ? cwd[0] : path[0]);
197 *buffer++ = ':';
198 path += 2;
200 /* convert cwd */
201 if (cwd[0] && cwd[1] == ':') { cwd += 2; cwd_len -= 2; }
202 for (char *p = cwd; *p; ++p) if (*p == '\\') *p = '/';
203 #endif
204 /* check if this path is absolute */
205 if (path[0] != '/') {
206 /* prepend pwd */
207 if (cwd_len > 0) {
208 if (buf_size < cwd_len+1) return NULL;
209 memmove(buffer, cwd, cwd_len);
210 res_len += cwd_len;
212 } else {
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] == '/');
218 /* main loop */
219 while (*path) {
220 const char *e;
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 */
227 path = e;
228 continue;
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;
235 int sz = res_len;
236 int do_remove = 0;
237 if (pd[-1] == '/') { --pd; --sz; }
238 if (sz > 0) {
239 if (pd[-1] != '.') {
240 do_remove = 1;
241 } else if (sz > 1) {
242 /* last char is '.' and we have more chars */
243 if (pd[-2] != '.') {
244 do_remove = 1;
245 } else if (sz > 2) {
246 /* last two chars is ".." and we have more chars */
247 do_remove = (pd[-3] != '/');
251 if (do_remove) {
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 */
254 path = e;
255 continue;
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));
266 res_len += e-path;
267 path = e;
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++] = '/';
274 /* add terminator */
275 if (res_len+1 > buf_size) return NULL;
276 buffer[res_len] = 0;
277 return res;