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  * pathnt.c - NT specific path manipulation support
17  */
18 
19 #include "jam.h"
20 #include "pathsys.h"
21 #include "hash.h"
22 
23 #define WIN32_LEAN_AND_MEAN
24 #include <windows.h>
25 
26 #ifdef OS_CYGWIN
27 # include <cygwin/version.h>
28 # include <sys/cygwin.h>
29 # ifdef CYGWIN_VERSION_CYGWIN_CONV
30 #  include <errno.h>
31 # endif
32 # include <windows.h>
33 #endif
34 
35 #include <assert.h>
36 #include <stdlib.h>
37 
38 
39 /* The definition of this in winnt.h is not ANSI-C compatible. */
40 #undef INVALID_FILE_ATTRIBUTES
41 #define INVALID_FILE_ATTRIBUTES ((DWORD)-1)
42 
43 
44 typedef struct path_key_entry
45 {
46     OBJECT * path;
47     OBJECT * key;
48     int exists;
49 } path_key_entry;
50 
51 static struct hash * path_key_cache;
52 
53 
54 /*
55  * path_get_process_id_()
56  */
57 
path_get_process_id_(void)58 unsigned long path_get_process_id_( void )
59 {
60     return GetCurrentProcessId();
61 }
62 
63 
64 /*
65  * path_get_temp_path_()
66  */
67 
path_get_temp_path_(string * buffer)68 void path_get_temp_path_( string * buffer )
69 {
70     DWORD pathLength = GetTempPathA( 0, NULL );
71     string_reserve( buffer, pathLength );
72     pathLength = GetTempPathA( pathLength, buffer->value );
73     buffer->value[ pathLength - 1 ] = '\0';
74     buffer->size = pathLength - 1;
75 }
76 
77 
78 /*
79  * canonicWindowsPath() - convert a given path into its canonic/long format
80  *
81  * Appends the canonic path to the end of the given 'string' object.
82  *
83  * FIXME: This function is still work-in-progress as it originally did not
84  * necessarily return the canonic path format (could return slightly different
85  * results for certain equivalent path strings) and could accept paths pointing
86  * to non-existing file system entities as well.
87  *
88  * Caches results internally, automatically caching any parent paths it has to
89  * convert to their canonic format in the process.
90  *
91  * Prerequisites:
92  *  - path given in normalized form, i.e. all of its folder separators have
93  *    already been converted into '\\'
94  *  - path_key_cache path/key mapping cache object already initialized
95  */
96 
canonicWindowsPath(char const * const path,int const path_length,string * const out)97 static int canonicWindowsPath( char const * const path, int const path_length,
98     string * const out )
99 {
100     char const * last_element;
101     unsigned long saved_size;
102     char const * p;
103     int missing_parent;
104 
105     /* This is only called via path_key(), which initializes the cache. */
106     assert( path_key_cache );
107 
108     if ( !path_length )
109         return 1;
110 
111     if ( path_length == 1 && path[ 0 ] == '\\' )
112     {
113         string_push_back( out, '\\' );
114         return 1;
115     }
116 
117     if ( path[ 1 ] == ':' &&
118         ( path_length == 2 ||
119         ( path_length == 3 && path[ 2 ] == '\\' ) ) )
120     {
121         string_push_back( out, toupper( path[ 0 ] ) );
122         string_push_back( out, ':' );
123         string_push_back( out, '\\' );
124         return 1;
125     }
126 
127     /* Find last '\\'. */
128     for ( p = path + path_length - 1; p >= path && *p != '\\'; --p );
129     last_element = p + 1;
130 
131     /* Special case '\' && 'D:\' - include trailing '\'. */
132     if ( p == path ||
133         p == path + 2 && path[ 1 ] == ':' )
134         ++p;
135 
136     missing_parent = 0;
137 
138     if ( p >= path )
139     {
140         char const * const dir = path;
141         int const dir_length = p - path;
142         OBJECT * const dir_obj = object_new_range( dir, dir_length );
143         int found;
144         path_key_entry * const result = (path_key_entry *)hash_insert(
145             path_key_cache, dir_obj, &found );
146         if ( !found )
147         {
148             result->path = dir_obj;
149             if ( canonicWindowsPath( dir, dir_length, out ) )
150                 result->exists = 1;
151             else
152                 result->exists = 0;
153             result->key = object_new( out->value );
154         }
155         else
156         {
157             object_free( dir_obj );
158             string_append( out, object_str( result->key ) );
159         }
160         if ( !result->exists )
161             missing_parent = 1;
162     }
163 
164     if ( out->size && out->value[ out->size - 1 ] != '\\' )
165         string_push_back( out, '\\' );
166 
167     saved_size = out->size;
168     string_append_range( out, last_element, path + path_length );
169 
170     if ( !missing_parent )
171     {
172         char const * const n = last_element;
173         int const n_length = path + path_length - n;
174         if ( !( n_length == 1 && n[ 0 ] == '.' )
175             && !( n_length == 2 && n[ 0 ] == '.' && n[ 1 ] == '.' ) )
176         {
177             WIN32_FIND_DATA fd;
178             HANDLE const hf = FindFirstFileA( out->value, &fd );
179             if ( hf != INVALID_HANDLE_VALUE )
180             {
181                 string_truncate( out, saved_size );
182                 string_append( out, fd.cFileName );
183                 FindClose( hf );
184                 return 1;
185             }
186         }
187         else
188         {
189             return 1;
190         }
191     }
192     return 0;
193 }
194 
195 
196 /*
197  * normalize_path() - 'normalizes' the given path for the path-key mapping
198  *
199  * The resulting string has nothing to do with 'normalized paths' as used in
200  * Boost Jam build scripts and the built-in NORMALIZE_PATH rule. It is intended
201  * to be used solely as an intermediate step when mapping an arbitrary path to
202  * its canonical representation.
203  *
204  * When choosing the intermediate string the important things are for it to be
205  * inexpensive to calculate and any two paths having different canonical
206  * representations also need to have different calculated intermediate string
207  * representations. Any implemented additional rules serve only to simplify
208  * constructing the canonical path representation from the calculated
209  * intermediate string.
210  *
211  * Implemented returned path rules:
212  *  - use backslashes as path separators
213  *  - lowercase only (since all Windows file systems are case insensitive)
214  *  - trim trailing path separator except in case of a root path, i.e. 'X:\'
215  */
216 
normalize_path(string * path)217 static void normalize_path( string * path )
218 {
219     char * s;
220     for ( s = path->value; s < path->value + path->size; ++s )
221         *s = *s == '/' ? '\\' : tolower( *s );
222     /* Strip trailing "/". */
223     if ( path->size && path->size != 3 && path->value[ path->size - 1 ] == '\\'
224         )
225         string_pop_back( path );
226 }
227 
228 
path_key(OBJECT * const path,int const known_to_be_canonic)229 static path_key_entry * path_key( OBJECT * const path,
230     int const known_to_be_canonic )
231 {
232     path_key_entry * result;
233     int found;
234 
235     if ( !path_key_cache )
236         path_key_cache = hashinit( sizeof( path_key_entry ), "path to key" );
237 
238     result = (path_key_entry *)hash_insert( path_key_cache, path, &found );
239     if ( !found )
240     {
241         OBJECT * normalized;
242         int normalized_size;
243         path_key_entry * nresult;
244         result->path = path;
245         {
246             string buf[ 1 ];
247             string_copy( buf, object_str( path ) );
248             normalize_path( buf );
249             normalized = object_new( buf->value );
250             normalized_size = buf->size;
251             string_free( buf );
252         }
253         nresult = (path_key_entry *)hash_insert( path_key_cache, normalized,
254             &found );
255         if ( !found || nresult == result )
256         {
257             nresult->path = normalized;
258             if ( known_to_be_canonic )
259             {
260                 nresult->key = object_copy( path );
261                 nresult->exists = 1;
262             }
263             else
264             {
265                 string canonic_path[ 1 ];
266                 string_new( canonic_path );
267                 if ( canonicWindowsPath( object_str( normalized ), normalized_size,
268                         canonic_path ) )
269                     nresult->exists = 1;
270                 else
271                     nresult->exists = 0;
272                 nresult->key = object_new( canonic_path->value );
273                 string_free( canonic_path );
274             }
275         }
276         else
277             object_free( normalized );
278         if ( nresult != result )
279         {
280             result->path = object_copy( path );
281             result->key = object_copy( nresult->key );
282             result->exists = nresult->exists;
283         }
284     }
285 
286     return result;
287 }
288 
289 
290 /*
291  * translate_path_cyg2win() - conversion of a cygwin to a Windows path.
292  *
293  * FIXME: skip grist
294  */
295 
296 #ifdef OS_CYGWIN
translate_path_cyg2win(string * path)297 static int translate_path_cyg2win( string * path )
298 {
299     int translated = 0;
300 
301 #ifdef CYGWIN_VERSION_CYGWIN_CONV
302     /* Use new Cygwin API added with Cygwin 1.7. Old one had no error
303      * handling and has been deprecated.
304      */
305     char * dynamicBuffer = 0;
306     char buffer[ MAX_PATH + 1001 ];
307     char const * result = buffer;
308     cygwin_conv_path_t const conv_type = CCP_POSIX_TO_WIN_A | CCP_RELATIVE;
309     ssize_t const apiResult = cygwin_conv_path( conv_type, path->value,
310         buffer, sizeof( buffer ) / sizeof( *buffer ) );
311     assert( apiResult == 0 || apiResult == -1 );
312     assert( apiResult || strlen( result ) < sizeof( buffer ) / sizeof(
313         *buffer ) );
314     if ( apiResult )
315     {
316         result = 0;
317         if ( errno == ENOSPC )
318         {
319             ssize_t const size = cygwin_conv_path( conv_type, path->value,
320                 NULL, 0 );
321             assert( size >= -1 );
322             if ( size > 0 )
323             {
324                 dynamicBuffer = (char *)BJAM_MALLOC_ATOMIC( size );
325                 if ( dynamicBuffer )
326                 {
327                     ssize_t const apiResult = cygwin_conv_path( conv_type,
328                         path->value, dynamicBuffer, size );
329                     assert( apiResult == 0 || apiResult == -1 );
330                     if ( !apiResult )
331                     {
332                         result = dynamicBuffer;
333                         assert( strlen( result ) < size );
334                     }
335                 }
336             }
337         }
338     }
339 #else  /* CYGWIN_VERSION_CYGWIN_CONV */
340     /* Use old Cygwin API deprecated with Cygwin 1.7. */
341     char result[ MAX_PATH + 1 ];
342     cygwin_conv_to_win32_path( path->value, result );
343     assert( strlen( result ) <= MAX_PATH );
344 #endif  /* CYGWIN_VERSION_CYGWIN_CONV */
345 
346     if ( result )
347     {
348         string_truncate( path, 0 );
349         string_append( path, result );
350         translated = 1;
351     }
352 
353 #ifdef CYGWIN_VERSION_CYGWIN_CONV
354     if ( dynamicBuffer )
355         BJAM_FREE( dynamicBuffer );
356 #endif
357 
358     return translated;
359 }
360 #endif  /* OS_CYGWIN */
361 
362 
363 /*
364  * path_translate_to_os_()
365  */
366 
path_translate_to_os_(char const * f,string * file)367 int path_translate_to_os_( char const * f, string * file )
368 {
369     int translated = 0;
370 
371     /* by default, pass on the original path */
372     string_copy( file, f );
373 
374 #ifdef OS_CYGWIN
375     translated = translate_path_cyg2win( file );
376 #endif
377 
378     return translated;
379 }
380 
381 
path_register_key(OBJECT * canonic_path)382 void path_register_key( OBJECT * canonic_path )
383 {
384     path_key( canonic_path, 1 );
385 }
386 
387 
path_as_key(OBJECT * path)388 OBJECT * path_as_key( OBJECT * path )
389 {
390     return object_copy( path_key( path, 0 )->key );
391 }
392 
393 
free_path_key_entry(void * xentry,void * const data)394 static void free_path_key_entry( void * xentry, void * const data )
395 {
396     path_key_entry * const entry = (path_key_entry *)xentry;
397     object_free( entry->path );
398     object_free( entry->key );
399 }
400 
401 
path_done(void)402 void path_done( void )
403 {
404     if ( path_key_cache )
405     {
406         hashenumerate( path_key_cache, &free_path_key_entry, 0 );
407         hashdone( path_key_cache );
408     }
409 }
410