| /* |
| * 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; |
| } |