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