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 = int32_t(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 = int32_t(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 = int32_t(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 = int32_t(end - p);
119         end = p;
120     }
121 
122     /* Leaves base. */
123     f->f_base.ptr = file;
124     f->f_base.len = int32_t(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     int32_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