1 /* Search paths for files.
2 */
3
4 /*
5
6 Copyright (C) 1991-2003 The National Gallery
7
8 This program is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 2 of the License, or
11 (at your option) any later version.
12
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License along
19 with this program; if not, write to the Free Software Foundation, Inc.,
20 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21
22 */
23
24 /*
25
26 These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk
27
28 */
29
30 /* just load .defs/.wses from "."
31 #define DEBUG_LOCAL
32 */
33
34 /* show path searches
35 #define DEBUG_SEARCH
36 */
37
38 /* show path rewrites
39 #define DEBUG_REWRITE
40 */
41
42 #include "ip.h"
43
44 /* Default search paths if prefs fail.
45 */
46 GSList *path_start_default = NULL;
47 GSList *path_search_default = NULL;
48 const char *path_tmp_default = NULL;
49
50 /* We rewrite paths to try to handle files referenced in workspaces in
51 * directories that move.
52 *
53 * For example, suppose we have workspace.ws in /some/directory which loads
54 * image.v in that directory. The workspace will include a line like
55 * (Image_file "/some/directory/image"). Now if directory is moved to
56 * /other/directory and workspace.ws reloaded, we want to rewrite the string
57 * "/some/directory/image.v" to "/other/directory/image.v".
58 *
59 * Also consider picking ICC profiles in export/import: we want to avoid
60 * putting the path into the ws file, we need to go back to "$VIPSHOME" again.
61 *
62 * Rewrite rules can be "locked". For example, we don't want the rewrite from
63 * "/home/john" to "$HOME" to ever be removed.
64 */
65
66 typedef struct _Rewrite {
67 char *old;
68 char *new;
69 gboolean lock;
70 } Rewrite;
71
72 static GSList *rewrite_list = NULL;
73
74 static void
path_rewrite_free(Rewrite * rewrite)75 path_rewrite_free( Rewrite *rewrite )
76 {
77 rewrite_list = g_slist_remove( rewrite_list, rewrite );
78
79 IM_FREE( rewrite->old );
80 IM_FREE( rewrite->new );
81 IM_FREE( rewrite );
82 }
83
84 void
path_rewrite_free_all(void)85 path_rewrite_free_all( void )
86 {
87 while( rewrite_list ) {
88 Rewrite *rewrite = (Rewrite *) rewrite_list->data;
89
90 IM_FREEF( path_rewrite_free, rewrite );
91 }
92 }
93
94 static Rewrite *
path_rewrite_new(const char * old,const char * new,gboolean lock)95 path_rewrite_new( const char *old, const char *new, gboolean lock )
96 {
97 Rewrite *rewrite;
98
99 rewrite = g_new( Rewrite, 1 );
100 rewrite->old = g_strdup( old );
101 rewrite->new = g_strdup( new );
102 rewrite->lock = lock;
103 rewrite_list = g_slist_prepend( rewrite_list, rewrite );
104
105 return( rewrite );
106 }
107
108 static gint
path_rewrite_sort_fn(Rewrite * a,Rewrite * b)109 path_rewrite_sort_fn( Rewrite *a, Rewrite *b )
110 {
111 return( strlen( b->old ) - strlen( a->old ) );
112 }
113
114 static Rewrite *
path_rewrite_lookup(const char * old)115 path_rewrite_lookup( const char *old )
116 {
117 GSList *p;
118 Rewrite *rewrite;
119
120 for( p = rewrite_list; p; p = p->next ) {
121 rewrite = (Rewrite *) p->data;
122
123 if( strcmp( old, rewrite->old ) == 0 )
124 return( rewrite );
125 }
126
127 return( NULL );
128 }
129
130 /* Add a new rewrite pair to the rewrite list. @new can be NULL, meaning
131 * remove a rewrite rule.
132 */
133 void
path_rewrite_add(const char * old,const char * new,gboolean lock)134 path_rewrite_add( const char *old, const char *new, gboolean lock )
135 {
136 char old_buf[FILENAME_MAX + 1];
137 char new_buf[FILENAME_MAX + 1];
138
139 Rewrite *rewrite;
140
141 #ifdef DEBUG_REWRITE
142 printf( "path_rewrite_add: old = %s, new = %s, lock = %d\n",
143 old, new, lock );
144 #endif /*DEBUG_REWRITE*/
145
146 g_return_if_fail( old );
147
148 /* We want the old path in long form, with a trailing '/'. The
149 * trailing '/' will stop us rewriting filenames.
150 *
151 * If we keep all @old paths in long form we can avoid rewrite loops.
152 */
153 im_strncpy( old_buf, old, FILENAME_MAX );
154 strcat( old_buf, G_DIR_SEPARATOR_S );
155 path_expand( old_buf );
156 old = old_buf;
157
158 if( new ) {
159 /* We must keep the new path in short (unexpanded) form,
160 * obviously.
161 */
162 im_strncpy( new_buf, new, FILENAME_MAX );
163 strcat( new_buf, G_DIR_SEPARATOR_S );
164 new = new_buf;
165 }
166
167 /* If old is a prefix of new we will get endless expansion.
168 */
169 if( new &&
170 is_prefix( old, new ) )
171 return;
172
173 if( (rewrite = path_rewrite_lookup( old )) ) {
174 if( !rewrite->lock &&
175 (!new ||
176 strcmp( old, new ) == 0) ) {
177 #ifdef DEBUG_REWRITE
178 printf( "path_rewrite_add: removing\n" );
179 #endif /*DEBUG_REWRITE*/
180
181 IM_FREEF( path_rewrite_free, rewrite );
182 }
183 else if( !rewrite->lock &&
184 new ) {
185 #ifdef DEBUG_REWRITE
186 printf( "path_rewrite_add: updating\n" );
187 #endif /*DEBUG_REWRITE*/
188
189 IM_SETSTR( rewrite->new, new );
190 }
191 else {
192 #ifdef DEBUG_REWRITE
193 printf( "path_rewrite_add: rewrite rule locked\n" );
194 #endif /*DEBUG_REWRITE*/
195 }
196 }
197 else if( new &&
198 strcmp( old, new ) != 0 ) {
199 #ifdef DEBUG_REWRITE
200 printf( "path_rewrite_add: adding\n" );
201 #endif /*DEBUG_REWRITE*/
202
203 (void) path_rewrite_new( old, new, lock );
204 }
205
206 /* Keep longest old first, in case one old is a prefix of
207 * another.
208 */
209 rewrite_list = g_slist_sort( rewrite_list,
210 (GCompareFunc) path_rewrite_sort_fn );
211
212 #ifdef DEBUG_REWRITE
213 {
214 GSList *p;
215
216 printf( "path_rewrite_add: state:\n" );
217
218 for( p = rewrite_list; p; p = p->next ) {
219 rewrite = (Rewrite *) p->data;
220
221 printf( "\told = %s, new = %s\n", rewrite->old, rewrite->new );
222 }
223 }
224 #endif /*DEBUG_REWRITE*/
225 }
226
227 /* Rewrite a string using the rewrite list. buf must be FILENAME_MAX
228 * characters.
229 */
230 void
path_rewrite(char * buf)231 path_rewrite( char *buf )
232 {
233 GSList *p;
234 gboolean changed;
235
236 #ifdef DEBUG_REWRITE
237 printf( "path_rewrite: %s\n", buf );
238 #endif /*DEBUG_REWRITE*/
239
240 do {
241 changed = FALSE;
242
243 for( p = rewrite_list; p; p = p->next ) {
244 Rewrite *rewrite = (Rewrite *) p->data;
245
246 if( is_prefix( rewrite->old, buf ) ) {
247 int olen = strlen( rewrite->old );
248 int nlen = strlen( rewrite->new );
249 int blen = strlen( buf );
250
251 if( blen - olen + nlen > FILENAME_MAX - 3 )
252 break;
253
254 memmove( buf + nlen, buf + olen,
255 blen - olen + 1 );
256 memcpy( buf, rewrite->new, nlen );
257
258 changed = TRUE;
259
260 break;
261 }
262 }
263 } while( changed );
264
265 #ifdef DEBUG_REWRITE
266 printf( "\t-> %s\n", buf );
267 #endif /*DEBUG_REWRITE*/
268 }
269
270 /* The inverse: rewrite in long form ready for file ops.
271 */
272 void
path_expand(char * path)273 path_expand( char *path )
274 {
275 char buf[FILENAME_MAX];
276
277 expand_variables( path, buf );
278 nativeize_path( buf );
279 absoluteize_path( buf );
280 canonicalize_path( buf );
281 im_strncpy( path, buf, FILENAME_MAX );
282 }
283
284 /* Rewite a path to compact form. @path must be FILENAME_MAX characters.
285 *
286 * Examples:
287 *
288 * /home/john/../somefile -> $HOME/../somefile
289 * /home/./john/../somefile -> $HOME/../somefile
290 * fred -> ./fred
291 */
292 void
path_compact(char * path)293 path_compact( char *path )
294 {
295 path_expand( path );
296 path_rewrite( path );
297 }
298
299 /* Turn a search path (eg. "/pics/lr:/pics/hr") into a list of directory names.
300 */
301 GSList *
path_parse(const char * path)302 path_parse( const char *path )
303 {
304 GSList *op = NULL;
305 const char *p;
306 const char *e;
307 int len;
308 char name[FILENAME_MAX + 1];
309
310 for( p = path; *p; p = e ) {
311 /* Find the start of the next component, or the NULL
312 * character.
313 */
314 if( !(e = strchr( p, G_SEARCHPATH_SEPARATOR )) )
315 e = p + strlen( p );
316 len = e - p + 1;
317
318 /* Copy to our buffer, turn to string.
319 */
320 im_strncpy( name, p, IM_MIN( len, FILENAME_MAX ) );
321
322 /* Add to path list.
323 */
324 op = g_slist_append( op, im_strdupn( name ) );
325
326 /* Skip G_SEARCHPATH_SEPARATOR.
327 */
328 if( *e == G_SEARCHPATH_SEPARATOR )
329 e++;
330 }
331
332 return( op );
333 }
334
335 /* Free a path. path_free() is reserved n OS X :(
336 */
337 void
path_free2(GSList * path)338 path_free2( GSList *path )
339 {
340 slist_map( path, (SListMapFn) im_free, NULL );
341 g_slist_free( path );
342 }
343
344 /* Sub-fn of below. Add length of this string + 1 (for ':').
345 */
346 static int
path_add_component(const char * str,int c)347 path_add_component( const char *str, int c )
348 {
349 return( c + strlen( str ) + 1 );
350 }
351
352 /* Sub-fn of below. Copy string to buffer, append ':', return new end.
353 */
354 static char *
path_add_string(const char * str,char * buf)355 path_add_string( const char *str, char *buf )
356 {
357 strcpy( buf, str );
358 strcat( buf, G_SEARCHPATH_SEPARATOR_S );
359
360 return( buf + strlen( buf ) );
361 }
362
363 /* Turn a list of directory names into a search path.
364 */
365 char *
path_unparse(GSList * path)366 path_unparse( GSList *path )
367 {
368 int len = GPOINTER_TO_INT( slist_fold( path, 0,
369 (SListFoldFn) path_add_component, NULL ) );
370 char *buf = imalloc( NULL, len + 1 );
371
372 /* Build new string.
373 */
374 slist_fold( path, buf, (SListFoldFn) path_add_string, NULL );
375
376 /* Fix '\0' to remove trailing G_SEARCHPATH_SEPARATOR.
377 */
378 if( len > 0 )
379 buf[len - 1] = '\0';
380
381 return( buf );
382 }
383
384 /* Track this stuff during a file search.
385 */
386 typedef struct _Search {
387 /* Pattern we search for, and it's compiled form. This does not
388 * include any directory components.
389 */
390 char *basename;
391 GPatternSpec *wild;
392
393 /* Directory offset. If the original pattern is a relative path, eg.
394 * "poop/x*.v", we search every directory on path for a subdirectory
395 * called "poop" and then search all files within that.
396 */
397 char *dirname;
398
399 /* User function to call for every matching file.
400 */
401 path_map_fn fn;
402 void *a;
403
404 /* Files we've previously offered to the user function: we remove
405 * duplicates. So "path1/wombat.def" hides "path2/wombat.def".
406 */
407 GSList *previous;
408 } Search;
409
410 static void
path_search_free(Search * search)411 path_search_free( Search *search )
412 {
413 IM_FREEF( g_free, search->basename );
414 IM_FREEF( g_free, search->dirname );
415 IM_FREEF( slist_free_all, search->previous );
416 IM_FREEF( g_pattern_spec_free, search->wild );
417 }
418
419 static gboolean
path_search_init(Search * search,const char * patt,path_map_fn fn,void * a)420 path_search_init( Search *search, const char *patt, path_map_fn fn, void *a )
421 {
422 search->basename = g_path_get_basename( patt );
423 search->dirname = g_path_get_dirname( patt );
424 search->wild = NULL;
425 search->fn = fn;
426 search->a = a;
427 search->previous = NULL;
428
429 if( !(search->wild = g_pattern_spec_new( search->basename )) ) {
430 path_search_free( search );
431 return( FALSE );
432 }
433
434 return( TRUE );
435 }
436
437 static void *
path_str_eq(const char * s1,const char * s2)438 path_str_eq( const char *s1, const char *s2 )
439 {
440 if( strcmp( s1, s2 ) == 0 )
441 return( (void *) s1 );
442 else
443 return( NULL );
444 }
445
446 /* Test for string matches pattern. If the match is successful, call a user
447 * function.
448 */
449 static void *
path_search_match(Search * search,const char * dir_name,const char * name)450 path_search_match( Search *search, const char *dir_name, const char *name )
451 {
452 if( g_pattern_match_string( search->wild, name ) &&
453 !slist_map( search->previous,
454 (SListMapFn) path_str_eq, (gpointer) name ) ) {
455 char buf[FILENAME_MAX + 10];
456 void *result;
457
458 /* Add to exclusion list.
459 */
460 search->previous =
461 g_slist_prepend( search->previous, g_strdup( name ) );
462
463 im_snprintf( buf, FILENAME_MAX,
464 "%s" G_DIR_SEPARATOR_S "%s", dir_name, name );
465
466 path_compact( buf );
467
468 #ifdef DEBUG_SEARCH
469 printf( "path_search_match: matched \"%s\"\n", buf );
470 #endif /*DEBUG_SEARCH*/
471
472 if( (result = search->fn( buf, search->a, NULL, NULL )) )
473 return( result );
474 }
475
476 return( NULL );
477 }
478
479 /* Scan a directory, calling a function for every entry. Abort scan if
480 * function returns non-NULL.
481 */
482 static void *
path_scan_dir(const char * dir_name,Search * search)483 path_scan_dir( const char *dir_name, Search *search )
484 {
485 char buf[FILENAME_MAX];
486 GDir *dir;
487 const char *name;
488 void *result;
489
490 /* Add the pattern offset, if any. It's '.' for no offset.
491 */
492 im_snprintf( buf, FILENAME_MAX,
493 "%s" G_DIR_SEPARATOR_S "%s", dir_name, search->dirname );
494
495 if( !(dir = (GDir *) callv_string_filename(
496 (callv_string_fn) g_dir_open, buf, NULL, NULL, NULL )) )
497 return( NULL );
498
499 while( (name = g_dir_read_name( dir )) )
500 if( (result = path_search_match( search, buf, name )) ) {
501 g_dir_close( dir );
502 return( result );
503 }
504
505 g_dir_close( dir );
506
507 return( NULL );
508 }
509
510 /* Scan a search path, applying a function to every file name which matches a
511 * pattern. If the user function returns NULL, keep looking, otherwise return
512 * its result. We return NULL on error, or if the user function returns NULL
513 * for all filenames which match.
514 *
515 * Remove duplicates: if fred.wombat is in the first and second dirs on the
516 * path, only apply to the first occurence.
517
518 FIXME ... speed up with a hash and a (date based) cache at some point
519
520 */
521 void *
path_map(GSList * path,const char * patt,path_map_fn fn,void * a)522 path_map( GSList *path, const char *patt, path_map_fn fn, void *a )
523 {
524 Search search;
525 void *result;
526
527 #ifdef DEBUG_SEARCH
528 printf( "path_map: searching for \"%s\"\n", patt );
529 #endif /*DEBUG_SEARCH*/
530
531 if( !path_search_init( &search, patt, fn, a ) )
532 return( NULL );
533
534 result = slist_map( path, (SListMapFn) path_scan_dir, &search );
535
536 path_search_free( &search );
537
538 return( result );
539 }
540
541 /* As above, but scan a single directory.
542 */
543 void *
path_map_dir(const char * dir,const char * patt,path_map_fn fn,void * a)544 path_map_dir( const char *dir, const char *patt, path_map_fn fn, void *a )
545 {
546 Search search;
547 void *result;
548
549 #ifdef DEBUG_SEARCH
550 printf( "path_map_dir: searching for \"%s\"\n", patt );
551 #endif /*DEBUG_SEARCH*/
552
553 if( !path_search_init( &search, patt, fn, a ) )
554 return( NULL );
555
556 if( !(result = path_scan_dir( dir, &search )) ) {
557 /* Not found? Maybe - error message anyway.
558 */
559 error_top( _( "Not found." ) );
560 error_sub( _( "File \"%s\" not found." ), patt );
561 }
562
563 path_search_free( &search );
564
565 return( result );
566 }
567
568 /* Search for a file on the search path.
569 */
570 char *
path_find_file(const char * filename)571 path_find_file( const char *filename )
572 {
573 char *fname;
574
575 #ifdef DEBUG_SEARCH
576 printf( "path_find_file: \"%s\"\n", filename );
577 #endif /*DEBUG_SEARCH*/
578
579 /* Try file name exactly.
580 */
581 if( existsf( "%s", filename ) )
582 return( im_strdupn( filename ) );
583
584 /* Search everywhere.
585 */
586 if( (fname = path_map( PATH_SEARCH, filename,
587 (path_map_fn) im_strdupn, NULL )) )
588 return( fname );
589
590 error_top( _( "Not found." ) );
591 error_sub( _( "File \"%s\" not found on path" ), filename );
592
593 return( NULL );
594 }
595
596 void
path_init(void)597 path_init( void )
598 {
599 char buf[FILENAME_MAX];
600
601 path_rewrite_add( get_prefix(), "$VIPSHOME", TRUE );
602 path_rewrite_add( g_get_home_dir(), "$HOME", TRUE );
603 path_rewrite_add( get_savedir(), "$SAVEDIR", TRUE );
604
605 /* You might think we could add a rule to swap '.' for
606 * g_get_current_dir(), but that would then make workspaces depend on
607 * a certain value of cwd before they could work.
608 */
609
610 /* And the expanded form too.
611 */
612 expand_variables( get_savedir(), buf );
613 path_rewrite_add( buf, "$SAVEDIR", TRUE );
614
615 #ifdef DEBUG_LOCAL
616 printf( "path_init: loading start from \".\" only\n" );
617 path_start_default = path_parse( "." );
618 path_search_default = path_parse( "." );
619 path_tmp_default = im_strdup( NULL, "." );
620 #else /*!DEBUG_LOCAL*/
621 im_snprintf( buf, FILENAME_MAX,
622 "%s" G_DIR_SEPARATOR_S "start" G_SEARCHPATH_SEPARATOR_S
623 "$VIPSHOME" G_DIR_SEPARATOR_S "share" G_DIR_SEPARATOR_S
624 "$PACKAGE" G_DIR_SEPARATOR_S "start",
625 get_savedir() );
626 path_start_default = path_parse( buf );
627
628 im_snprintf( buf, FILENAME_MAX,
629 "%s" G_DIR_SEPARATOR_S "data" G_SEARCHPATH_SEPARATOR_S
630 "$VIPSHOME" G_DIR_SEPARATOR_S "share" G_DIR_SEPARATOR_S
631 "$PACKAGE" G_DIR_SEPARATOR_S "data" G_SEARCHPATH_SEPARATOR_S
632 ".",
633 get_savedir() );
634 path_search_default = path_parse( buf );
635
636 im_snprintf( buf, FILENAME_MAX,
637 "%s" G_DIR_SEPARATOR_S "tmp", get_savedir() );
638 path_tmp_default = im_strdup( NULL, buf );
639 #endif /*DEBUG_LOCAL*/
640 }
641