blob: dadaef82edf710ffed4af886db6e2cbc45090aa9 [file] [log] [blame]
/*
* 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 http://www.boost.org/LICENSE_1_0.txt)
*/
/*
* filesys.c - OS independant file system manipulation support
*
* External routines:
* file_build1() - construct a path string based on PATHNAME information
* file_dirscan() - scan a directory for files
* file_done() - module cleanup called on shutdown
* file_info() - return cached information about a path
* file_is_file() - return whether a path identifies an existing file
* file_query() - get cached information about a path, query the OS if
* needed
* file_remove_atexit() - schedule a path to be removed on program exit
* file_time() - get a file timestamp
*
* External routines - utilites for OS specific module implementations:
* file_query_posix_() - query information about a path using POSIX stat()
*
* Internal routines:
* file_dirscan_impl() - no-profiling worker for file_dirscan()
*/
#include "jam.h"
#include "filesys.h"
#include "lists.h"
#include "object.h"
#include "pathsys.h"
#include "strings.h"
#include <assert.h>
#include <sys/stat.h>
/* Internal OS specific implementation details - have names ending with an
* underscore and are expected to be implemented in an OS specific fileXXX.c
* module.
*/
void file_dirscan_( file_info_t * const dir, scanback func, void * closure );
int file_collect_dir_content_( file_info_t * const dir );
void file_query_( file_info_t * const );
static void file_dirscan_impl( OBJECT * dir, scanback func, void * closure );
static void free_file_info( void * xfile, void * data );
static void remove_files_atexit( void );
static struct hash * filecache_hash;
/*
* file_build1() - construct a path string based on PATHNAME information
*/
void file_build1( PATHNAME * const f, string * file )
{
if ( DEBUG_SEARCH )
{
printf( "build file: " );
if ( f->f_root.len )
printf( "root = '%.*s' ", f->f_root.len, f->f_root.ptr );
if ( f->f_dir.len )
printf( "dir = '%.*s' ", f->f_dir.len, f->f_dir.ptr );
if ( f->f_base.len )
printf( "base = '%.*s' ", f->f_base.len, f->f_base.ptr );
printf( "\n" );
}
/* Start with the grist. If the current grist is not surrounded by <>'s, add
* them.
*/
if ( f->f_grist.len )
{
if ( f->f_grist.ptr[ 0 ] != '<' )
string_push_back( file, '<' );
string_append_range(
file, f->f_grist.ptr, f->f_grist.ptr + f->f_grist.len );
if ( file->value[ file->size - 1 ] != '>' )
string_push_back( file, '>' );
}
}
/*
* file_dirscan() - scan a directory for files
*/
void file_dirscan( OBJECT * dir, scanback func, void * closure )
{
PROFILE_ENTER( FILE_DIRSCAN );
file_dirscan_impl( dir, func, closure );
PROFILE_EXIT( FILE_DIRSCAN );
}
/*
* file_done() - module cleanup called on shutdown
*/
void file_done()
{
remove_files_atexit();
if ( filecache_hash )
{
hashenumerate( filecache_hash, free_file_info, (void *)0 );
hashdone( filecache_hash );
}
}
/*
* file_info() - return cached information about a path
*
* Returns a default initialized structure containing only the path's normalized
* name in case this is the first time this file system entity has been
* referenced.
*/
file_info_t * file_info( OBJECT * const path, int * found )
{
OBJECT * const path_key = path_as_key( path );
file_info_t * finfo;
if ( !filecache_hash )
filecache_hash = hashinit( sizeof( file_info_t ), "file_info" );
finfo = (file_info_t *)hash_insert( filecache_hash, path_key, found );
if ( !*found )
{
finfo->name = path_key;
finfo->files = L0;
}
else
object_free( path_key );
return finfo;
}
/*
* file_is_file() - return whether a path identifies an existing file
*/
int file_is_file( OBJECT * const path )
{
file_info_t const * const ff = file_query( path );
return ff ? ff->is_file : -1;
}
/*
* file_time() - get a file timestamp
*/
int file_time( OBJECT * const path, timestamp * const time )
{
file_info_t const * const ff = file_query( path );
if ( !ff ) return -1;
timestamp_copy( time, &ff->time );
return 0;
}
/*
* file_query() - get cached information about a path, query the OS if needed
*
* Returns 0 in case querying the OS about the given path fails, e.g. because
* the path does not reference an existing file system object.
*/
file_info_t * file_query( OBJECT * const path )
{
/* FIXME: Add tracking for disappearing files (i.e. those that can not be
* detected by stat() even though they had been detected successfully
* before) and see how they should be handled in the rest of Boost Jam code.
* Possibly allow Jamfiles to specify some files as 'volatile' which would
* make Boost Jam avoid caching information about those files and instead
* ask the OS about them every time.
*/
int found;
file_info_t * const ff = file_info( path, &found );
if ( !found )
{
file_query_( ff );
if ( ff->exists )
{
/* Set the path's timestamp to 1 in case it is 0 or undetected to avoid
* confusion with non-existing paths.
*/
if ( timestamp_empty( &ff->time ) )
timestamp_init( &ff->time, 1, 0 );
}
}
if ( !ff->exists )
{
return 0;
}
return ff;
}
/*
* file_query_posix_() - query information about a path using POSIX stat()
*
* Fallback file_query_() implementation for OS specific modules.
*
* Note that the Windows POSIX stat() function implementation suffers from
* several issues:
* * Does not support file timestamps with resolution finer than 1 second,
* meaning it can not be used to detect file timestamp changes of less than
* 1 second. One possible consequence is that some fast-paced touch commands
* (such as those done by Boost Build's internal testing system if it does
* not do some extra waiting) will not be detected correctly by the build
* system.
* * Returns file modification times automatically adjusted for daylight
* savings time even though daylight savings time should have nothing to do
* with internal time representation.
*/
void file_query_posix_( file_info_t * const info )
{
struct stat statbuf;
char const * const pathstr = object_str( info->name );
char const * const pathspec = *pathstr ? pathstr : ".";
if ( stat( pathspec, &statbuf ) < 0 )
{
info->is_file = 0;
info->is_dir = 0;
info->exists = 0;
timestamp_clear( &info->time );
}
else
{
info->is_file = statbuf.st_mode & S_IFREG ? 1 : 0;
info->is_dir = statbuf.st_mode & S_IFDIR ? 1 : 0;
info->exists = 1;
timestamp_init( &info->time, statbuf.st_mtime, 0 );
}
}
/*
* file_remove_atexit() - schedule a path to be removed on program exit
*/
static LIST * files_to_remove = L0;
void file_remove_atexit( OBJECT * const path )
{
files_to_remove = list_push_back( files_to_remove, object_copy( path ) );
}
/*
* file_dirscan_impl() - no-profiling worker for file_dirscan()
*/
static void file_dirscan_impl( OBJECT * dir, scanback func, void * closure )
{
file_info_t * const d = file_query( dir );
if ( !d || !d->is_dir )
return;
/* Lazy collect the directory content information. */
if ( list_empty( d->files ) )
{
if ( DEBUG_BINDSCAN )
printf( "scan directory %s\n", object_str( d->name ) );
if ( file_collect_dir_content_( d ) < 0 )
return;
}
/* OS specific part of the file_dirscan operation. */
file_dirscan_( d, func, closure );
/* Report the collected directory content. */
{
LISTITER iter = list_begin( d->files );
LISTITER const end = list_end( d->files );
for ( ; iter != end; iter = list_next( iter ) )
{
OBJECT * const path = list_item( iter );
file_info_t const * const ffq = file_query( path );
/* Using a file name read from a file_info_t structure allows OS
* specific implementations to store some kind of a normalized file
* name there. Using such a normalized file name then allows us to
* correctly recognize different file paths actually identifying the
* same file. For instance, an implementation may:
* - convert all file names internally to lower case on a case
* insensitive file system
* - convert the NTFS paths to their long path variants as that
* file system each file system entity may have a long and a
* short path variant thus allowing for many different path
* strings identifying the same file.
*/
(*func)( closure, ffq->name, 1 /* stat()'ed */, &ffq->time );
}
}
}
static void free_file_info( void * xfile, void * data )
{
file_info_t * const file = (file_info_t *)xfile;
object_free( file->name );
list_free( file->files );
}
static void remove_files_atexit( void )
{
LISTITER iter = list_begin( files_to_remove );
LISTITER const end = list_end( files_to_remove );
for ( ; iter != end; iter = list_next( iter ) )
remove( object_str( list_item( iter ) ) );
list_free( files_to_remove );
files_to_remove = L0;
}