2 * Copyright 1993, 1995 Christopher Seiwald.
4 * This file is part of Jam - see jam.c for Copyright information.
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)
16 * filent.c - scan directories and archives on NT
19 * file_archscan() - scan an archive for files
20 * file_mkdir() - create a directory
21 * file_supported_fmt_resolution() - file modification timestamp resolution
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
37 #include "jam_strings.h"
41 # undef FILENAME /* cpp namespace collision */
44 #define WIN32_LEAN_AND_MEAN
53 int file_collect_archive_content_( file_archive_info_t
* const archive
);
56 * file_collect_dir_content_() - collects directory content information
59 int file_collect_dir_content_( file_info_t
* const d
)
69 assert( list_empty( d
->files
) );
71 d_length
= strlen( object_str( d
->name
) );
73 memset( (char *)&f
, '\0', sizeof( f
) );
74 f
.f_dir
.ptr
= object_str( d
->name
);
75 f
.f_dir
.len
= d_length
;
77 /* Prepare file search specification for the FindXXX() Windows API. */
79 string_copy( pathspec
, ".\\*" );
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. '\'.
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
, "*" );
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).
98 /* FIXME: Avoid duplicate FindXXX Windows API calls here and in the code
99 * determining a normalized path.
101 WIN32_FIND_DATAA finfo
;
102 HANDLE
const findHandle
= FindFirstFileA( pathspec
->value
, &finfo
);
103 if ( findHandle
== INVALID_HANDLE_VALUE
)
105 string_free( pathspec
);
109 string_new( pathname
);
112 OBJECT
* pathname_obj
;
114 f
.f_base
.ptr
= finfo
.cFileName
;
115 f
.f_base
.len
= strlen( finfo
.cFileName
);
117 string_truncate( pathname
, 0 );
118 path_build( &f
, pathname
);
120 pathname_obj
= object_new( pathname
->value
);
121 path_register_key( pathname_obj
);
122 files
= list_push_back( files
, pathname_obj
);
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
;
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
)
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
) )
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
);
145 while ( FindNextFileA( findHandle
, &finfo
) );
147 FindClose( findHandle
);
150 string_free( pathname
);
151 string_free( pathspec
);
159 * file_dirscan_() - OS specific file_dirscan() implementation
162 void file_dirscan_( file_info_t
* const d
, scanback func
, void * closure
)
167 /* Special case \ or d:\ : enter it */
169 char const * const name
= object_str( d
->name
);
170 if ( name
[ 0 ] == '\\' && !name
[ 1 ] )
172 (*func
)( closure
, d
->name
, 1 /* stat()'ed */, &d
->time
);
174 else if ( name
[ 0 ] && name
[ 1 ] == ':' && name
[ 2 ] && !name
[ 3 ] )
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.
181 * Jam core has workarounds for that. Given:
182 * x = c:\whatever\foo ;
185 * There will be no trailing slash in $(p), but there will be one in
186 * $(p2). But, that seems rather fragile.
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
);
198 * file_mkdir() - create a directory
201 int file_mkdir( char const * const path
)
203 return _mkdir( path
);
208 * file_query_() - query information about a path from the OS
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).
215 int try_file_query_root( file_info_t
* const info
)
217 WIN32_FILE_ATTRIBUTE_DATA fileData
;
219 char const * const pathstr
= object_str( info
->name
);
225 else if ( pathstr
[ 0 ] == '\\' && ! pathstr
[ 1 ] )
230 else if ( pathstr
[ 1 ] == ':' )
232 if ( !pathstr
[ 2 ] || ( pathstr
[ 2 ] == '\\' && !pathstr
[ 3 ] ) )
234 buf
[ 0 ] = pathstr
[ 0 ];
249 /* We have a root path */
250 if ( !GetFileAttributesExA( buf
, GetFileExInfoStandard
, &fileData
) )
255 timestamp_clear( &info
->time
);
259 info
->is_dir
= fileData
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
;
260 info
->is_file
= !info
->is_dir
;
262 timestamp_from_filetime( &info
->time
, &fileData
.ftLastWriteTime
);
267 void file_query_( file_info_t
* const info
)
269 char const * const pathstr
= object_str( info
->name
);
272 file_info_t
* parent_info
;
274 if ( try_file_query_root( info
) )
277 if ( ( dir
= strrchr( pathstr
, '\\' ) ) )
279 parent
= object_new_range( pathstr
, dir
- pathstr
);
283 parent
= object_copy( constant_empty
);
285 parent_info
= file_query( parent
);
286 object_free( parent
);
287 if ( !parent_info
|| !parent_info
->is_dir
)
292 timestamp_clear( &info
->time
);
299 timestamp_clear( &info
->time
);
300 if ( list_empty( parent_info
->files
) )
301 file_collect_dir_content_( parent_info
);
307 * file_supported_fmt_resolution() - file modification timestamp resolution
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.
313 * Does not take into consideration any OS or file system related restrictions.
315 * Return value 0 indicates that any value supported by the OS is also supported
319 void file_supported_fmt_resolution( timestamp
* const t
)
321 /* On Windows we support nano-second file modification timestamp resolution,
322 * just the same as the Windows OS itself.
324 timestamp_init( t
, 0, 0 );
329 * file_archscan() - scan an archive for files
332 /* Straight from SunOS */
334 #define ARMAG "!<arch>\n"
351 #define SARHDR sizeof( struct ar_hdr )
353 void file_archscan( char const * arch
, scanback func
, void * closure
)
355 OBJECT
* path
= object_new( arch
);
356 file_archive_info_t
* archive
= file_archive_query( path
);
360 if ( filelist_empty( archive
->members
) )
362 if ( file_collect_archive_content_( archive
) < 0 )
366 /* Report the collected archive content. */
368 FILELISTITER iter
= filelist_begin( archive
->members
);
369 FILELISTITER
const end
= filelist_end( archive
->members
);
370 char buf
[ MAXJPATH
];
372 for ( ; iter
!= end
; iter
= filelist_next( iter
) )
374 file_info_t
* member_file
= filelist_item( iter
);
376 /* Construct member path: 'archive-path(member-name)'
378 sprintf( buf
, "%s(%s)",
379 object_str( archive
->file
->name
),
380 object_str( member_file
->name
) );
382 OBJECT
* const member
= object_new( buf
);
383 (*func
)( closure
, member
, 1 /* time valid */, &member_file
->time
);
384 object_free( member
);
392 * file_archivescan_() - OS specific file_archivescan() implementation
395 void file_archivescan_( file_archive_info_t
* const archive
, archive_scanback func
,
402 * file_collect_archive_content_() - collects information about archive members
405 int file_collect_archive_content_( file_archive_info_t
* const archive
)
407 struct ar_hdr ar_hdr
;
408 char * string_table
= 0;
409 char buf
[ MAXJPATH
];
411 const char * path
= object_str( archive
->file
->name
);
412 int const fd
= open( path
, O_RDONLY
| O_BINARY
, 0 );
414 if ( ! filelist_empty( archive
->members
) ) filelist_free( archive
->members
);
419 if ( read( fd
, buf
, SARMAG
) != SARMAG
|| strncmp( ARMAG
, buf
, SARMAG
) )
427 if ( DEBUG_BINDSCAN
)
428 out_printf( "scan archive %s\n", path
);
430 while ( ( read( fd
, &ar_hdr
, SARHDR
) == SARHDR
) &&
431 !memcmp( ar_hdr
.ar_fmag
, ARFMAG
, SARFMAG
) )
438 sscanf( ar_hdr
.ar_date
, "%ld", &lar_date
);
439 sscanf( ar_hdr
.ar_size
, "%ld", &lar_size
);
441 lar_size
= ( lar_size
+ 1 ) & ~1;
443 if ( ar_hdr
.ar_name
[ 0 ] == '/' && ar_hdr
.ar_name
[ 1 ] == '/' )
445 /* This is the "string table" entry of the symbol table, holding
446 * filename strings longer than 15 characters, i.e. those that do
447 * not fit into ar_name.
449 string_table
= (char*)BJAM_MALLOC_ATOMIC( lar_size
+ 1 );
450 if ( read( fd
, string_table
, lar_size
) != lar_size
)
451 out_printf( "error reading string table\n" );
452 string_table
[ lar_size
] = '\0';
453 offset
+= SARHDR
+ lar_size
;
456 else if ( ar_hdr
.ar_name
[ 0 ] == '/' && ar_hdr
.ar_name
[ 1 ] != ' ' )
458 /* Long filenames are recognized by "/nnnn" where nnnn is the
459 * string's offset in the string table represented in ASCII
462 name
= string_table
+ atoi( ar_hdr
.ar_name
+ 1 );
463 for ( endname
= name
; *endname
&& *endname
!= '\n'; ++endname
);
468 name
= ar_hdr
.ar_name
;
469 endname
= name
+ sizeof( ar_hdr
.ar_name
);
472 /* strip trailing white-space, slashes, and backslashes */
474 while ( endname
-- > name
)
475 if ( !isspace( *endname
) && ( *endname
!= '\\' ) && ( *endname
!=
480 /* strip leading directory names, an NT specialty */
483 if ( (c
= strrchr( name
, '/' )) != nullptr )
485 if ( (c
= strrchr( name
, '\\' )) != nullptr )
489 sprintf( buf
, "%.*s", int(endname
- name
), name
);
491 if ( strcmp( buf
, "") != 0 )
493 file_info_t
* member
= 0;
495 /* NT static libraries appear to store the objects in a sequence
496 * reverse to the order in which they were inserted.
497 * Here we reverse the stored sequence by pushing members to front of
498 * member file list to get the intended members order.
500 archive
->members
= filelist_push_front( archive
->members
, object_new( buf
) );
501 member
= filelist_front( archive
->members
);
505 timestamp_init( &member
->time
, (time_t)lar_date
, 0 );
508 offset
+= SARHDR
+ lar_size
;
509 lseek( fd
, offset
, 0 );