• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  *  Copyright 2001-2004 David Abrahams.
3  *  Copyright 2005 Rene Rivera.
4  *  Distributed under the Boost Software License, Version 1.0.
5  *  (See accompanying file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt)
6  */
7 
8 /*
9  * filesys.c - OS independent file system manipulation support
10  *
11  * External routines:
12  *  file_build1()        - construct a path string based on PATHNAME information
13  *  file_dirscan()       - scan a directory for files
14  *  file_done()          - module cleanup called on shutdown
15  *  file_info()          - return cached information about a path
16  *  file_is_file()       - return whether a path identifies an existing file
17  *  file_query()         - get cached information about a path, query the OS if
18  *                         needed
19  *  file_remove_atexit() - schedule a path to be removed on program exit
20  *  file_time()          - get a file timestamp
21  *
22  * External routines - utilities for OS specific module implementations:
23  *  file_query_posix_()  - query information about a path using POSIX stat()
24  *
25  * Internal routines:
26  *  file_dirscan_impl()  - no-profiling worker for file_dirscan()
27  */
28 
29 
30 #include "jam.h"
31 #include "filesys.h"
32 
33 #include "lists.h"
34 #include "object.h"
35 #include "pathsys.h"
36 #include "jam_strings.h"
37 #include "output.h"
38 
39 #include <assert.h>
40 #include <sys/stat.h>
41 
42 
43 /* Internal OS specific implementation details - have names ending with an
44  * underscore and are expected to be implemented in an OS specific fileXXX.c
45  * module.
46  */
47 void file_dirscan_( file_info_t * const dir, scanback func, void * closure );
48 int file_collect_dir_content_( file_info_t * const dir );
49 void file_query_( file_info_t * const );
50 
51 void file_archivescan_( file_archive_info_t * const archive, archive_scanback func,
52                         void * closure );
53 int file_collect_archive_content_( file_archive_info_t * const archive );
54 void file_archive_query_( file_archive_info_t * const );
55 
56 static void file_archivescan_impl( OBJECT * path, archive_scanback func,
57                                    void * closure );
58 static void file_dirscan_impl( OBJECT * dir, scanback func, void * closure );
59 static void free_file_archive_info( void * xarchive, void * data );
60 static void free_file_info( void * xfile, void * data );
61 
62 static void remove_files_atexit( void );
63 
64 
65 static struct hash * filecache_hash;
66 static struct hash * archivecache_hash;
67 
68 
69 /*
70  * file_archive_info() - return cached information about an archive
71  *
72  * Returns a default initialized structure containing only queried file's info
73  * in case this is the first time this file system entity has been
74  * referenced.
75  */
76 
file_archive_info(OBJECT * const path,int * found)77 file_archive_info_t * file_archive_info( OBJECT * const path, int * found )
78 {
79     OBJECT * const path_key = path_as_key( path );
80     file_archive_info_t * archive;
81 
82     if ( !archivecache_hash )
83         archivecache_hash = hashinit( sizeof( file_archive_info_t ),
84             "file_archive_info" );
85 
86     archive = (file_archive_info_t *)hash_insert( archivecache_hash, path_key,
87             found );
88 
89     if ( !*found )
90     {
91         archive->name = path_key;
92         archive->file = 0;
93         archive->members = FL0;
94     }
95     else
96         object_free( path_key );
97 
98     return archive;
99 }
100 
101 
102 /*
103  * file_archive_query() - get cached information about a archive file path
104  *
105  * Returns 0 in case querying the OS about the given path fails, e.g. because
106  * the path does not reference an existing file system object.
107  */
108 
file_archive_query(OBJECT * const path)109 file_archive_info_t * file_archive_query( OBJECT * const path )
110 {
111     int found;
112     file_archive_info_t * const archive = file_archive_info( path, &found );
113     file_info_t * file = file_query( path );
114 
115     if ( !( file && file->is_file ) )
116     {
117         return 0;
118     }
119 
120     archive->file = file;
121 
122 
123     return archive;
124 }
125 
126 
127 
128 /*
129  * file_archivescan() - scan an archive for members
130  */
131 
file_archivescan(OBJECT * path,archive_scanback func,void * closure)132 void file_archivescan( OBJECT * path, archive_scanback func, void * closure )
133 {
134     PROFILE_ENTER( FILE_ARCHIVESCAN );
135     file_archivescan_impl( path, func, closure );
136     PROFILE_EXIT( FILE_ARCHIVESCAN );
137 }
138 
139 
140 /*
141  * file_build1() - construct a path string based on PATHNAME information
142  */
143 
file_build1(PATHNAME * const f,string * file)144 void file_build1( PATHNAME * const f, string * file )
145 {
146     if ( DEBUG_SEARCH )
147     {
148         out_printf( "build file: " );
149         if ( f->f_root.len )
150             out_printf( "root = '%.*s' ", f->f_root.len, f->f_root.ptr );
151         if ( f->f_dir.len )
152             out_printf( "dir = '%.*s' ", f->f_dir.len, f->f_dir.ptr );
153         if ( f->f_base.len )
154             out_printf( "base = '%.*s' ", f->f_base.len, f->f_base.ptr );
155         out_printf( "\n" );
156     }
157 
158     /* Start with the grist. If the current grist is not surrounded by <>'s, add
159      * them.
160      */
161     if ( f->f_grist.len )
162     {
163         if ( f->f_grist.ptr[ 0 ] != '<' )
164             string_push_back( file, '<' );
165         string_append_range(
166             file, f->f_grist.ptr, f->f_grist.ptr + f->f_grist.len );
167         if ( file->value[ file->size - 1 ] != '>' )
168             string_push_back( file, '>' );
169     }
170 }
171 
172 
173 /*
174  * file_dirscan() - scan a directory for files
175  */
176 
file_dirscan(OBJECT * dir,scanback func,void * closure)177 void file_dirscan( OBJECT * dir, scanback func, void * closure )
178 {
179     PROFILE_ENTER( FILE_DIRSCAN );
180     file_dirscan_impl( dir, func, closure );
181     PROFILE_EXIT( FILE_DIRSCAN );
182 }
183 
184 
185 /*
186  * file_done() - module cleanup called on shutdown
187  */
188 
file_done()189 void file_done()
190 {
191     remove_files_atexit();
192     if ( filecache_hash )
193     {
194         hashenumerate( filecache_hash, free_file_info, (void *)0 );
195         hashdone( filecache_hash );
196     }
197 
198     if ( archivecache_hash )
199     {
200         hashenumerate( archivecache_hash, free_file_archive_info, (void *)0 );
201         hashdone( archivecache_hash );
202     }
203 }
204 
205 
206 /*
207  * file_info() - return cached information about a path
208  *
209  * Returns a default initialized structure containing only the path's normalized
210  * name in case this is the first time this file system entity has been
211  * referenced.
212  */
213 
file_info(OBJECT * const path,int * found)214 file_info_t * file_info( OBJECT * const path, int * found )
215 {
216     OBJECT * const path_key = path_as_key( path );
217     file_info_t * finfo;
218 
219     if ( !filecache_hash )
220         filecache_hash = hashinit( sizeof( file_info_t ), "file_info" );
221 
222     finfo = (file_info_t *)hash_insert( filecache_hash, path_key, found );
223     if ( !*found )
224     {
225         finfo->name = path_key;
226         finfo->files = L0;
227     }
228     else
229         object_free( path_key );
230 
231     return finfo;
232 }
233 
234 
235 /*
236  * file_is_file() - return whether a path identifies an existing file
237  */
238 
file_is_file(OBJECT * const path)239 int file_is_file( OBJECT * const path )
240 {
241     file_info_t const * const ff = file_query( path );
242     return ff ? ff->is_file : -1;
243 }
244 
245 
246 /*
247  * file_time() - get a file timestamp
248  */
249 
file_time(OBJECT * const path,timestamp * const time)250 int file_time( OBJECT * const path, timestamp * const time )
251 {
252     file_info_t const * const ff = file_query( path );
253     if ( !ff ) return -1;
254     timestamp_copy( time, &ff->time );
255     return 0;
256 }
257 
258 
259 /*
260  * file_query() - get cached information about a path, query the OS if needed
261  *
262  * Returns 0 in case querying the OS about the given path fails, e.g. because
263  * the path does not reference an existing file system object.
264  */
265 
file_query(OBJECT * const path)266 file_info_t * file_query( OBJECT * const path )
267 {
268     /* FIXME: Add tracking for disappearing files (i.e. those that can not be
269      * detected by stat() even though they had been detected successfully
270      * before) and see how they should be handled in the rest of Boost Jam code.
271      * Possibly allow Jamfiles to specify some files as 'volatile' which would
272      * make Boost Jam avoid caching information about those files and instead
273      * ask the OS about them every time.
274      */
275     int found;
276     file_info_t * const ff = file_info( path, &found );
277     if ( !found )
278     {
279         file_query_( ff );
280         if ( ff->exists )
281         {
282             /* Set the path's timestamp to 1 in case it is 0 or undetected to avoid
283              * confusion with non-existing paths.
284              */
285             if ( timestamp_empty( &ff->time ) )
286                 timestamp_init( &ff->time, 1, 0 );
287         }
288     }
289     if ( !ff->exists )
290     {
291         return 0;
292     }
293     return ff;
294 }
295 
296 #ifndef OS_NT
297 
298 /*
299  * file_query_posix_() - query information about a path using POSIX stat()
300  *
301  * Fallback file_query_() implementation for OS specific modules.
302  *
303  * Note that the Windows POSIX stat() function implementation suffers from
304  * several issues:
305  *   * Does not support file timestamps with resolution finer than 1 second,
306  *     meaning it can not be used to detect file timestamp changes of less than
307  *     1 second. One possible consequence is that some fast-paced touch commands
308  *     (such as those done by Boost Build's internal testing system if it does
309  *     not do some extra waiting) will not be detected correctly by the build
310  *     system.
311  *   * Returns file modification times automatically adjusted for daylight
312  *     savings time even though daylight savings time should have nothing to do
313  *     with internal time representation.
314  */
315 
file_query_posix_(file_info_t * const info)316 void file_query_posix_( file_info_t * const info )
317 {
318     struct stat statbuf;
319     char const * const pathstr = object_str( info->name );
320     char const * const pathspec = *pathstr ? pathstr : ".";
321 
322     if ( stat( pathspec, &statbuf ) < 0 )
323     {
324         info->is_file = 0;
325         info->is_dir = 0;
326         info->exists = 0;
327         timestamp_clear( &info->time );
328     }
329     else
330     {
331         info->is_file = statbuf.st_mode & S_IFREG ? 1 : 0;
332         info->is_dir = statbuf.st_mode & S_IFDIR ? 1 : 0;
333         info->exists = 1;
334 #if defined(_POSIX_VERSION) && _POSIX_VERSION >= 200809
335 #if defined(OS_MACOSX)
336         timestamp_init( &info->time, statbuf.st_mtimespec.tv_sec, statbuf.st_mtimespec.tv_nsec );
337 #else
338         timestamp_init( &info->time, statbuf.st_mtim.tv_sec, statbuf.st_mtim.tv_nsec );
339 #endif
340 #else
341         timestamp_init( &info->time, statbuf.st_mtime, 0 );
342 #endif
343     }
344 }
345 
346 /*
347  * file_supported_fmt_resolution() - file modification timestamp resolution
348  *
349  * Returns the minimum file modification timestamp resolution supported by this
350  * Boost Jam implementation. File modification timestamp changes of less than
351  * the returned value might not be recognized.
352  *
353  * Does not take into consideration any OS or file system related restrictions.
354  *
355  * Return value 0 indicates that any value supported by the OS is also supported
356  * here.
357  */
358 
file_supported_fmt_resolution(timestamp * const t)359 void file_supported_fmt_resolution( timestamp * const t )
360 {
361 #if defined(_POSIX_VERSION) && _POSIX_VERSION >= 200809
362     timestamp_init( t, 0, 1 );
363 #else
364     /* The current implementation does not support file modification timestamp
365      * resolution of less than one second.
366      */
367     timestamp_init( t, 1, 0 );
368 #endif
369 }
370 
371 #endif
372 
373 
374 /*
375  * file_remove_atexit() - schedule a path to be removed on program exit
376  */
377 
378 static LIST * files_to_remove = L0;
379 
file_remove_atexit(OBJECT * const path)380 void file_remove_atexit( OBJECT * const path )
381 {
382     files_to_remove = list_push_back( files_to_remove, object_copy( path ) );
383 }
384 
385 
386 /*
387  * file_archivescan_impl() - no-profiling worker for file_archivescan()
388  */
389 
file_archivescan_impl(OBJECT * path,archive_scanback func,void * closure)390 static void file_archivescan_impl( OBJECT * path, archive_scanback func, void * closure )
391 {
392     file_archive_info_t * const archive = file_archive_query( path );
393     if ( !archive || !archive->file->is_file )
394         return;
395 
396     /* Lazy collect the archive content information. */
397     if ( filelist_empty( archive->members ) )
398     {
399         if ( DEBUG_BINDSCAN )
400             printf( "scan archive %s\n", object_str( archive->file->name ) );
401         if ( file_collect_archive_content_( archive ) < 0 )
402             return;
403     }
404 
405     /* OS specific part of the file_archivescan operation. */
406     file_archivescan_( archive, func, closure );
407 
408     /* Report the collected archive content. */
409     {
410         FILELISTITER iter = filelist_begin( archive->members );
411         FILELISTITER const end = filelist_end( archive->members );
412         char buf[ MAXJPATH ];
413 
414         for ( ; iter != end ; iter = filelist_next( iter ) )
415         {
416             file_info_t * member_file = filelist_item( iter );
417             LIST * symbols = member_file->files;
418 
419             /* Construct member path: 'archive-path(member-name)'
420              */
421             sprintf( buf, "%s(%s)",
422                 object_str( archive->file->name ),
423                 object_str( member_file->name ) );
424 
425             {
426                 OBJECT * const member = object_new( buf );
427                 (*func)( closure, member, symbols, 1, &member_file->time );
428                 object_free( member );
429             }
430         }
431     }
432 }
433 
434 
435 /*
436  * file_dirscan_impl() - no-profiling worker for file_dirscan()
437  */
438 
file_dirscan_impl(OBJECT * dir,scanback func,void * closure)439 static void file_dirscan_impl( OBJECT * dir, scanback func, void * closure )
440 {
441     file_info_t * const d = file_query( dir );
442     if ( !d || !d->is_dir )
443         return;
444 
445     /* Lazy collect the directory content information. */
446     if ( list_empty( d->files ) )
447     {
448         if ( DEBUG_BINDSCAN )
449             out_printf( "scan directory %s\n", object_str( d->name ) );
450         if ( file_collect_dir_content_( d ) < 0 )
451             return;
452     }
453 
454     /* OS specific part of the file_dirscan operation. */
455     file_dirscan_( d, func, closure );
456 
457     /* Report the collected directory content. */
458     {
459         LISTITER iter = list_begin( d->files );
460         LISTITER const end = list_end( d->files );
461         for ( ; iter != end; iter = list_next( iter ) )
462         {
463             OBJECT * const path = list_item( iter );
464             file_info_t const * const ffq = file_query( path );
465             /* Using a file name read from a file_info_t structure allows OS
466              * specific implementations to store some kind of a normalized file
467              * name there. Using such a normalized file name then allows us to
468              * correctly recognize different file paths actually identifying the
469              * same file. For instance, an implementation may:
470              *  - convert all file names internally to lower case on a case
471              *    insensitive file system
472              *  - convert the NTFS paths to their long path variants as that
473              *    file system each file system entity may have a long and a
474              *    short path variant thus allowing for many different path
475              *    strings identifying the same file.
476              */
477             (*func)( closure, ffq->name, 1 /* stat()'ed */, &ffq->time );
478         }
479     }
480 }
481 
482 
free_file_archive_info(void * xarchive,void * data)483 static void free_file_archive_info( void * xarchive, void * data )
484 {
485     file_archive_info_t * const archive = (file_archive_info_t *)xarchive;
486 
487     if ( archive ) filelist_free( archive->members );
488 }
489 
490 
free_file_info(void * xfile,void * data)491 static void free_file_info( void * xfile, void * data )
492 {
493     file_info_t * const file = (file_info_t *)xfile;
494     object_free( file->name );
495     list_free( file->files );
496 }
497 
498 
remove_files_atexit(void)499 static void remove_files_atexit( void )
500 {
501     LISTITER iter = list_begin( files_to_remove );
502     LISTITER const end = list_end( files_to_remove );
503     for ( ; iter != end; iter = list_next( iter ) )
504         remove( object_str( list_item( iter ) ) );
505     list_free( files_to_remove );
506     files_to_remove = L0;
507 }
508 
509 
510 /*
511  * FILELIST linked-list implementation
512  */
513 
filelist_new(OBJECT * path)514 FILELIST * filelist_new( OBJECT * path )
515 {
516     FILELIST * list = (FILELIST *)BJAM_MALLOC( sizeof( FILELIST ) );
517 
518     memset( list, 0, sizeof( *list ) );
519     list->size = 0;
520     list->head = 0;
521     list->tail = 0;
522 
523     return filelist_push_back( list, path );
524 }
525 
filelist_push_back(FILELIST * list,OBJECT * path)526 FILELIST * filelist_push_back( FILELIST * list, OBJECT * path )
527 {
528     FILEITEM * item;
529     file_info_t * file;
530 
531     /* Lazy initialization
532      */
533     if ( filelist_empty( list ) )
534     {
535         list = filelist_new( path );
536         return list;
537     }
538 
539 
540     item = (FILEITEM *)BJAM_MALLOC( sizeof( FILEITEM ) );
541     memset( item, 0, sizeof( *item ) );
542     item->value = (file_info_t *)BJAM_MALLOC( sizeof( file_info_t ) );
543 
544     file = item->value;
545     memset( file, 0, sizeof( *file ) );
546 
547     file->name = path;
548     file->files = L0;
549 
550     if ( list->tail )
551     {
552         list->tail->next = item;
553     }
554     else
555     {
556         list->head = item;
557     }
558     list->tail = item;
559     list->size++;
560 
561     return list;
562 }
563 
filelist_push_front(FILELIST * list,OBJECT * path)564 FILELIST * filelist_push_front( FILELIST * list, OBJECT * path )
565 {
566     FILEITEM * item;
567     file_info_t * file;
568 
569     /* Lazy initialization
570      */
571     if ( filelist_empty( list ) )
572     {
573         list = filelist_new( path );
574         return list;
575     }
576 
577 
578     item = (FILEITEM *)BJAM_MALLOC( sizeof( FILEITEM ) );
579     memset( item, 0, sizeof( *item ) );
580     item->value = (file_info_t *)BJAM_MALLOC( sizeof( file_info_t ) );
581 
582     file = item->value;
583     memset( file, 0, sizeof( *file ) );
584 
585     file->name = path;
586     file->files = L0;
587 
588     if ( list->head )
589     {
590         item->next = list->head;
591     }
592     else
593     {
594         list->tail = item;
595     }
596     list->head = item;
597     list->size++;
598 
599     return list;
600 }
601 
602 
filelist_pop_front(FILELIST * list)603 FILELIST * filelist_pop_front( FILELIST * list )
604 {
605     FILEITEM * item;
606 
607     if ( filelist_empty( list ) ) return list;
608 
609     item = list->head;
610 
611     if ( item )
612     {
613         if ( item->value ) free_file_info( item->value, 0 );
614 
615         list->head = item->next;
616         list->size--;
617         if ( !list->size ) list->tail = list->head;
618 
619 #ifdef BJAM_NO_MEM_CACHE
620         BJAM_FREE( item );
621 #endif
622     }
623 
624     return list;
625 }
626 
filelist_length(FILELIST * list)627 int filelist_length( FILELIST * list )
628 {
629     int result = 0;
630     if ( !filelist_empty( list ) ) result = list->size;
631 
632     return result;
633 }
634 
filelist_free(FILELIST * list)635 void filelist_free( FILELIST * list )
636 {
637     if ( filelist_empty( list ) ) return;
638 
639     while ( filelist_length( list ) ) filelist_pop_front( list );
640 
641 #ifdef BJAM_NO_MEM_CACHE
642     BJAM_FREE( list );
643 #endif
644 }
645 
filelist_empty(FILELIST * list)646 int filelist_empty( FILELIST * list )
647 {
648     return ( list == FL0 );
649 }
650 
651 
filelist_begin(FILELIST * list)652 FILELISTITER filelist_begin( FILELIST * list )
653 {
654     if ( filelist_empty( list )
655          || list->head == 0 ) return (FILELISTITER)0;
656 
657     return &list->head->value;
658 }
659 
660 
filelist_end(FILELIST * list)661 FILELISTITER filelist_end( FILELIST * list )
662 {
663     return (FILELISTITER)0;
664 }
665 
666 
filelist_next(FILELISTITER iter)667 FILELISTITER filelist_next( FILELISTITER iter )
668 {
669     if ( iter )
670     {
671         /*  Given FILEITEM.value is defined as first member of FILEITEM structure
672          *  and FILELISTITER = &FILEITEM.value,
673          *  FILEITEM = *(FILEITEM **)FILELISTITER
674          */
675         FILEITEM * item = (FILEITEM *)iter;
676         iter = ( item->next ? &item->next->value : (FILELISTITER)0 );
677     }
678 
679     return iter;
680 }
681 
682 
filelist_item(FILELISTITER it)683 file_info_t * filelist_item( FILELISTITER it )
684 {
685     file_info_t * result = (file_info_t *)0;
686 
687     if ( it )
688     {
689         result = (file_info_t *)*it;
690     }
691 
692     return result;
693 }
694 
695 
filelist_front(FILELIST * list)696 file_info_t * filelist_front(  FILELIST * list )
697 {
698     if ( filelist_empty( list )
699          || list->head == 0 ) return (file_info_t *)0;
700 
701     return list->head->value;
702 }
703 
704 
filelist_back(FILELIST * list)705 file_info_t * filelist_back(  FILELIST * list )
706 {
707     if ( filelist_empty( list )
708          || list->tail == 0 ) return (file_info_t *)0;
709 
710     return list->tail->value;
711 }
712