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