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