]>
Commit | Line | Data |
---|---|---|
efb2f309 | 1 | /*Copyright 1993-2002 by Easy Software Products. |
caddbb58 | 2 | Copyright 1993, 1995, 1996, 1998 Aladdin Enterprises. All rights reserved. |
3 | ||
8e4ff0ae | 4 | This file is part of GNU Ghostscript. |
caddbb58 | 5 | |
8e4ff0ae | 6 | GNU Ghostscript is distributed in the hope that it will be useful, but |
caddbb58 | 7 | WITHOUT ANY WARRANTY. No author or distributor accepts responsibility |
8 | to anyone for the consequences of using it or for whether it serves any | |
9 | particular purpose or works at all, unless he says so in writing. Refer | |
10 | to the GNU General Public License for full details. | |
11 | ||
8e4ff0ae | 12 | Everyone is granted permission to copy, modify and redistribute GNU |
13 | Ghostscript, but only under the conditions described in the GNU General | |
caddbb58 | 14 | Public License. A copy of this license is supposed to have been given |
15 | to you along with GNU Ghostscript so you can know your rights and | |
8e4ff0ae | 16 | responsibilities. It should be in a file named COPYING. Among other |
17 | things, the copyright notice and this notice must be preserved on all | |
18 | copies. | |
caddbb58 | 19 | |
20 | Aladdin Enterprises supports the work of the GNU Project, but is not | |
21 | affiliated with the Free Software Foundation or the GNU Project. GNU | |
22 | Ghostscript, as distributed by Aladdin Enterprises, does not require any | |
23 | GNU software to build or run it. | |
8e4ff0ae | 24 | */ |
25 | ||
efb2f309 | 26 | /*$Id: gp_unifs.c,v 1.7 2002/01/02 17:59:10 mike Exp $ */ |
8e4ff0ae | 27 | /* "Unix-like" file system platform routines for Ghostscript */ |
28 | #include "memory_.h" | |
29 | #include "string_.h" | |
30 | #include "gx.h" | |
31 | #include "gp.h" | |
32 | #include "gsstruct.h" | |
caddbb58 | 33 | #include "gsutil.h" /* for string_match */ |
8e4ff0ae | 34 | #include "stat_.h" |
35 | #include "dirent_.h" | |
caddbb58 | 36 | #include <sys/param.h> /* for MAXPATHLEN */ |
50b9556e | 37 | #include <cups/cups.h> |
8e4ff0ae | 38 | |
39 | /* Some systems (Interactive for example) don't define MAXPATHLEN, | |
40 | * so we define it here. (This probably should be done via a Config-Script.) | |
41 | */ | |
42 | ||
43 | #ifndef MAXPATHLEN | |
44 | # define MAXPATHLEN 1024 | |
45 | #endif | |
46 | ||
8e4ff0ae | 47 | /* ------ File naming and accessing ------ */ |
48 | ||
49 | /* Define the default scratch file name prefix. */ | |
50 | const char gp_scratch_file_name_prefix[] = "gs_"; | |
51 | ||
52 | /* Define the name of the null output file. */ | |
53 | const char gp_null_file_name[] = "/dev/null"; | |
54 | ||
55 | /* Define the name that designates the current directory. */ | |
56 | const char gp_current_directory_name[] = "."; | |
57 | ||
58 | /* Create and open a scratch file with a given name prefix. */ | |
59 | /* Write the actual file name at fname. */ | |
60 | FILE * | |
caddbb58 | 61 | gp_open_scratch_file(const char *prefix, char fname[gp_file_name_sizeof], |
62 | const char *mode) | |
50b9556e | 63 | { |
64 | int fd; /* File descriptor for temp file */ | |
caddbb58 | 65 | |
caddbb58 | 66 | |
50b9556e | 67 | if ((fd = cupsTempFd(fname, gp_file_name_sizeof)) < 0) |
0819478b | 68 | return (NULL); |
69 | else | |
70 | return (fdopen(fd, mode)); | |
8e4ff0ae | 71 | } |
72 | ||
73 | /* Open a file with the given name, as a stream of uninterpreted bytes. */ | |
74 | FILE * | |
75 | gp_fopen(const char *fname, const char *mode) | |
caddbb58 | 76 | { |
77 | return fopen(fname, mode); | |
78 | } | |
79 | ||
80 | /* Set a file into binary or text mode. */ | |
81 | int | |
82 | gp_setmode_binary(FILE * pfile, bool mode) | |
83 | { | |
84 | return 0; /* Noop under Unix */ | |
8e4ff0ae | 85 | } |
86 | ||
87 | /* ------ File enumeration ------ */ | |
88 | ||
89 | /* Thanks to Fritz Elfert (Fritz_Elfert@wue.maus.de) for */ | |
90 | /* the original version of the following code, and Richard Mlynarik */ | |
91 | /* (mly@adoc.xerox.com) for an improved version. */ | |
92 | ||
93 | typedef struct dirstack_s dirstack; | |
94 | struct dirstack_s { | |
caddbb58 | 95 | dirstack *next; |
96 | DIR *entry; | |
8e4ff0ae | 97 | }; |
caddbb58 | 98 | |
8e4ff0ae | 99 | gs_private_st_ptrs1(st_dirstack, dirstack, "dirstack", |
caddbb58 | 100 | dirstack_enum_ptrs, dirstack_reloc_ptrs, next); |
8e4ff0ae | 101 | |
102 | struct file_enum_s { | |
caddbb58 | 103 | DIR *dirp; /* pointer to current open directory */ |
104 | char *pattern; /* original pattern */ | |
105 | char *work; /* current path */ | |
106 | int worklen; /* strlen (work) */ | |
107 | dirstack *dstack; /* directory stack */ | |
108 | int patlen; | |
109 | int pathead; /* how much of pattern to consider | |
8e4ff0ae | 110 | * when listing files in current directory */ |
caddbb58 | 111 | bool first_time; |
112 | gs_memory_t *memory; | |
8e4ff0ae | 113 | }; |
114 | gs_private_st_ptrs3(st_file_enum, struct file_enum_s, "file_enum", | |
caddbb58 | 115 | file_enum_enum_ptrs, file_enum_reloc_ptrs, pattern, work, dstack); |
8e4ff0ae | 116 | |
117 | /* Private procedures */ | |
118 | ||
119 | /* Do a wild-card match. */ | |
120 | #ifdef DEBUG | |
121 | private bool | |
caddbb58 | 122 | wmatch(const byte * str, uint len, const byte * pstr, uint plen, |
123 | const string_match_params * psmp) | |
124 | { | |
125 | bool match = string_match(str, len, pstr, plen, psmp); | |
126 | ||
127 | if (gs_debug_c('e')) { | |
128 | dlputs("[e]string_match(\""); | |
129 | fwrite(str, 1, len, dstderr); | |
130 | dputs("\", \""); | |
131 | fwrite(pstr, 1, plen, dstderr); | |
132 | dprintf1("\") = %s\n", (match ? "TRUE" : "false")); | |
133 | } | |
134 | return match; | |
8e4ff0ae | 135 | } |
136 | #define string_match wmatch | |
137 | #endif | |
138 | ||
139 | /* Search a string backward for a character. */ | |
140 | /* (This substitutes for strrchr, which some systems don't provide.) */ | |
141 | private char * | |
142 | rchr(char *str, char ch, int len) | |
caddbb58 | 143 | { |
144 | register char *p = str + len; | |
145 | ||
146 | while (p > str) | |
147 | if (*--p == ch) | |
148 | return p; | |
149 | return 0; | |
8e4ff0ae | 150 | } |
151 | ||
152 | /* Pop a directory from the enumeration stack. */ | |
153 | private bool | |
caddbb58 | 154 | popdir(file_enum * pfen) |
155 | { | |
156 | dirstack *d = pfen->dstack; | |
157 | ||
158 | if (d == 0) | |
159 | return false; | |
160 | pfen->dirp = d->entry; | |
161 | pfen->dstack = d->next; | |
162 | gs_free_object(pfen->memory, d, "gp_enumerate_files(popdir)"); | |
163 | return true; | |
8e4ff0ae | 164 | } |
165 | ||
166 | /* Initialize an enumeration. */ | |
167 | file_enum * | |
caddbb58 | 168 | gp_enumerate_files_init(const char *pat, uint patlen, gs_memory_t * mem) |
169 | { | |
170 | file_enum *pfen; | |
171 | char *p; | |
172 | char *work; | |
173 | ||
174 | /* Reject attempts to enumerate paths longer than the */ | |
175 | /* system-dependent limit. */ | |
176 | if (patlen > MAXPATHLEN) | |
177 | return 0; | |
8e4ff0ae | 178 | |
caddbb58 | 179 | /* Reject attempts to enumerate with a pattern containing zeroes. */ |
180 | { | |
181 | const char *p1; | |
8e4ff0ae | 182 | |
caddbb58 | 183 | for (p1 = pat; p1 < pat + patlen; p1++) |
184 | if (*p1 == 0) | |
8e4ff0ae | 185 | return 0; |
caddbb58 | 186 | } |
187 | /* >>> Should crunch strings of repeated "/"'s in pat to a single "/" | |
188 | * >>> to match stupid unix filesystem "conventions" */ | |
8e4ff0ae | 189 | |
caddbb58 | 190 | pfen = gs_alloc_struct(mem, file_enum, &st_file_enum, |
191 | "gp_enumerate_files"); | |
192 | if (pfen == 0) | |
193 | return 0; | |
8e4ff0ae | 194 | |
caddbb58 | 195 | /* pattern and work could be allocated as strings, */ |
196 | /* but it's simpler for GC and freeing to allocate them as bytes. */ | |
8e4ff0ae | 197 | |
caddbb58 | 198 | pfen->pattern = |
199 | (char *)gs_alloc_bytes(mem, patlen + 1, | |
200 | "gp_enumerate_files(pattern)"); | |
201 | if (pfen->pattern == 0) | |
202 | return 0; | |
203 | memcpy(pfen->pattern, pat, patlen); | |
204 | pfen->pattern[patlen] = 0; | |
8e4ff0ae | 205 | |
caddbb58 | 206 | work = (char *)gs_alloc_bytes(mem, MAXPATHLEN + 1, |
207 | "gp_enumerate_files(work)"); | |
208 | if (work == 0) | |
209 | return 0; | |
210 | pfen->work = work; | |
211 | ||
212 | p = work; | |
213 | memcpy(p, pat, patlen); | |
214 | p += patlen; | |
215 | *p = 0; | |
216 | ||
217 | /* Remove directory specifications beyond the first wild card. */ | |
218 | /* Some systems don't have strpbrk, so we code it open. */ | |
219 | p = pfen->work; | |
220 | while (!(*p == '*' || *p == '?' || *p == 0)) | |
221 | p++; | |
222 | while (!(*p == '/' || *p == 0)) | |
223 | p++; | |
224 | if (*p == '/') | |
8e4ff0ae | 225 | *p = 0; |
caddbb58 | 226 | /* Substring for first wildcard match */ |
227 | pfen->pathead = p - work; | |
228 | ||
229 | /* Select the next higher directory-level. */ | |
230 | p = rchr(work, '/', p - work); | |
231 | if (!p) { /* No directory specification */ | |
232 | work[0] = 0; | |
233 | pfen->worklen = 0; | |
234 | } else { | |
235 | if (p == work) { /* Root directory -- don't turn "/" into "" */ | |
236 | p++; | |
237 | } | |
238 | *p = 0; | |
239 | pfen->worklen = p - work; | |
240 | } | |
241 | ||
242 | pfen->memory = mem; | |
243 | pfen->dstack = 0; | |
244 | pfen->first_time = true; | |
245 | pfen->patlen = patlen; | |
246 | return pfen; | |
8e4ff0ae | 247 | } |
248 | ||
249 | /* Enumerate the next file. */ | |
250 | uint | |
caddbb58 | 251 | gp_enumerate_files_next(file_enum * pfen, char *ptr, uint maxlen) |
252 | { | |
253 | const dir_entry *de; | |
254 | char *work = pfen->work; | |
255 | int worklen = pfen->worklen; | |
256 | char *pattern = pfen->pattern; | |
257 | int pathead = pfen->pathead; | |
258 | int len; | |
259 | struct stat stbuf; | |
260 | ||
261 | if (pfen->first_time) { | |
262 | pfen->dirp = ((worklen == 0) ? opendir(".") : opendir(work)); | |
263 | if_debug1('e', "[e]file_enum:First-Open '%s'\n", work); | |
264 | pfen->first_time = false; | |
265 | if (pfen->dirp == 0) { /* first opendir failed */ | |
266 | gp_enumerate_files_close(pfen); | |
267 | return ~(uint) 0; | |
268 | } | |
269 | } | |
270 | top:de = readdir(pfen->dirp); | |
271 | if (de == 0) { /* No more entries in this directory */ | |
272 | char *p; | |
273 | ||
274 | if_debug0('e', "[e]file_enum:Closedir\n"); | |
275 | closedir(pfen->dirp); | |
276 | /* Back working directory and matching pattern up one level */ | |
277 | p = rchr(work, '/', worklen); | |
278 | if (p != 0) { | |
279 | if (p == work) | |
280 | p++; | |
281 | *p = 0; | |
282 | worklen = p - work; | |
283 | } else | |
284 | worklen = 0; | |
285 | p = rchr(pattern, '/', pathead); | |
286 | if (p != 0) | |
287 | pathead = p - pattern; | |
8e4ff0ae | 288 | else |
caddbb58 | 289 | pathead = 0; |
290 | ||
291 | if (popdir(pfen)) { /* Back up the directory tree. */ | |
292 | if_debug1('e', "[e]file_enum:Dir popped '%s'\n", work); | |
293 | goto top; | |
294 | } else { | |
295 | if_debug0('e', "[e]file_enum:Dirstack empty\n"); | |
296 | gp_enumerate_files_close(pfen); | |
297 | return ~(uint) 0; | |
298 | } | |
299 | } | |
300 | /* Skip . and .. */ | |
301 | len = strlen(de->d_name); | |
302 | if (len <= 2 && (!strcmp(de->d_name, ".") || !strcmp(de->d_name, ".."))) | |
303 | goto top; | |
304 | if (len + worklen + 1 > MAXPATHLEN) | |
305 | /* Should be an error, I suppose */ | |
306 | goto top; | |
307 | if (worklen == 0) { /* "Current" directory (evil un*x kludge) */ | |
308 | memcpy(work, de->d_name, len + 1); | |
309 | } else if (worklen == 1 && work[0] == '/') { /* Root directory */ | |
310 | memcpy(work + 1, de->d_name, len + 1); | |
311 | len = len + 1; | |
312 | } else { | |
313 | work[worklen] = '/'; | |
314 | memcpy(work + worklen + 1, de->d_name, len + 1); | |
315 | len = worklen + 1 + len; | |
316 | } | |
317 | ||
318 | /* Test for a match at this directory level */ | |
319 | if (!string_match((byte *) work, len, (byte *) pattern, pathead, NULL)) | |
320 | goto top; | |
321 | ||
322 | /* Perhaps descend into subdirectories */ | |
323 | if (pathead < pfen->patlen) { | |
324 | DIR *dp; | |
325 | ||
326 | if (((stat(work, &stbuf) >= 0) | |
327 | ? !stat_is_dir(stbuf) | |
328 | /* Couldn't stat it. | |
329 | * Well, perhaps it's a directory and | |
330 | * we'll be able to list it anyway. | |
331 | * If it isn't or we can't, no harm done. */ | |
332 | : 0)) | |
333 | goto top; | |
334 | ||
335 | if (pfen->patlen == pathead + 1) { /* Listing "foo/?/" -- return this entry */ | |
336 | /* if it's a directory. */ | |
337 | if (!stat_is_dir(stbuf)) { /* Do directoryp test the hard way */ | |
8e4ff0ae | 338 | dp = opendir(work); |
caddbb58 | 339 | if (!dp) |
340 | goto top; | |
341 | closedir(dp); | |
342 | } | |
343 | work[len++] = '/'; | |
344 | goto winner; | |
345 | } | |
346 | /* >>> Should optimise the case in which the next level */ | |
347 | /* >>> of directory has no wildcards. */ | |
348 | dp = opendir(work); | |
8e4ff0ae | 349 | #ifdef DEBUG |
caddbb58 | 350 | { |
351 | char save_end = pattern[pathead]; | |
352 | ||
353 | pattern[pathead] = 0; | |
354 | if_debug2('e', "[e]file_enum:fname='%s', p='%s'\n", | |
355 | work, pattern); | |
356 | pattern[pathead] = save_end; | |
357 | } | |
8e4ff0ae | 358 | #endif /* DEBUG */ |
caddbb58 | 359 | if (!dp) |
360 | /* Can't list this one */ | |
361 | goto top; | |
362 | else { /* Advance to the next directory-delimiter */ | |
363 | /* in pattern */ | |
364 | char *p; | |
365 | dirstack *d; | |
366 | ||
367 | for (p = pattern + pathead + 1;; p++) { | |
368 | if (*p == 0) { /* No more subdirectories to match */ | |
369 | pathead = pfen->patlen; | |
370 | break; | |
371 | } else if (*p == '/') { | |
372 | pathead = p - pattern; | |
373 | break; | |
374 | } | |
375 | } | |
376 | ||
377 | /* Push a directory onto the enumeration stack. */ | |
378 | d = gs_alloc_struct(pfen->memory, dirstack, | |
379 | &st_dirstack, | |
380 | "gp_enumerate_files(pushdir)"); | |
381 | if (d != 0) { | |
382 | d->next = pfen->dstack; | |
383 | d->entry = pfen->dirp; | |
384 | pfen->dstack = d; | |
385 | } else | |
386 | DO_NOTHING; /* >>> e_VMerror!!! */ | |
387 | ||
388 | if_debug1('e', "[e]file_enum:Dir pushed '%s'\n", | |
389 | work); | |
390 | worklen = len; | |
391 | pfen->dirp = dp; | |
392 | goto top; | |
393 | } | |
394 | } | |
395 | winner: | |
396 | /* We have a winner! */ | |
397 | pfen->worklen = worklen; | |
398 | pfen->pathead = pathead; | |
399 | memcpy(ptr, work, len); | |
400 | return len; | |
8e4ff0ae | 401 | } |
402 | ||
403 | /* Clean up the file enumeration. */ | |
404 | void | |
caddbb58 | 405 | gp_enumerate_files_close(file_enum * pfen) |
406 | { | |
407 | gs_memory_t *mem = pfen->memory; | |
408 | ||
409 | if_debug0('e', "[e]file_enum:Cleanup\n"); | |
410 | while (popdir(pfen)) /* clear directory stack */ | |
411 | DO_NOTHING; | |
412 | gs_free_object(mem, (byte *) pfen->work, | |
413 | "gp_enumerate_close(work)"); | |
414 | gs_free_object(mem, (byte *) pfen->pattern, | |
415 | "gp_enumerate_files_close(pattern)"); | |
416 | gs_free_object(mem, pfen, "gp_enumerate_files_close"); | |
8e4ff0ae | 417 | } |
418 | ||
419 | /* Test-cases: | |
420 | (../?*r*?/?*.ps) {==} 100 string filenameforall | |
421 | (../?*r*?/?*.ps*) {==} 100 string filenameforall | |
422 | (../?*r*?/) {==} 100 string filenameforall | |
423 | (/t*?/?*.ps) {==} 100 string filenameforall | |
caddbb58 | 424 | */ |