]>
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. | |
1e59de90 | 5 | * (See accompanying file LICENSE.txt or https://www.bfgroup.xyz/b2/LICENSE.txt) |
7c673cae FG |
6 | */ |
7 | ||
8 | /* | |
92f5a8d4 | 9 | * filesys.c - OS independent file system manipulation support |
7c673cae FG |
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 | * | |
92f5a8d4 | 22 | * External routines - utilities for OS specific module implementations: |
7c673cae FG |
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" | |
f67539c2 | 36 | #include "jam_strings.h" |
7c673cae FG |
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 | { | |
11fdf7f2 | 91 | archive->name = path_key; |
7c673cae FG |
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 | ||
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 | ||
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 | ||
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 | ||
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 | ||
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 | ||
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 | ||
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 | ||
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 | ||
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 | ||
11fdf7f2 | 296 | #ifndef OS_NT |
7c673cae FG |
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 | ||
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; | |
11fdf7f2 TL |
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 | |
7c673cae | 341 | timestamp_init( &info->time, statbuf.st_mtime, 0 ); |
11fdf7f2 | 342 | #endif |
7c673cae FG |
343 | } |
344 | } | |
345 | ||
11fdf7f2 TL |
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 | ||
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 | ||
7c673cae FG |
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 | ||
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 | ||
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 | ||
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 | ||
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 | ||
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 | ||
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 | ||
514 | FILELIST * filelist_new( OBJECT * path ) | |
515 | { | |
1e59de90 | 516 | FILELIST * list = b2::jam::make_ptr<FILELIST>(); |
7c673cae FG |
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 | ||
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 | ||
1e59de90 TL |
540 | item = b2::jam::make_ptr<FILEITEM>(); |
541 | item->value = b2::jam::make_ptr<file_info_t>(); | |
7c673cae FG |
542 | |
543 | file = item->value; | |
7c673cae FG |
544 | file->name = path; |
545 | file->files = L0; | |
546 | ||
547 | if ( list->tail ) | |
548 | { | |
549 | list->tail->next = item; | |
550 | } | |
551 | else | |
552 | { | |
553 | list->head = item; | |
554 | } | |
555 | list->tail = item; | |
556 | list->size++; | |
557 | ||
558 | return list; | |
559 | } | |
560 | ||
561 | FILELIST * filelist_push_front( FILELIST * list, OBJECT * path ) | |
562 | { | |
563 | FILEITEM * item; | |
564 | file_info_t * file; | |
565 | ||
566 | /* Lazy initialization | |
567 | */ | |
568 | if ( filelist_empty( list ) ) | |
569 | { | |
570 | list = filelist_new( path ); | |
571 | return list; | |
572 | } | |
573 | ||
574 | ||
1e59de90 | 575 | item = b2::jam::make_ptr<FILEITEM>(); |
7c673cae | 576 | memset( item, 0, sizeof( *item ) ); |
1e59de90 | 577 | item->value = b2::jam::make_ptr<file_info_t>(); |
7c673cae FG |
578 | |
579 | file = item->value; | |
580 | memset( file, 0, sizeof( *file ) ); | |
581 | ||
582 | file->name = path; | |
583 | file->files = L0; | |
584 | ||
585 | if ( list->head ) | |
586 | { | |
587 | item->next = list->head; | |
588 | } | |
589 | else | |
590 | { | |
591 | list->tail = item; | |
592 | } | |
593 | list->head = item; | |
594 | list->size++; | |
595 | ||
596 | return list; | |
597 | } | |
598 | ||
599 | ||
600 | FILELIST * filelist_pop_front( FILELIST * list ) | |
601 | { | |
602 | FILEITEM * item; | |
603 | ||
604 | if ( filelist_empty( list ) ) return list; | |
605 | ||
606 | item = list->head; | |
607 | ||
608 | if ( item ) | |
609 | { | |
1e59de90 TL |
610 | if ( item->value ) |
611 | { | |
612 | free_file_info( item->value, 0 ); | |
613 | b2::jam::free_ptr( item->value ); | |
614 | } | |
7c673cae FG |
615 | |
616 | list->head = item->next; | |
617 | list->size--; | |
618 | if ( !list->size ) list->tail = list->head; | |
619 | ||
1e59de90 | 620 | b2::jam::free_ptr( item ); |
7c673cae FG |
621 | } |
622 | ||
623 | return list; | |
624 | } | |
625 | ||
626 | int filelist_length( FILELIST * list ) | |
627 | { | |
628 | int result = 0; | |
629 | if ( !filelist_empty( list ) ) result = list->size; | |
630 | ||
631 | return result; | |
632 | } | |
633 | ||
634 | void filelist_free( FILELIST * list ) | |
635 | { | |
7c673cae FG |
636 | if ( filelist_empty( list ) ) return; |
637 | ||
638 | while ( filelist_length( list ) ) filelist_pop_front( list ); | |
639 | ||
1e59de90 | 640 | b2::jam::free_ptr( list ); |
7c673cae FG |
641 | } |
642 | ||
643 | int filelist_empty( FILELIST * list ) | |
644 | { | |
645 | return ( list == FL0 ); | |
646 | } | |
647 | ||
648 | ||
649 | FILELISTITER filelist_begin( FILELIST * list ) | |
650 | { | |
651 | if ( filelist_empty( list ) | |
652 | || list->head == 0 ) return (FILELISTITER)0; | |
653 | ||
654 | return &list->head->value; | |
655 | } | |
656 | ||
657 | ||
658 | FILELISTITER filelist_end( FILELIST * list ) | |
659 | { | |
660 | return (FILELISTITER)0; | |
661 | } | |
662 | ||
663 | ||
664 | FILELISTITER filelist_next( FILELISTITER iter ) | |
665 | { | |
666 | if ( iter ) | |
667 | { | |
668 | /* Given FILEITEM.value is defined as first member of FILEITEM structure | |
669 | * and FILELISTITER = &FILEITEM.value, | |
670 | * FILEITEM = *(FILEITEM **)FILELISTITER | |
671 | */ | |
672 | FILEITEM * item = (FILEITEM *)iter; | |
673 | iter = ( item->next ? &item->next->value : (FILELISTITER)0 ); | |
674 | } | |
675 | ||
676 | return iter; | |
677 | } | |
678 | ||
679 | ||
680 | file_info_t * filelist_item( FILELISTITER it ) | |
681 | { | |
682 | file_info_t * result = (file_info_t *)0; | |
683 | ||
684 | if ( it ) | |
685 | { | |
686 | result = (file_info_t *)*it; | |
687 | } | |
688 | ||
689 | return result; | |
690 | } | |
691 | ||
692 | ||
693 | file_info_t * filelist_front( FILELIST * list ) | |
694 | { | |
695 | if ( filelist_empty( list ) | |
696 | || list->head == 0 ) return (file_info_t *)0; | |
697 | ||
698 | return list->head->value; | |
699 | } | |
700 | ||
701 | ||
702 | file_info_t * filelist_back( FILELIST * list ) | |
703 | { | |
704 | if ( filelist_empty( list ) | |
705 | || list->tail == 0 ) return (file_info_t *)0; | |
706 | ||
707 | return list->tail->value; | |
708 | } |