1/*
2 * Copyright 1993-2002 Christopher Seiwald and Perforce Software, Inc.
3 *
4 * This file is part of Jam - see jam.c for Copyright information.
5 */
6
7/*
8 * pathunix.c - manipulate file names on UNIX, NT, OS2, AmigaOS
9 *
10 * External routines:
11 *
12 *	path_parse() - split a file name into dir/base/suffix/member
13 *	path_build() - build a filename given dir/base/suffix/member
14 *	path_parent() - make a PATHNAME point to its parent dir
15 *
16 * File_parse() and path_build() just manipuate a string and a structure;
17 * they do not make system calls.
18 *
19 * 04/08/94 (seiwald) - Coherent/386 support added.
20 * 12/26/93 (seiwald) - handle dir/.suffix properly in path_build()
21 * 12/19/94 (mikem) - solaris string table insanity support
22 * 12/21/94 (wingerd) Use backslashes for pathnames - the NT way.
23 * 02/14/95 (seiwald) - parse and build /xxx properly
24 * 02/23/95 (wingerd) Compilers on NT can handle "/" in pathnames, so we
25 *                    should expect hdr searches to come up with strings
26 *                    like "thing/thing.h". So we need to test for "/" as
27 *                    well as "\" when parsing pathnames.
28 * 03/16/95 (seiwald) - fixed accursed typo on line 69.
29 * 05/03/96 (seiwald) - split from filent.c, fileunix.c
30 * 12/20/96 (seiwald) - when looking for the rightmost . in a file name,
31 *		      don't include the archive member name.
32 * 01/13/01 (seiwald) - turn off \ handling on UNIX, on by accident
33 * 11/04/02 (seiwald) - const-ing for string literals
34 */
35
36# include "jam.h"
37# include "pathsys.h"
38
39# ifdef USE_PATHUNIX
40
41# if defined( unix )
42# include <unistd.h>
43# endif
44
45/*
46 * path_parse() - split a file name into dir/base/suffix/member
47 */
48
49void
50path_parse(
51	const char *file,
52	PATHNAME *f )
53{
54	const char *p, *q;
55	const char *end;
56
57	memset( (char *)f, 0, sizeof( *f ) );
58
59	/* Look for <grist> */
60
61	if( file[0] == '<' && ( p = strchr( file, '>' ) ) )
62	{
63	    f->f_grist.ptr = file;
64	    f->f_grist.len = p - file;
65	    file = p + 1;
66	}
67
68	/* Look for dir/ */
69
70	p = strrchr( file, '/' );
71
72# if PATH_DELIM == '\\'
73	/* On NT, look for dir\ as well */
74	{
75	    char *p1 = strrchr( file, '\\' );
76	    p = p1 > p ? p1 : p;
77	}
78# endif
79
80	if( p )
81	{
82	    f->f_dir.ptr = file;
83	    f->f_dir.len = p - file;
84
85	    /* Special case for / - dirname is /, not "" */
86
87	    if( !f->f_dir.len )
88		f->f_dir.len = 1;
89
90# if PATH_DELIM == '\\'
91	    /* Special case for D:/ - dirname is D:/, not "D:" */
92
93	    if( f->f_dir.len == 2 && file[1] == ':' )
94		f->f_dir.len = 3;
95# endif
96
97	    file = p + 1;
98	}
99
100	end = file + strlen( file );
101
102	/* Look for (member) */
103
104	if( ( p = strchr( file, '(' ) ) && end[-1] == ')' )
105	{
106	    f->f_member.ptr = p + 1;
107	    f->f_member.len = end - p - 2;
108	    end = p;
109	}
110
111	/* Look for .suffix */
112	/* This would be memrchr() */
113
114	p = 0;
115	q = file;
116
117	while( q = (char *)memchr( q, '.', end - q ) )
118	    p = q++;
119
120	if( p )
121	{
122	    f->f_suffix.ptr = p;
123	    f->f_suffix.len = end - p;
124	    end = p;
125	}
126
127	/* Leaves base */
128
129	f->f_base.ptr = file;
130	f->f_base.len = end - file;
131}
132
133/*
134 * path_build() - build a filename given dir/base/suffix/member
135 */
136
137void
138path_build(
139	PATHNAME *f,
140	char	*file,
141	int	binding )
142{
143	/* Start with the grist.  If the current grist isn't */
144	/* surrounded by <>'s, add them. */
145
146	if( f->f_grist.len )
147	{
148	    if( f->f_grist.ptr[0] != '<' ) *file++ = '<';
149	    memcpy( file, f->f_grist.ptr, f->f_grist.len );
150	    file += f->f_grist.len;
151	    if( file[-1] != '>' ) *file++ = '>';
152	}
153
154	/* Don't prepend root if it's . or directory is rooted */
155
156# if PATH_DELIM == '/'
157
158	if( f->f_root.len
159	    && !( f->f_root.len == 1 && f->f_root.ptr[0] == '.' )
160	    && !( f->f_dir.len && f->f_dir.ptr[0] == '/' ) )
161
162# else /* unix */
163
164	if( f->f_root.len
165	    && !( f->f_root.len == 1 && f->f_root.ptr[0] == '.' )
166	    && !( f->f_dir.len && f->f_dir.ptr[0] == '/' )
167	    && !( f->f_dir.len && f->f_dir.ptr[0] == '\\' )
168	    && !( f->f_dir.len && f->f_dir.ptr[1] == ':' ) )
169
170# endif /* unix */
171
172	{
173	    memcpy( file, f->f_root.ptr, f->f_root.len );
174	    file += f->f_root.len;
175	    *file++ = PATH_DELIM;
176	}
177
178	if( f->f_dir.len )
179	{
180	    memcpy( file, f->f_dir.ptr, f->f_dir.len );
181	    file += f->f_dir.len;
182	}
183
184	/* UNIX: Put / between dir and file */
185	/* NT:   Put \ between dir and file */
186
187	if( f->f_dir.len && ( f->f_base.len || f->f_suffix.len ) )
188	{
189	    /* UNIX: Special case for dir \ : don't add another \ */
190	    /* NT:   Special case for dir / : don't add another / */
191
192# if PATH_DELIM == '\\'
193	    if( !( f->f_dir.len == 3 && f->f_dir.ptr[1] == ':' ) )
194# endif
195		if( !( f->f_dir.len == 1 && f->f_dir.ptr[0] == PATH_DELIM ) )
196		    *file++ = PATH_DELIM;
197	}
198
199	if( f->f_base.len )
200	{
201	    memcpy( file, f->f_base.ptr, f->f_base.len );
202	    file += f->f_base.len;
203	}
204
205	if( f->f_suffix.len )
206	{
207	    memcpy( file, f->f_suffix.ptr, f->f_suffix.len );
208	    file += f->f_suffix.len;
209	}
210
211	if( f->f_member.len )
212	{
213	    *file++ = '(';
214	    memcpy( file, f->f_member.ptr, f->f_member.len );
215	    file += f->f_member.len;
216	    *file++ = ')';
217	}
218	*file = 0;
219}
220
221/*
222 *	path_parent() - make a PATHNAME point to its parent dir
223 */
224
225void
226path_parent( PATHNAME *f )
227{
228	/* just set everything else to nothing */
229
230	f->f_base.ptr =
231	f->f_suffix.ptr =
232	f->f_member.ptr = "";
233
234	f->f_base.len =
235	f->f_suffix.len =
236	f->f_member.len = 0;
237}
238
239/*
240 *	normalize_path() - normalize a path
241 *
242 *	It doesn't really generate a unique representation of a path to an entry,
243 *	but at least reduces the number of categories that represent the same
244 *	entry. On error, or if the supplied buffer is too small, NULL is returned.
245 */
246
247char *
248normalize_path(const char *path, char *buffer, size_t bufferSize)
249{
250	// init cwd
251	static char _cwd[PATH_MAX];
252	static char *cwd = 0;
253	static size_t cwdLen = 0;
254	int pathLen = (path ? strlen(path) : 0);
255	int resultLen = 0;
256	int resolveDotDot = !0;
257	// init cwd
258	if (!cwd) {
259		cwd = getcwd(_cwd, PATH_MAX);
260		if (cwd)
261			cwdLen = strlen(cwd);
262		else
263			return 0;
264	}
265	// check length
266	if (cwdLen + pathLen + 2 > bufferSize)
267		return 0;
268	// construct result
269	if (pathLen > 0 && path[0] == PATH_DELIM) {
270		// absolute path: ignore cwd
271		buffer[0] = PATH_DELIM;
272		buffer[1] = '\0';
273		resultLen = 1;
274		path++;
275		pathLen--;
276	} else {
277		// relative path: copy cwd into result
278		memcpy(buffer, cwd, cwdLen + 1);
279		resultLen = cwdLen;
280	}
281	// append path componentwise to the result, skipping "." and empty
282	// components, and chopping off a component per ".."
283	while (pathLen > 0) {
284		// find component
285		char *separator = strchr(path, PATH_DELIM);
286		const char *component = path;
287		int componentLen = 0;
288		if (separator) {
289			componentLen = separator - path;
290			pathLen -= componentLen + 1;
291			path = separator + 1;
292		} else {
293			componentLen = pathLen;
294			path += componentLen;
295			pathLen = 0;
296		}
297		// handle found component
298		if (componentLen > 0) {
299			if (componentLen == 1 && component[0] == '.') {
300				// component is ".": skip
301			} else if (resolveDotDot && componentLen == 2 && component[0] == '.'
302					   && component[1] == '.') {
303				// component is "..": eat the last component of the result
304				char *lastSeparator = strrchr(buffer, PATH_DELIM);
305				if (lastSeparator) {
306					resultLen = lastSeparator - buffer;
307					if (resultLen == 0) {
308						// always leave at least the root
309						buffer[0] = PATH_DELIM;
310						resultLen = 1;
311					}
312					buffer[resultLen] = '\0';
313				} // else: not good
314			} else {
315				// normal component: append
316				if (resultLen < 1 || buffer[resultLen - 1] != PATH_DELIM)
317					buffer[resultLen++] = PATH_DELIM;
318				memcpy(buffer + resultLen, component, componentLen);
319				resultLen += componentLen;
320				buffer[resultLen] = '\0';
321				// After we found the first real path component, we don't
322				// resolve ".." anymore, as it could be a (sym)link, which
323				// could break the algorithm.
324				resolveDotDot = 0;
325			}
326		}
327	}
328	return buffer;
329}
330
331# endif /* unix, NT, OS/2, AmigaOS */
332