Imported Upstream version 1.57.0
[platform/upstream/boost.git] / tools / build / src / engine / filent.c
1 /*
2  * Copyright 1993, 1995 Christopher Seiwald.
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  * filent.c - scan directories and archives on NT
17  *
18  * External routines:
19  *  file_archscan()                 - scan an archive for files
20  *  file_mkdir()                    - create a directory
21  *  file_supported_fmt_resolution() - file modification timestamp resolution
22  *
23  * External routines called only via routines in filesys.c:
24  *  file_collect_dir_content_() - collects directory content information
25  *  file_dirscan_()             - OS specific file_dirscan() implementation
26  *  file_query_()               - query information about a path from the OS
27  */
28
29 #include "jam.h"
30 #ifdef OS_NT
31 #include "filesys.h"
32
33 #include "object.h"
34 #include "pathsys.h"
35 #include "strings.h"
36
37 #ifdef __BORLANDC__
38 # undef FILENAME  /* cpp namespace collision */
39 #endif
40
41 #define WIN32_LEAN_AND_MEAN
42 #include <windows.h>
43
44 #include <assert.h>
45 #include <ctype.h>
46 #include <direct.h>
47 #include <io.h>
48
49
50 /*
51  * file_collect_dir_content_() - collects directory content information
52  */
53
54 int file_collect_dir_content_( file_info_t * const d )
55 {
56     PATHNAME f;
57     string pathspec[ 1 ];
58     string pathname[ 1 ];
59     LIST * files = L0;
60     int d_length;
61
62     assert( d );
63     assert( d->is_dir );
64     assert( list_empty( d->files ) );
65
66     d_length = strlen( object_str( d->name ) );
67
68     memset( (char *)&f, '\0', sizeof( f ) );
69     f.f_dir.ptr = object_str( d->name );
70     f.f_dir.len = d_length;
71
72     /* Prepare file search specification for the FindXXX() Windows API. */
73     if ( !d_length )
74         string_copy( pathspec, ".\\*" );
75     else
76     {
77         /* We can not simply assume the given folder name will never include its
78          * trailing path separator or otherwise we would not support the Windows
79          * root folder specified without its drive letter, i.e. '\'.
80          */
81         char const trailingChar = object_str( d->name )[ d_length - 1 ] ;
82         string_copy( pathspec, object_str( d->name ) );
83         if ( ( trailingChar != '\\' ) && ( trailingChar != '/' ) )
84             string_append( pathspec, "\\" );
85         string_append( pathspec, "*" );
86     }
87
88     /* The following code for collecting information about all files in a folder
89      * needs to be kept synchronized with how the file_query() operation is
90      * implemented (collects information about a single file).
91      */
92     {
93         /* FIXME: Avoid duplicate FindXXX Windows API calls here and in the code
94          * determining a normalized path.
95          */
96         WIN32_FIND_DATA finfo;
97         HANDLE const findHandle = FindFirstFileA( pathspec->value, &finfo );
98         if ( findHandle == INVALID_HANDLE_VALUE )
99         {
100             string_free( pathspec );
101             return -1;
102         }
103
104         string_new( pathname );
105         do
106         {
107             OBJECT * pathname_obj;
108
109             f.f_base.ptr = finfo.cFileName;
110             f.f_base.len = strlen( finfo.cFileName );
111
112             string_truncate( pathname, 0 );
113             path_build( &f, pathname );
114
115             pathname_obj = object_new( pathname->value );
116             path_register_key( pathname_obj );
117             files = list_push_back( files, pathname_obj );
118             {
119                 int found;
120                 file_info_t * const ff = file_info( pathname_obj, &found );
121                 ff->is_dir = finfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
122                 ff->is_file = !ff->is_dir;
123                 ff->exists = 1;
124                 timestamp_from_filetime( &ff->time, &finfo.ftLastWriteTime );
125                 // Use the timestamp of the link target, not the link itself
126                 // (i.e. stat instead of lstat)
127                 if ( finfo.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT )
128                 {
129                     HANDLE hLink = CreateFileA( pathname->value, 0, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL );
130                     BY_HANDLE_FILE_INFORMATION target_finfo[ 1 ];
131                     if ( hLink != INVALID_HANDLE_VALUE && GetFileInformationByHandle( hLink, target_finfo ) )
132                     {
133                         ff->is_file = target_finfo->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ? 0 : 1;
134                         ff->is_dir = target_finfo->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ? 1 : 0;
135                         timestamp_from_filetime( &ff->time, &target_finfo->ftLastWriteTime );
136                     }
137                 }
138             }
139         }
140         while ( FindNextFile( findHandle, &finfo ) );
141
142         FindClose( findHandle );
143     }
144
145     string_free( pathname );
146     string_free( pathspec );
147
148     d->files = files;
149     return 0;
150 }
151
152
153 /*
154  * file_dirscan_() - OS specific file_dirscan() implementation
155  */
156
157 void file_dirscan_( file_info_t * const d, scanback func, void * closure )
158 {
159     assert( d );
160     assert( d->is_dir );
161
162     /* Special case \ or d:\ : enter it */
163     {
164         char const * const name = object_str( d->name );
165         if ( name[ 0 ] == '\\' && !name[ 1 ] )
166         {
167             (*func)( closure, d->name, 1 /* stat()'ed */, &d->time );
168         }
169         else if ( name[ 0 ] && name[ 1 ] == ':' && name[ 2 ] && !name[ 3 ] )
170         {
171             /* We have just entered a 3-letter drive name spelling (with a
172              * trailing slash), into the hash table. Now enter its two-letter
173              * variant, without the trailing slash, so that if we try to check
174              * whether "c:" exists, we hit it.
175              *
176              * Jam core has workarounds for that. Given:
177              *    x = c:\whatever\foo ;
178              *    p = $(x:D) ;
179              *    p2 = $(p:D) ;
180              * There will be no trailing slash in $(p), but there will be one in
181              * $(p2). But, that seems rather fragile.
182              */
183             OBJECT * const dir_no_slash = object_new_range( name, 2 );
184             (*func)( closure, d->name, 1 /* stat()'ed */, &d->time );
185             (*func)( closure, dir_no_slash, 1 /* stat()'ed */, &d->time );
186             object_free( dir_no_slash );
187         }
188     }
189 }
190
191
192 /*
193  * file_mkdir() - create a directory
194  */
195
196 int file_mkdir( char const * const path )
197 {
198     return _mkdir( path );
199 }
200
201
202 /*
203  * file_query_() - query information about a path from the OS
204  *
205  * The following code for collecting information about a single file needs to be
206  * kept synchronized with how the file_collect_dir_content_() operation is
207  * implemented (collects information about all files in a folder).
208  */
209
210 int try_file_query_root( file_info_t * const info )
211 {
212     WIN32_FILE_ATTRIBUTE_DATA fileData;
213     char buf[ 4 ];
214     char const * const pathstr = object_str( info->name );
215     if ( !pathstr[ 0 ] )
216     {
217         buf[ 0 ] = '.';
218         buf[ 1 ] = 0;
219     }
220     else if ( pathstr[ 0 ] == '\\' && ! pathstr[ 1 ] )
221     {
222         buf[ 0 ] = '\\';
223         buf[ 1 ] = '\0';
224     }
225     else if ( pathstr[ 1 ] == ':' )
226     {
227         if ( !pathstr[ 2 ] )
228         {
229         }
230         else if ( !pathstr[ 2 ] || ( pathstr[ 2 ] == '\\' && !pathstr[ 3 ] ) )
231         {
232             buf[ 0 ] = pathstr[ 0 ];
233             buf[ 1 ] = ':';
234             buf[ 2 ] = '\\';
235             buf[ 3 ] = '\0';
236         }
237         else
238         {
239             return 0;
240         }
241     }
242     else
243     {
244         return 0;
245     }
246         
247     /* We have a root path */
248     if ( !GetFileAttributesExA( buf, GetFileExInfoStandard, &fileData ) )
249     {
250         info->is_dir = 0;
251         info->is_file = 0;
252         info->exists = 0;
253         timestamp_clear( &info->time );
254     }
255     else
256     {
257         info->is_dir = fileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
258         info->is_file = !info->is_dir;
259         info->exists = 1;
260         timestamp_from_filetime( &info->time, &fileData.ftLastWriteTime );
261     }
262     return 1;
263 }
264
265 void file_query_( file_info_t * const info )
266 {
267     char const * const pathstr = object_str( info->name );
268     const char * dir;
269     OBJECT * parent;
270     file_info_t * parent_info;
271
272     if ( try_file_query_root( info ) )
273         return;
274
275     if ( ( dir = strrchr( pathstr, '\\' ) ) )
276     {
277         parent = object_new_range( pathstr, dir - pathstr );
278     }
279     else
280     {
281         parent = object_copy( constant_empty );
282     }
283     parent_info = file_query( parent );
284     object_free( parent );
285     if ( !parent_info || !parent_info->is_dir )
286     {
287         info->is_dir = 0;
288         info->is_file = 0;
289         info->exists = 0;
290         timestamp_clear( &info->time );
291     }
292     else
293     {
294         info->is_dir = 0;
295         info->is_file = 0;
296         info->exists = 0;
297         timestamp_clear( &info->time );
298         if ( list_empty( parent_info->files ) )
299             file_collect_dir_content_( parent_info );
300     }
301 }
302
303
304 /*
305  * file_supported_fmt_resolution() - file modification timestamp resolution
306  *
307  * Returns the minimum file modification timestamp resolution supported by this
308  * Boost Jam implementation. File modification timestamp changes of less than
309  * the returned value might not be recognized.
310  *
311  * Does not take into consideration any OS or file system related restrictions.
312  *
313  * Return value 0 indicates that any value supported by the OS is also supported
314  * here.
315  */
316
317 void file_supported_fmt_resolution( timestamp * const t )
318 {
319     /* On Windows we support nano-second file modification timestamp resolution,
320      * just the same as the Windows OS itself.
321      */
322     timestamp_init( t, 0, 0 );
323 }
324
325
326 /*
327  * file_archscan() - scan an archive for files
328  */
329
330 /* Straight from SunOS */
331
332 #define ARMAG  "!<arch>\n"
333 #define SARMAG  8
334
335 #define ARFMAG  "`\n"
336
337 struct ar_hdr
338 {
339     char ar_name[ 16 ];
340     char ar_date[ 12 ];
341     char ar_uid[ 6 ];
342     char ar_gid[ 6 ];
343     char ar_mode[ 8 ];
344     char ar_size[ 10 ];
345     char ar_fmag[ 2 ];
346 };
347
348 #define SARFMAG  2
349 #define SARHDR  sizeof( struct ar_hdr )
350
351 void file_archscan( char const * archive, scanback func, void * closure )
352 {
353     struct ar_hdr ar_hdr;
354     char * string_table = 0;
355     char buf[ MAXJPATH ];
356     long offset;
357     int const fd = open( archive, O_RDONLY | O_BINARY, 0 );
358
359     if ( fd < 0 )
360         return;
361
362     if ( read( fd, buf, SARMAG ) != SARMAG || strncmp( ARMAG, buf, SARMAG ) )
363     {
364         close( fd );
365         return;
366     }
367
368     offset = SARMAG;
369
370     if ( DEBUG_BINDSCAN )
371         printf( "scan archive %s\n", archive );
372
373     while ( ( read( fd, &ar_hdr, SARHDR ) == SARHDR ) &&
374         !memcmp( ar_hdr.ar_fmag, ARFMAG, SARFMAG ) )
375     {
376         long lar_date;
377         long lar_size;
378         char * name = 0;
379         char * endname;
380
381         sscanf( ar_hdr.ar_date, "%ld", &lar_date );
382         sscanf( ar_hdr.ar_size, "%ld", &lar_size );
383
384         lar_size = ( lar_size + 1 ) & ~1;
385
386         if ( ar_hdr.ar_name[ 0 ] == '/' && ar_hdr.ar_name[ 1 ] == '/' )
387         {
388             /* This is the "string table" entry of the symbol table, holding
389              * filename strings longer than 15 characters, i.e. those that do
390              * not fit into ar_name.
391              */
392             string_table = BJAM_MALLOC_ATOMIC( lar_size + 1 );
393             if ( read( fd, string_table, lar_size ) != lar_size )
394                 printf( "error reading string table\n" );
395             string_table[ lar_size ] = '\0';
396             offset += SARHDR + lar_size;
397             continue;
398         }
399         else if ( ar_hdr.ar_name[ 0 ] == '/' && ar_hdr.ar_name[ 1 ] != ' ' )
400         {
401             /* Long filenames are recognized by "/nnnn" where nnnn is the
402              * string's offset in the string table represented in ASCII
403              * decimals.
404              */
405             name = string_table + atoi( ar_hdr.ar_name + 1 );
406             for ( endname = name; *endname && *endname != '\n'; ++endname );
407         }
408         else
409         {
410             /* normal name */
411             name = ar_hdr.ar_name;
412             endname = name + sizeof( ar_hdr.ar_name );
413         }
414
415         /* strip trailing white-space, slashes, and backslashes */
416
417         while ( endname-- > name )
418             if ( !isspace( *endname ) && ( *endname != '\\' ) && ( *endname !=
419                 '/' ) )
420                 break;
421         *++endname = 0;
422
423         /* strip leading directory names, an NT specialty */
424         {
425             char * c;
426             if ( c = strrchr( name, '/' ) )
427                 name = c + 1;
428             if ( c = strrchr( name, '\\' ) )
429                 name = c + 1;
430         }
431
432         sprintf( buf, "%s(%.*s)", archive, endname - name, name );
433         {
434             OBJECT * const member = object_new( buf );
435             timestamp time;
436             timestamp_init( &time, (time_t)lar_date, 0 );
437             (*func)( closure, member, 1 /* time valid */, &time );
438             object_free( member );
439         }
440
441         offset += SARHDR + lar_size;
442         lseek( fd, offset, 0 );
443     }
444
445     close( fd );
446 }
447
448 #endif  /* OS_NT */