]>
Commit | Line | Data |
---|---|---|
7c673cae FG |
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 | ||
b32b8144 FG |
53 | int file_collect_archive_content_( file_archive_info_t * const archive ); |
54 | ||
7c673cae FG |
55 | /* |
56 | * file_collect_dir_content_() - collects directory content information | |
57 | */ | |
58 | ||
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 | ||
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 | ||
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 | ||
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 | ||
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 | ||
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 | ||
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 | ||
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 | ||
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 = 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 */ |