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