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
38 # undef FILENAME /* cpp namespace collision */
41 #define WIN32_LEAN_AND_MEAN
51 * file_collect_dir_content_() - collects directory content information
54 int file_collect_dir_content_( file_info_t * const d )
64 assert( list_empty( d->files ) );
66 d_length = strlen( object_str( d->name ) );
68 memset( (char *)&f, '\0', sizeof( f ) );
69 f.f_dir.ptr = object_str( d->name );
70 f.f_dir.len = d_length;
72 /* Prepare file search specification for the FindXXX() Windows API. */
74 string_copy( pathspec, ".\\*" );
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. '\'.
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, "*" );
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).
93 /* FIXME: Avoid duplicate FindXXX Windows API calls here and in the code
94 * determining a normalized path.
96 WIN32_FIND_DATA finfo;
97 HANDLE const findHandle = FindFirstFileA( pathspec->value, &finfo );
98 if ( findHandle == INVALID_HANDLE_VALUE )
100 string_free( pathspec );
104 string_new( pathname );
107 OBJECT * pathname_obj;
109 f.f_base.ptr = finfo.cFileName;
110 f.f_base.len = strlen( finfo.cFileName );
112 string_truncate( pathname, 0 );
113 path_build( &f, pathname );
115 pathname_obj = object_new( pathname->value );
116 path_register_key( pathname_obj );
117 files = list_push_back( files, pathname_obj );
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;
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 )
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 ) )
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 );
140 while ( FindNextFile( findHandle, &finfo ) );
142 FindClose( findHandle );
145 string_free( pathname );
146 string_free( pathspec );
154 * file_dirscan_() - OS specific file_dirscan() implementation
157 void file_dirscan_( file_info_t * const d, scanback func, void * closure )
162 /* Special case \ or d:\ : enter it */
164 char const * const name = object_str( d->name );
165 if ( name[ 0 ] == '\\' && !name[ 1 ] )
167 (*func)( closure, d->name, 1 /* stat()'ed */, &d->time );
169 else if ( name[ 0 ] && name[ 1 ] == ':' && name[ 2 ] && !name[ 3 ] )
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.
176 * Jam core has workarounds for that. Given:
177 * x = c:\whatever\foo ;
180 * There will be no trailing slash in $(p), but there will be one in
181 * $(p2). But, that seems rather fragile.
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 );
193 * file_mkdir() - create a directory
196 int file_mkdir( char const * const path )
198 return _mkdir( path );
203 * file_query_() - query information about a path from the OS
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).
210 int try_file_query_root( file_info_t * const info )
212 WIN32_FILE_ATTRIBUTE_DATA fileData;
214 char const * const pathstr = object_str( info->name );
220 else if ( pathstr[ 0 ] == '\\' && ! pathstr[ 1 ] )
225 else if ( pathstr[ 1 ] == ':' )
230 else if ( !pathstr[ 2 ] || ( pathstr[ 2 ] == '\\' && !pathstr[ 3 ] ) )
232 buf[ 0 ] = pathstr[ 0 ];
247 /* We have a root path */
248 if ( !GetFileAttributesExA( buf, GetFileExInfoStandard, &fileData ) )
253 timestamp_clear( &info->time );
257 info->is_dir = fileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
258 info->is_file = !info->is_dir;
260 timestamp_from_filetime( &info->time, &fileData.ftLastWriteTime );
265 void file_query_( file_info_t * const info )
267 char const * const pathstr = object_str( info->name );
270 file_info_t * parent_info;
272 if ( try_file_query_root( info ) )
275 if ( ( dir = strrchr( pathstr, '\\' ) ) )
277 parent = object_new_range( pathstr, dir - pathstr );
281 parent = object_copy( constant_empty );
283 parent_info = file_query( parent );
284 object_free( parent );
285 if ( !parent_info || !parent_info->is_dir )
290 timestamp_clear( &info->time );
297 timestamp_clear( &info->time );
298 if ( list_empty( parent_info->files ) )
299 file_collect_dir_content_( parent_info );
305 * file_supported_fmt_resolution() - file modification timestamp resolution
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.
311 * Does not take into consideration any OS or file system related restrictions.
313 * Return value 0 indicates that any value supported by the OS is also supported
317 void file_supported_fmt_resolution( timestamp * const t )
319 /* On Windows we support nano-second file modification timestamp resolution,
320 * just the same as the Windows OS itself.
322 timestamp_init( t, 0, 0 );
327 * file_archscan() - scan an archive for files
330 /* Straight from SunOS */
332 #define ARMAG "!<arch>\n"
349 #define SARHDR sizeof( struct ar_hdr )
351 void file_archscan( char const * archive, scanback func, void * closure )
353 struct ar_hdr ar_hdr;
354 char * string_table = 0;
355 char buf[ MAXJPATH ];
357 int const fd = open( archive, O_RDONLY | O_BINARY, 0 );
362 if ( read( fd, buf, SARMAG ) != SARMAG || strncmp( ARMAG, buf, SARMAG ) )
370 if ( DEBUG_BINDSCAN )
371 printf( "scan archive %s\n", archive );
373 while ( ( read( fd, &ar_hdr, SARHDR ) == SARHDR ) &&
374 !memcmp( ar_hdr.ar_fmag, ARFMAG, SARFMAG ) )
381 sscanf( ar_hdr.ar_date, "%ld", &lar_date );
382 sscanf( ar_hdr.ar_size, "%ld", &lar_size );
384 lar_size = ( lar_size + 1 ) & ~1;
386 if ( ar_hdr.ar_name[ 0 ] == '/' && ar_hdr.ar_name[ 1 ] == '/' )
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.
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;
399 else if ( ar_hdr.ar_name[ 0 ] == '/' && ar_hdr.ar_name[ 1 ] != ' ' )
401 /* Long filenames are recognized by "/nnnn" where nnnn is the
402 * string's offset in the string table represented in ASCII
405 name = string_table + atoi( ar_hdr.ar_name + 1 );
406 for ( endname = name; *endname && *endname != '\n'; ++endname );
411 name = ar_hdr.ar_name;
412 endname = name + sizeof( ar_hdr.ar_name );
415 /* strip trailing white-space, slashes, and backslashes */
417 while ( endname-- > name )
418 if ( !isspace( *endname ) && ( *endname != '\\' ) && ( *endname !=
423 /* strip leading directory names, an NT specialty */
426 if ( c = strrchr( name, '/' ) )
428 if ( c = strrchr( name, '\\' ) )
432 sprintf( buf, "%s(%.*s)", archive, endname - name, name );
434 OBJECT * const member = object_new( buf );
436 timestamp_init( &time, (time_t)lar_date, 0 );
437 (*func)( closure, member, 1 /* time valid */, &time );
438 object_free( member );
441 offset += SARHDR + lar_size;
442 lseek( fd, offset, 0 );