blob: 00dcc49b363fda3eb0036ddf87bfb3968f7a37ed [file] [log] [blame]
/*
* Copyright 1993, 1995 Christopher Seiwald.
*
* This file is part of Jam - see jam.c for Copyright information.
*/
/* This file is ALSO:
* Copyright 2001-2004 David Abrahams.
* Copyright 2005 Rene Rivera.
* Distributed under the Boost Software License, Version 1.0.
* (See accompanying file LICENSE_1_0.txt or copy at
* http://www.boost.org/LICENSE_1_0.txt)
*/
/*
* filent.c - scan directories and archives on NT
*
* External routines:
* file_archscan() - scan an archive for files
* file_mkdir() - create a directory
* file_supported_fmt_resolution() - file modification timestamp resolution
*
* External routines called only via routines in filesys.c:
* file_collect_dir_content_() - collects directory content information
* file_dirscan_() - OS specific file_dirscan() implementation
* file_query_() - query information about a path from the OS
*/
#include "jam.h"
#ifdef OS_NT
#include "filesys.h"
#include "object.h"
#include "pathsys.h"
#include "strings.h"
#ifdef __BORLANDC__
# undef FILENAME /* cpp namespace collision */
#endif
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <assert.h>
#include <ctype.h>
#include <direct.h>
#include <io.h>
/*
* file_collect_dir_content_() - collects directory content information
*/
int file_collect_dir_content_( file_info_t * const d )
{
PATHNAME f;
string pathspec[ 1 ];
string pathname[ 1 ];
LIST * files = L0;
int d_length;
assert( d );
assert( d->is_dir );
assert( list_empty( d->files ) );
d_length = strlen( object_str( d->name ) );
memset( (char *)&f, '\0', sizeof( f ) );
f.f_dir.ptr = object_str( d->name );
f.f_dir.len = d_length;
/* Prepare file search specification for the FindXXX() Windows API. */
if ( !d_length )
string_copy( pathspec, ".\\*" );
else
{
/* We can not simply assume the given folder name will never include its
* trailing path separator or otherwise we would not support the Windows
* root folder specified without its drive letter, i.e. '\'.
*/
char const trailingChar = object_str( d->name )[ d_length - 1 ] ;
string_copy( pathspec, object_str( d->name ) );
if ( ( trailingChar != '\\' ) && ( trailingChar != '/' ) )
string_append( pathspec, "\\" );
string_append( pathspec, "*" );
}
/* The following code for collecting information about all files in a folder
* needs to be kept synchronized with how the file_query() operation is
* implemented (collects information about a single file).
*/
{
/* FIXME: Avoid duplicate FindXXX Windows API calls here and in the code
* determining a normalized path.
*/
WIN32_FIND_DATA finfo;
HANDLE const findHandle = FindFirstFileA( pathspec->value, &finfo );
if ( findHandle == INVALID_HANDLE_VALUE )
{
string_free( pathspec );
return -1;
}
string_new( pathname );
do
{
OBJECT * pathname_obj;
f.f_base.ptr = finfo.cFileName;
f.f_base.len = strlen( finfo.cFileName );
string_truncate( pathname, 0 );
path_build( &f, pathname );
pathname_obj = object_new( pathname->value );
path_register_key( pathname_obj );
files = list_push_back( files, pathname_obj );
{
int found;
file_info_t * const ff = file_info( pathname_obj, &found );
ff->is_dir = finfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
ff->is_file = !ff->is_dir;
ff->exists = 1;
timestamp_from_filetime( &ff->time, &finfo.ftLastWriteTime );
// Use the timestamp of the link target, not the link itself
// (i.e. stat instead of lstat)
if ( finfo.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT )
{
HANDLE hLink = CreateFileA( pathname->value, 0, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL );
BY_HANDLE_FILE_INFORMATION target_finfo[ 1 ];
if ( hLink != INVALID_HANDLE_VALUE && GetFileInformationByHandle( hLink, target_finfo ) )
{
ff->is_file = target_finfo->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ? 0 : 1;
ff->is_dir = target_finfo->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ? 1 : 0;
timestamp_from_filetime( &ff->time, &target_finfo->ftLastWriteTime );
}
}
}
}
while ( FindNextFile( findHandle, &finfo ) );
FindClose( findHandle );
}
string_free( pathname );
string_free( pathspec );
d->files = files;
return 0;
}
/*
* file_dirscan_() - OS specific file_dirscan() implementation
*/
void file_dirscan_( file_info_t * const d, scanback func, void * closure )
{
assert( d );
assert( d->is_dir );
/* Special case \ or d:\ : enter it */
{
char const * const name = object_str( d->name );
if ( name[ 0 ] == '\\' && !name[ 1 ] )
{
(*func)( closure, d->name, 1 /* stat()'ed */, &d->time );
}
else if ( name[ 0 ] && name[ 1 ] == ':' && name[ 2 ] && !name[ 3 ] )
{
/* We have just entered a 3-letter drive name spelling (with a
* trailing slash), into the hash table. Now enter its two-letter
* variant, without the trailing slash, so that if we try to check
* whether "c:" exists, we hit it.
*
* Jam core has workarounds for that. Given:
* x = c:\whatever\foo ;
* p = $(x:D) ;
* p2 = $(p:D) ;
* There will be no trailing slash in $(p), but there will be one in
* $(p2). But, that seems rather fragile.
*/
OBJECT * const dir_no_slash = object_new_range( name, 2 );
(*func)( closure, d->name, 1 /* stat()'ed */, &d->time );
(*func)( closure, dir_no_slash, 1 /* stat()'ed */, &d->time );
object_free( dir_no_slash );
}
}
}
/*
* file_mkdir() - create a directory
*/
int file_mkdir( char const * const path )
{
return _mkdir( path );
}
/*
* file_query_() - query information about a path from the OS
*
* The following code for collecting information about a single file needs to be
* kept synchronized with how the file_collect_dir_content_() operation is
* implemented (collects information about all files in a folder).
*/
int try_file_query_root( file_info_t * const info )
{
WIN32_FILE_ATTRIBUTE_DATA fileData;
char buf[ 4 ];
char const * const pathstr = object_str( info->name );
if ( !pathstr[ 0 ] )
{
buf[ 0 ] = '.';
buf[ 1 ] = 0;
}
else if ( pathstr[ 0 ] == '\\' && ! pathstr[ 1 ] )
{
buf[ 0 ] = '\\';
buf[ 1 ] = '\0';
}
else if ( pathstr[ 1 ] == ':' )
{
if ( !pathstr[ 2 ] )
{
}
else if ( !pathstr[ 2 ] || ( pathstr[ 2 ] == '\\' && !pathstr[ 3 ] ) )
{
buf[ 0 ] = pathstr[ 0 ];
buf[ 1 ] = ':';
buf[ 2 ] = '\\';
buf[ 3 ] = '\0';
}
else
{
return 0;
}
}
else
{
return 0;
}
/* We have a root path */
if ( !GetFileAttributesExA( buf, GetFileExInfoStandard, &fileData ) )
{
info->is_dir = 0;
info->is_file = 0;
info->exists = 0;
timestamp_clear( &info->time );
}
else
{
info->is_dir = fileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
info->is_file = !info->is_dir;
info->exists = 1;
timestamp_from_filetime( &info->time, &fileData.ftLastWriteTime );
}
return 1;
}
void file_query_( file_info_t * const info )
{
char const * const pathstr = object_str( info->name );
const char * dir;
OBJECT * parent;
file_info_t * parent_info;
if ( try_file_query_root( info ) )
return;
if ( ( dir = strrchr( pathstr, '\\' ) ) )
{
parent = object_new_range( pathstr, dir - pathstr );
}
else
{
parent = object_copy( constant_empty );
}
parent_info = file_query( parent );
object_free( parent );
if ( !parent_info || !parent_info->is_dir )
{
info->is_dir = 0;
info->is_file = 0;
info->exists = 0;
timestamp_clear( &info->time );
}
else
{
info->is_dir = 0;
info->is_file = 0;
info->exists = 0;
timestamp_clear( &info->time );
if ( list_empty( parent_info->files ) )
file_collect_dir_content_( parent_info );
}
}
/*
* file_supported_fmt_resolution() - file modification timestamp resolution
*
* Returns the minimum file modification timestamp resolution supported by this
* Boost Jam implementation. File modification timestamp changes of less than
* the returned value might not be recognized.
*
* Does not take into consideration any OS or file system related restrictions.
*
* Return value 0 indicates that any value supported by the OS is also supported
* here.
*/
void file_supported_fmt_resolution( timestamp * const t )
{
/* On Windows we support nano-second file modification timestamp resolution,
* just the same as the Windows OS itself.
*/
timestamp_init( t, 0, 0 );
}
/*
* file_archscan() - scan an archive for files
*/
/* Straight from SunOS */
#define ARMAG "!<arch>\n"
#define SARMAG 8
#define ARFMAG "`\n"
struct ar_hdr
{
char ar_name[ 16 ];
char ar_date[ 12 ];
char ar_uid[ 6 ];
char ar_gid[ 6 ];
char ar_mode[ 8 ];
char ar_size[ 10 ];
char ar_fmag[ 2 ];
};
#define SARFMAG 2
#define SARHDR sizeof( struct ar_hdr )
void file_archscan( char const * archive, scanback func, void * closure )
{
struct ar_hdr ar_hdr;
char * string_table = 0;
char buf[ MAXJPATH ];
long offset;
int const fd = open( archive, O_RDONLY | O_BINARY, 0 );
if ( fd < 0 )
return;
if ( read( fd, buf, SARMAG ) != SARMAG || strncmp( ARMAG, buf, SARMAG ) )
{
close( fd );
return;
}
offset = SARMAG;
if ( DEBUG_BINDSCAN )
printf( "scan archive %s\n", archive );
while ( ( read( fd, &ar_hdr, SARHDR ) == SARHDR ) &&
!memcmp( ar_hdr.ar_fmag, ARFMAG, SARFMAG ) )
{
long lar_date;
long lar_size;
char * name = 0;
char * endname;
sscanf( ar_hdr.ar_date, "%ld", &lar_date );
sscanf( ar_hdr.ar_size, "%ld", &lar_size );
lar_size = ( lar_size + 1 ) & ~1;
if ( ar_hdr.ar_name[ 0 ] == '/' && ar_hdr.ar_name[ 1 ] == '/' )
{
/* This is the "string table" entry of the symbol table, holding
* filename strings longer than 15 characters, i.e. those that do
* not fit into ar_name.
*/
string_table = BJAM_MALLOC_ATOMIC( lar_size + 1 );
if ( read( fd, string_table, lar_size ) != lar_size )
printf( "error reading string table\n" );
string_table[ lar_size ] = '\0';
offset += SARHDR + lar_size;
continue;
}
else if ( ar_hdr.ar_name[ 0 ] == '/' && ar_hdr.ar_name[ 1 ] != ' ' )
{
/* Long filenames are recognized by "/nnnn" where nnnn is the
* string's offset in the string table represented in ASCII
* decimals.
*/
name = string_table + atoi( ar_hdr.ar_name + 1 );
for ( endname = name; *endname && *endname != '\n'; ++endname );
}
else
{
/* normal name */
name = ar_hdr.ar_name;
endname = name + sizeof( ar_hdr.ar_name );
}
/* strip trailing white-space, slashes, and backslashes */
while ( endname-- > name )
if ( !isspace( *endname ) && ( *endname != '\\' ) && ( *endname !=
'/' ) )
break;
*++endname = 0;
/* strip leading directory names, an NT specialty */
{
char * c;
if ( c = strrchr( name, '/' ) )
name = c + 1;
if ( c = strrchr( name, '\\' ) )
name = c + 1;
}
sprintf( buf, "%s(%.*s)", archive, endname - name, name );
{
OBJECT * const member = object_new( buf );
timestamp time;
timestamp_init( &time, (time_t)lar_date, 0 );
(*func)( closure, member, 1 /* time valid */, &time );
object_free( member );
}
offset += SARHDR + lar_size;
lseek( fd, offset, 0 );
}
close( fd );
}
#endif /* OS_NT */