xref: /plan9/sys/src/cmd/gs/src/gp_unifs.c (revision 593dc095aefb2a85c828727bbfa9da139a49bdf4)
1 /* Copyright (C) 1993, 2000-2004 artofcode LLC. All rights reserved.
2 
3   This software is provided AS-IS with no warranty, either express or
4   implied.
5 
6   This software is distributed under license and may not be copied,
7   modified or distributed except as expressly authorized under the terms
8   of the license contained in the file LICENSE in this distribution.
9 
10   For more information about licensing, please refer to
11   http://www.ghostscript.com/licensing/. For information on
12   commercial licensing, go to http://www.artifex.com/licensing/ or
13   contact Artifex Software, Inc., 101 Lucas Valley Road #110,
14   San Rafael, CA  94903, U.S.A., +1(415)492-9861.
15 */
16 
17 /* $Id: gp_unifs.c,v 1.17 2004/01/20 01:39:30 giles Exp $ */
18 /* "Unix-like" file system platform routines for Ghostscript */
19 
20 #include "memory_.h"
21 #include "string_.h"
22 #include "stdio_.h"		/* for FILENAME_MAX */
23 #include "gx.h"
24 #include "gp.h"
25 #include "gpmisc.h"
26 #include "gsstruct.h"
27 #include "gsutil.h"		/* for string_match */
28 #include "stat_.h"
29 #include "dirent_.h"
30 #include "unistd_.h"
31 #include <stdlib.h>             /* for mkstemp/mktemp */
32 
33 /* Provide a definition of the maximum path length in case the system
34  * headers don't define it. This should be gp_file_name_sizeof from
35  * gp.h once that value is properly sent in a system-dependent way.
36  * HP-UX 11i 11.11 incorrectly defines FILENAME_MAX as 14.
37  */
38 #ifdef FILENAME_MAX
39 #  if FILENAME_MAX < 80  /* arbitrary */
40 #    undef FILENAME_MAX
41 #  endif
42 #endif
43 #ifndef FILENAME_MAX
44 #  define FILENAME_MAX 1024
45 #endif
46 
47 /* Library routines not declared in a standard header */
48 extern char *mktemp(char *);
49 
50 /* ------ File naming and accessing ------ */
51 
52 /* Define the default scratch file name prefix. */
53 const char gp_scratch_file_name_prefix[] = "gs_";
54 
55 /* Define the name of the null output file. */
56 const char gp_null_file_name[] = "/dev/null";
57 
58 /* Define the name that designates the current directory. */
59 const char gp_current_directory_name[] = ".";
60 
61 /* Create and open a scratch file with a given name prefix. */
62 /* Write the actual file name at fname. */
63 FILE *
gp_open_scratch_file(const char * prefix,char fname[gp_file_name_sizeof],const char * mode)64 gp_open_scratch_file(const char *prefix, char fname[gp_file_name_sizeof],
65 		     const char *mode)
66 {	/* The -8 is for XXXXXX plus a possible final / and -. */
67     int prefix_length = strlen(prefix);
68     int len = gp_file_name_sizeof - prefix_length - 8;
69     FILE *fp;
70 
71     if (gp_file_name_is_absolute(prefix, prefix_length))
72 	*fname = 0;
73     else if (gp_gettmpdir(fname, &len) != 0)
74 	strcpy(fname, "/tmp/");
75     else {
76 	if (strlen(fname) != 0 && fname[strlen(fname) - 1] != '/')
77 	    strcat(fname, "/");
78     }
79     if (strlen(fname) + prefix_length + 8 >= gp_file_name_sizeof)
80 	return 0;		/* file name too long */
81     strcat(fname, prefix);
82     /* Prevent trailing X's in path from being converted by mktemp. */
83     if (*fname != 0 && fname[strlen(fname) - 1] == 'X')
84 	strcat(fname, "-");
85     strcat(fname, "XXXXXX");
86 
87 #ifdef HAVE_MKSTEMP
88     {
89 	    int file;
90 	    char ofname[gp_file_name_sizeof];
91 
92 	    /* save the old filename template in case mkstemp fails */
93 	    memcpy(ofname, fname, gp_file_name_sizeof);
94 
95 	    file = mkstemp(fname);
96 	    if (file < -1) {
97 		    eprintf1("**** Could not open temporary file %s\n", ofname);
98 		    return NULL;
99 	    }
100 	    fp = fdopen(file, mode);
101  	    if (fp == NULL)
102  		    close(file);
103     }
104 #else
105     mktemp(fname);
106     fp = gp_fopentemp(fname, mode);
107 #endif
108     if (fp == NULL)
109 	eprintf1("**** Could not open temporary file %s\n", fname);
110     return fp;
111 }
112 
113 /* Open a file with the given name, as a stream of uninterpreted bytes. */
114 FILE *
gp_fopen(const char * fname,const char * mode)115 gp_fopen(const char *fname, const char *mode)
116 {
117     return fopen(fname, mode);
118 }
119 
120 /* Set a file into binary or text mode. */
121 int
gp_setmode_binary(FILE * pfile,bool mode)122 gp_setmode_binary(FILE * pfile, bool mode)
123 {
124     return 0;			/* Noop under Unix */
125 }
126 
127 /* ------ File enumeration ------ */
128 
129 /* Thanks to Fritz Elfert (Fritz_Elfert@wue.maus.de) for */
130 /* the original version of the following code, and Richard Mlynarik */
131 /* (mly@adoc.xerox.com) for an improved version. */
132 
133 typedef struct dirstack_s dirstack;
134 struct dirstack_s {
135     dirstack *next;
136     DIR *entry;
137 };
138 
139 gs_private_st_ptrs1(st_dirstack, dirstack, "dirstack",
140 		    dirstack_enum_ptrs, dirstack_reloc_ptrs, next);
141 
142 struct file_enum_s {
143     DIR *dirp;			/* pointer to current open directory   */
144     char *pattern;		/* original pattern                    */
145     char *work;			/* current path                        */
146     int worklen;		/* strlen (work)                       */
147     dirstack *dstack;		/* directory stack                     */
148     int patlen;
149     int pathead;		/* how much of pattern to consider
150 				 *  when listing files in current directory */
151     bool first_time;
152     gs_memory_t *memory;
153 };
154 gs_private_st_ptrs3(st_file_enum, struct file_enum_s, "file_enum",
155 	  file_enum_enum_ptrs, file_enum_reloc_ptrs, pattern, work, dstack);
156 
157 /* Private procedures */
158 
159 /* Do a wild-card match. */
160 #ifdef DEBUG
161 private bool
wmatch(const byte * str,uint len,const byte * pstr,uint plen,const string_match_params * psmp)162 wmatch(const byte * str, uint len, const byte * pstr, uint plen,
163        const string_match_params * psmp)
164 {
165     bool match = string_match(str, len, pstr, plen, psmp);
166 
167     if (gs_debug_c('e')) {
168 	int i;
169 	dlputs("[e]string_match(\"");
170 	for (i=0; i<len; i++)
171 	    errprintf("%c", str[i]);
172 	dputs("\", \"");
173 	for (i=0; i<plen; i++)
174 	    errprintf("%c", pstr[i]);
175 	dprintf1("\") = %s\n", (match ? "TRUE" : "false"));
176     }
177     return match;
178 }
179 #define string_match wmatch
180 #endif
181 
182 /* Search a string backward for a character. */
183 /* (This substitutes for strrchr, which some systems don't provide.) */
184 private char *
rchr(char * str,char ch,int len)185 rchr(char *str, char ch, int len)
186 {
187     register char *p = str + len;
188 
189     while (p > str)
190 	if (*--p == ch)
191 	    return p;
192     return 0;
193 }
194 
195 /* Pop a directory from the enumeration stack. */
196 private bool
popdir(file_enum * pfen)197 popdir(file_enum * pfen)
198 {
199     dirstack *d = pfen->dstack;
200 
201     if (d == 0)
202 	return false;
203     pfen->dirp = d->entry;
204     pfen->dstack = d->next;
205     gs_free_object(pfen->memory, d, "gp_enumerate_files(popdir)");
206     return true;
207 }
208 
209 /* Initialize an enumeration. */
210 file_enum *
gp_enumerate_files_init(const char * pat,uint patlen,gs_memory_t * mem)211 gp_enumerate_files_init(const char *pat, uint patlen, gs_memory_t * mem)
212 {
213     file_enum *pfen;
214     char *p;
215     char *work;
216 
217     /* Reject attempts to enumerate paths longer than the */
218     /* system-dependent limit. */
219     if (patlen > FILENAME_MAX)
220 	return 0;
221 
222     /* Reject attempts to enumerate with a pattern containing zeroes. */
223     {
224 	const char *p1;
225 
226 	for (p1 = pat; p1 < pat + patlen; p1++)
227 	    if (*p1 == 0)
228 		return 0;
229     }
230     /* >>> Should crunch strings of repeated "/"'s in pat to a single "/"
231      * >>>  to match stupid unix filesystem "conventions" */
232 
233     pfen = gs_alloc_struct(mem, file_enum, &st_file_enum,
234 			   "gp_enumerate_files");
235     if (pfen == 0)
236 	return 0;
237 
238     /* pattern and work could be allocated as strings, */
239     /* but it's simpler for GC and freeing to allocate them as bytes. */
240 
241     pfen->pattern =
242 	(char *)gs_alloc_bytes(mem, patlen + 1,
243 			       "gp_enumerate_files(pattern)");
244     if (pfen->pattern == 0)
245 	return 0;
246     memcpy(pfen->pattern, pat, patlen);
247     pfen->pattern[patlen] = 0;
248 
249     work = (char *)gs_alloc_bytes(mem, FILENAME_MAX + 1,
250 				  "gp_enumerate_files(work)");
251     if (work == 0)
252 	return 0;
253     pfen->work = work;
254 
255     p = work;
256     memcpy(p, pat, patlen);
257     p += patlen;
258     *p = 0;
259 
260     /* Remove directory specifications beyond the first wild card. */
261     /* Some systems don't have strpbrk, so we code it open. */
262     p = pfen->work;
263     while (!(*p == '*' || *p == '?' || *p == 0))
264 	p++;
265     while (!(*p == '/' || *p == 0))
266 	p++;
267     if (*p == '/')
268 	*p = 0;
269     /* Substring for first wildcard match */
270     pfen->pathead = p - work;
271 
272     /* Select the next higher directory-level. */
273     p = rchr(work, '/', p - work);
274     if (!p) {			/* No directory specification */
275 	work[0] = 0;
276 	pfen->worklen = 0;
277     } else {
278 	if (p == work) {	/* Root directory -- don't turn "/" into "" */
279 	    p++;
280 	}
281 	*p = 0;
282 	pfen->worklen = p - work;
283     }
284 
285     pfen->memory = mem;
286     pfen->dstack = 0;
287     pfen->first_time = true;
288     pfen->patlen = patlen;
289     return pfen;
290 }
291 
292 /* Enumerate the next file. */
293 uint
gp_enumerate_files_next(file_enum * pfen,char * ptr,uint maxlen)294 gp_enumerate_files_next(file_enum * pfen, char *ptr, uint maxlen)
295 {
296     const dir_entry *de;
297     char *work = pfen->work;
298     int worklen = pfen->worklen;
299     char *pattern = pfen->pattern;
300     int pathead = pfen->pathead;
301     int len;
302     struct stat stbuf;
303 
304     if (pfen->first_time) {
305 	pfen->dirp = ((worklen == 0) ? opendir(".") : opendir(work));
306 	if_debug1('e', "[e]file_enum:First-Open '%s'\n", work);
307 	pfen->first_time = false;
308 	if (pfen->dirp == 0) {	/* first opendir failed */
309 	    gp_enumerate_files_close(pfen);
310 	    return ~(uint) 0;
311 	}
312     }
313   top:de = readdir(pfen->dirp);
314     if (de == 0) {		/* No more entries in this directory */
315 	char *p;
316 
317 	if_debug0('e', "[e]file_enum:Closedir\n");
318 	closedir(pfen->dirp);
319 	/* Back working directory and matching pattern up one level */
320 	p = rchr(work, '/', worklen);
321 	if (p != 0) {
322 	    if (p == work)
323 		p++;
324 	    *p = 0;
325 	    worklen = p - work;
326 	} else
327 	    worklen = 0;
328 	p = rchr(pattern, '/', pathead);
329 	if (p != 0)
330 	    pathead = p - pattern;
331 	else
332 	    pathead = 0;
333 
334 	if (popdir(pfen)) {	/* Back up the directory tree. */
335 	    if_debug1('e', "[e]file_enum:Dir popped '%s'\n", work);
336 	    goto top;
337 	} else {
338 	    if_debug0('e', "[e]file_enum:Dirstack empty\n");
339 	    gp_enumerate_files_close(pfen);
340 	    return ~(uint) 0;
341 	}
342     }
343     /* Skip . and .. */
344     len = strlen(de->d_name);
345     if (len <= 2 && (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")))
346 	goto top;
347     if (len + worklen + 1 > FILENAME_MAX)
348 	/* Should be an error, I suppose */
349 	goto top;
350     if (worklen == 0) {		/* "Current" directory (evil un*x kludge) */
351 	memcpy(work, de->d_name, len + 1);
352     } else if (worklen == 1 && work[0] == '/') {	/* Root directory */
353 	memcpy(work + 1, de->d_name, len + 1);
354 	len = len + 1;
355     } else {
356 	work[worklen] = '/';
357 	memcpy(work + worklen + 1, de->d_name, len + 1);
358 	len = worklen + 1 + len;
359     }
360 
361     /* Test for a match at this directory level */
362     if (!string_match((byte *) work, len, (byte *) pattern, pathead, NULL))
363 	goto top;
364 
365     /* Perhaps descend into subdirectories */
366     if (pathead < pfen->patlen) {
367 	DIR *dp;
368 
369 	if (((stat(work, &stbuf) >= 0)
370 	     ? !stat_is_dir(stbuf)
371 	/* Couldn't stat it.
372 	 * Well, perhaps it's a directory and
373 	 * we'll be able to list it anyway.
374 	 * If it isn't or we can't, no harm done. */
375 	     : 0))
376 	    goto top;
377 
378 	if (pfen->patlen == pathead + 1) {	/* Listing "foo/?/" -- return this entry */
379 	    /* if it's a directory. */
380 	    if (!stat_is_dir(stbuf)) {	/* Do directoryp test the hard way */
381 		dp = opendir(work);
382 		if (!dp)
383 		    goto top;
384 		closedir(dp);
385 	    }
386 	    work[len++] = '/';
387 	    goto winner;
388 	}
389 	/* >>> Should optimise the case in which the next level */
390 	/* >>> of directory has no wildcards. */
391 	dp = opendir(work);
392 #ifdef DEBUG
393 	{
394 	    char save_end = pattern[pathead];
395 
396 	    pattern[pathead] = 0;
397 	    if_debug2('e', "[e]file_enum:fname='%s', p='%s'\n",
398 		      work, pattern);
399 	    pattern[pathead] = save_end;
400 	}
401 #endif /* DEBUG */
402 	if (!dp)
403 	    /* Can't list this one */
404 	    goto top;
405 	else {			/* Advance to the next directory-delimiter */
406 	    /* in pattern */
407 	    char *p;
408 	    dirstack *d;
409 
410 	    for (p = pattern + pathead + 1;; p++) {
411 		if (*p == 0) {	/* No more subdirectories to match */
412 		    pathead = pfen->patlen;
413 		    break;
414 		} else if (*p == '/') {
415 		    pathead = p - pattern;
416 		    break;
417 		}
418 	    }
419 
420 	    /* Push a directory onto the enumeration stack. */
421 	    d = gs_alloc_struct(pfen->memory, dirstack,
422 				&st_dirstack,
423 				"gp_enumerate_files(pushdir)");
424 	    if (d != 0) {
425 		d->next = pfen->dstack;
426 		d->entry = pfen->dirp;
427 		pfen->dstack = d;
428 	    } else
429 		DO_NOTHING;	/* >>> e_VMerror!!! */
430 
431 	    if_debug1('e', "[e]file_enum:Dir pushed '%s'\n",
432 		      work);
433 	    worklen = len;
434 	    pfen->dirp = dp;
435 	    goto top;
436 	}
437     }
438   winner:
439     /* We have a winner! */
440     pfen->worklen = worklen;
441     pfen->pathead = pathead;
442     memcpy(ptr, work, len);
443     return len;
444 }
445 
446 /* Clean up the file enumeration. */
447 void
gp_enumerate_files_close(file_enum * pfen)448 gp_enumerate_files_close(file_enum * pfen)
449 {
450     gs_memory_t *mem = pfen->memory;
451 
452     if_debug0('e', "[e]file_enum:Cleanup\n");
453     while (popdir(pfen))	/* clear directory stack */
454 	DO_NOTHING;
455     gs_free_object(mem, (byte *) pfen->work,
456 		   "gp_enumerate_close(work)");
457     gs_free_object(mem, (byte *) pfen->pattern,
458 		   "gp_enumerate_files_close(pattern)");
459     gs_free_object(mem, pfen, "gp_enumerate_files_close");
460 }
461 
462 /* Test-cases:
463    (../?*r*?/?*.ps) {==} 100 string filenameforall
464    (../?*r*?/?*.ps*) {==} 100 string filenameforall
465    (../?*r*?/) {==} 100 string filenameforall
466    (/t*?/?*.ps) {==} 100 string filenameforall
467  */
468