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 /* This file is ALSO:
8 * Copyright 2001-2004 David Abrahams.
9 * Copyright 2005 Rene Rivera.
10 * Distributed under the Boost Software License, Version 1.0.
11 * (See accompanying file LICENSE_1_0.txt or copy at
12 * http://www.boost.org/LICENSE_1_0.txt)
13 */
14
15 /*
16 * pathsys.c - platform independent path manipulation support
17 *
18 * External routines:
19 * path_build() - build a filename given dir/base/suffix/member
20 * path_parent() - make a PATHNAME point to its parent dir
21 * path_parse() - split a file name into dir/base/suffix/member
22 * path_tmpdir() - returns the system dependent temporary folder path
23 * path_tmpfile() - returns a new temporary path
24 * path_tmpnam() - returns a new temporary name
25 *
26 * File_parse() and path_build() just manipulate a string and a structure;
27 * they do not make system calls.
28 */
29
30 #include "jam.h"
31 #include "pathsys.h"
32
33 #include "filesys.h"
34
35 #include <stdlib.h>
36 #include <time.h>
37
38 #include <algorithm>
39
40
41 /* Internal OS specific implementation details - have names ending with an
42 * underscore and are expected to be implemented in an OS specific pathXXX.c
43 * module.
44 */
45 unsigned long path_get_process_id_( void );
46 void path_get_temp_path_( string * buffer );
47 int path_translate_to_os_( char const * f, string * file );
48
49
50 /*
51 * path_parse() - split a file name into dir/base/suffix/member
52 */
53
path_parse(char const * file,PATHNAME * f)54 void path_parse( char const * file, PATHNAME * f )
55 {
56 char const * p;
57 char const * q;
58 char const * end;
59
60 memset( (char *)f, 0, sizeof( *f ) );
61
62 /* Look for '<grist>'. */
63
64 if ( ( file[ 0 ] == '<' ) && ( p = strchr( file, '>' ) ) )
65 {
66 f->f_grist.ptr = file;
67 f->f_grist.len = p - file;
68 file = p + 1;
69 }
70
71 /* Look for 'dir/'. */
72
73 p = strrchr( file, '/' );
74
75 #if PATH_DELIM == '\\'
76 /* On NT, look for dir\ as well */
77 {
78 char const * p1 = strrchr( p ? p + 1 : file, '\\' );
79 if ( p1 ) p = p1;
80 }
81 #endif
82
83 if ( p )
84 {
85 f->f_dir.ptr = file;
86 f->f_dir.len = p - file;
87
88 /* Special case for / - dirname is /, not "" */
89 if ( !f->f_dir.len )
90 ++f->f_dir.len;
91
92 #if PATH_DELIM == '\\'
93 /* Special case for D:/ - dirname is D:/, not "D:" */
94 if ( f->f_dir.len == 2 && file[ 1 ] == ':' )
95 ++f->f_dir.len;
96 #endif
97
98 file = p + 1;
99 }
100
101 end = file + strlen( file );
102
103 /* Look for '(member)'. */
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'. This would be memrchr(). */
112 p = 0;
113 for ( q = file; ( q = (char *)memchr( q, '.', end - q ) ); ++q )
114 p = q;
115 if ( p )
116 {
117 f->f_suffix.ptr = p;
118 f->f_suffix.len = end - p;
119 end = p;
120 }
121
122 /* Leaves base. */
123 f->f_base.ptr = file;
124 f->f_base.len = end - file;
125 }
126
127
128 /*
129 * is_path_delim() - true iff c is a path delimiter
130 */
131
is_path_delim(char const c)132 static int is_path_delim( char const c )
133 {
134 return c == PATH_DELIM
135 #if PATH_DELIM == '\\'
136 || c == '/'
137 #endif
138 ;
139 }
140
141
142 /*
143 * as_path_delim() - convert c to a path delimiter if it is not one already
144 */
145
as_path_delim(char const c)146 static char as_path_delim( char const c )
147 {
148 return is_path_delim( c ) ? c : PATH_DELIM;
149 }
150
151
152 /*
153 * path_build() - build a filename given dir/base/suffix/member
154 *
155 * To avoid changing slash direction on NT when reconstituting paths, instead of
156 * unconditionally appending PATH_DELIM we check the past-the-end character of
157 * the previous path element. If it is a path delimiter, we append that, and
158 * only append PATH_DELIM as a last resort. This heuristic is based on the fact
159 * that PATHNAME objects are usually the result of calling path_parse, which
160 * leaves the original slashes in the past-the-end position. Correctness depends
161 * on the assumption that all strings are zero terminated, so a past-the-end
162 * character will always be available.
163 *
164 * As an attendant patch, we had to ensure that backslashes are used explicitly
165 * in 'timestamp.c'.
166 */
167
path_build(PATHNAME * f,string * file)168 void path_build( PATHNAME * f, string * file )
169 {
170 int check_f;
171 int check_f_pos;
172
173 file_build1( f, file );
174
175 /* Do not prepend root if it is '.' or the directory is rooted. */
176 check_f = (f->f_root.len
177 && !( f->f_root.len == 1 && f->f_root.ptr[ 0 ] == '.')
178 && !( f->f_dir.len && f->f_dir.ptr[ 0 ] == '/' ));
179 #if PATH_DELIM == '\\'
180 check_f = (check_f
181 && !( f->f_dir.len && f->f_dir.ptr[ 0 ] == '\\' )
182 && !( f->f_dir.len && f->f_dir.ptr[ 1 ] == ':' ));
183 #endif
184 if (check_f)
185 {
186 string_append_range( file, f->f_root.ptr, f->f_root.ptr + f->f_root.len
187 );
188 /* If 'root' already ends with a path delimiter, do not add another one.
189 */
190 if ( !is_path_delim( f->f_root.ptr[ f->f_root.len - 1 ] ) )
191 string_push_back( file, as_path_delim( f->f_root.ptr[ f->f_root.len
192 ] ) );
193 }
194
195 if ( f->f_dir.len )
196 string_append_range( file, f->f_dir.ptr, f->f_dir.ptr + f->f_dir.len );
197
198 /* Put path separator between dir and file. */
199 /* Special case for root dir: do not add another path separator. */
200 check_f_pos = (f->f_dir.len && ( f->f_base.len || f->f_suffix.len ));
201 #if PATH_DELIM == '\\'
202 check_f_pos = (check_f_pos && !( f->f_dir.len == 3 && f->f_dir.ptr[ 1 ] == ':' ));
203 #endif
204 check_f_pos = (check_f_pos && !( f->f_dir.len == 1 && is_path_delim( f->f_dir.ptr[ 0 ])));
205 if (check_f_pos)
206 string_push_back( file, as_path_delim( f->f_dir.ptr[ f->f_dir.len ] ) );
207
208 if ( f->f_base.len )
209 string_append_range( file, f->f_base.ptr, f->f_base.ptr + f->f_base.len
210 );
211
212 if ( f->f_suffix.len )
213 string_append_range( file, f->f_suffix.ptr, f->f_suffix.ptr +
214 f->f_suffix.len );
215
216 if ( f->f_member.len )
217 {
218 string_push_back( file, '(' );
219 string_append_range( file, f->f_member.ptr, f->f_member.ptr +
220 f->f_member.len );
221 string_push_back( file, ')' );
222 }
223 }
224
225
226 /*
227 * path_parent() - make a PATHNAME point to its parent dir
228 */
229
path_parent(PATHNAME * f)230 void path_parent( PATHNAME * f )
231 {
232 f->f_base.ptr = f->f_suffix.ptr = f->f_member.ptr = "";
233 f->f_base.len = f->f_suffix.len = f->f_member.len = 0;
234 }
235
236
237 /*
238 * path_tmpdir() - returns the system dependent temporary folder path
239 *
240 * Returned value is stored inside a static buffer and should not be modified.
241 * Returned value does *not* include a trailing path separator.
242 */
243
path_tmpdir()244 string const * path_tmpdir()
245 {
246 static string buffer[ 1 ];
247 static int have_result;
248 if ( !have_result )
249 {
250 string_new( buffer );
251 path_get_temp_path_( buffer );
252 have_result = 1;
253 }
254 return buffer;
255 }
256
257
258 /*
259 * path_tmpnam() - returns a new temporary name
260 */
261
path_tmpnam(void)262 OBJECT * path_tmpnam( void )
263 {
264 char name_buffer[ 64 ];
265 unsigned long const pid = path_get_process_id_();
266 static unsigned long t;
267 if ( !t ) t = time( 0 ) & 0xffff;
268 t += 1;
269 sprintf( name_buffer, "jam%lx%lx.000", pid, t );
270 return object_new( name_buffer );
271 }
272
273
274 /*
275 * path_tmpfile() - returns a new temporary path
276 */
277
path_tmpfile(void)278 OBJECT * path_tmpfile( void )
279 {
280 OBJECT * result;
281 OBJECT * tmpnam;
282
283 string file_path[ 1 ];
284 string_copy( file_path, path_tmpdir()->value );
285 string_push_back( file_path, PATH_DELIM );
286 tmpnam = path_tmpnam();
287 string_append( file_path, object_str( tmpnam ) );
288 object_free( tmpnam );
289 result = object_new( file_path->value );
290 string_free( file_path );
291
292 return result;
293 }
294
295
296 /*
297 * path_translate_to_os() - translate filename to OS-native path
298 *
299 */
300
path_translate_to_os(char const * f,string * file)301 int path_translate_to_os( char const * f, string * file )
302 {
303 return path_translate_to_os_( f, file );
304 }
305
306
normalize(const std::string & p)307 std::string b2::paths::normalize(const std::string &p)
308 {
309 // We root the path as a sentinel. But we need to remember that we did so
310 // to un-root afterwards.
311 std::string result{"/"};
312 bool is_rooted = p[0] == '/' || p[0] == '\\';
313 result += p;
314
315 // Convert \ into /. On Windows, paths using / and \ are equivalent, and we
316 // want this function to obtain a canonic representation.
317 std::replace(result.begin(), result.end(), '\\', '/');
318
319 size_t ellipsis = 0;
320 for (auto end_pos = result.length(); end_pos > 0; )
321 {
322 auto path_pos = result.rfind('/', end_pos-1);
323 if (path_pos == std::string::npos) break;
324 if (path_pos == end_pos-1)
325 {
326 /* Found a trailing or duplicate '/'. Remove it. */
327 result.erase(path_pos, 1);
328 }
329 else if ((end_pos-path_pos == 2) && result[path_pos+1] == '.')
330 {
331 /* Found '/.'. Remove them all. */
332 result.erase(path_pos, 2);
333 }
334 else if ((end_pos-path_pos == 3) && result[path_pos+1] == '.' && result[path_pos+2] == '.')
335 {
336 /* Found '/..'. Remove them all. */
337 result.erase(path_pos, 3);
338 ellipsis += 1;
339 }
340 else if (ellipsis > 0)
341 {
342 /* An elided parent path. Remove it. */
343 result.erase(path_pos, end_pos-path_pos);
344 ellipsis -= 1;
345 }
346 end_pos = path_pos;
347 }
348
349 // Now we know that we need to add exactly ellipsis '..' path elements to the
350 // front and that our string is either empty or has a '/' as its first
351 // significant character. If we have any ellipsis remaining then the passed
352 // path must not have been rooted or else it is invalid we return empty.
353 if (ellipsis > 0)
354 {
355 if (is_rooted) return "";
356 do result.insert(0, "/.."); while (--ellipsis > 0);
357 }
358
359 // If we reduced to nothing we return a valid path depending on wether
360 // the input was rooted or not.
361 if (result.empty()) return is_rooted ? "/" : ".";
362 // Return the result without the sentinel if it's not rooted.
363 if (!is_rooted) return result.substr(1);
364
365 return result;
366 }
367