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