]>
Commit | Line | Data |
---|---|---|
7c673cae FG |
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 | ||
20effc67 TL |
38 | #include <algorithm> |
39 | ||
7c673cae FG |
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 | ||
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 | { | |
92f5a8d4 | 78 | char const * p1 = strrchr( p ? p + 1 : file, '\\' ); |
7c673cae FG |
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 | ||
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 | ||
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 | ||
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 | ); | |
92f5a8d4 | 188 | /* If 'root' already ends with a path delimiter, do not add another one. |
7c673cae FG |
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 | ||
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 | ||
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 | ||
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 | ||
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 | ||
301 | int path_translate_to_os( char const * f, string * file ) | |
302 | { | |
303 | return path_translate_to_os_( f, file ); | |
304 | } | |
20effc67 TL |
305 | |
306 | ||
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 | } |