/* Copyright (C) 1993, 2000 artofcode LLC. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA, 02111-1307. */ /*$Id: gp_unifs.c,v 1.6.2.2.2.1 2003/01/17 00:49:02 giles Exp $ */ /* "Unix-like" file system platform routines for Ghostscript */ #include "memory_.h" #include "string_.h" #include "gx.h" #include "gp.h" #include "gpmisc.h" #include "gsstruct.h" #include "gsutil.h" /* for string_match */ #include "stat_.h" #include "dirent_.h" #include "unistd_.h" #include /* for mkstemp/mktemp */ #include /* for MAXPATHLEN */ /* Some systems (Interactive for example) don't define MAXPATHLEN, * so we define it here. (This probably should be done via a Config-Script.) */ #ifndef MAXPATHLEN # define MAXPATHLEN 1024 #endif /* Library routines not declared in a standard header */ extern char *mktemp(P1(char *)); /* ------ File naming and accessing ------ */ /* Define the default scratch file name prefix. */ const char gp_scratch_file_name_prefix[] = "gs_"; /* Define the name of the null output file. */ const char gp_null_file_name[] = "/dev/null"; /* Define the name that designates the current directory. */ const char gp_current_directory_name[] = "."; /* Create and open a scratch file with a given name prefix. */ /* Write the actual file name at fname. */ FILE * gp_open_scratch_file(const char *prefix, char fname[gp_file_name_sizeof], const char *mode) { /* The -8 is for XXXXXX plus a possible final / and -. */ int prefix_length = strlen(prefix); int len = gp_file_name_sizeof - prefix_length - 8; if (gp_pathstring_not_bare(prefix, prefix_length)) *fname = 0; else if (gp_gettmpdir(fname, &len) != 0) strcpy(fname, "/tmp/"); else { if (strlen(fname) != 0 && fname[strlen(fname) - 1] != '/') strcat(fname, "/"); } if (strlen(fname) + prefix_length + 8 >= gp_file_name_sizeof) return 0; /* file name too long */ strcat(fname, prefix); /* Prevent trailing X's in path from being converted by mktemp. */ if (*fname != 0 && fname[strlen(fname) - 1] == 'X') strcat(fname, "-"); strcat(fname, "XXXXXX"); #ifdef HAVE_MKSTEMP { int file; FILE *fp; file = mkstemp(fname); if (file < -1) return NULL; fp = fdopen(file, mode); if (fp == NULL) close(file); return fp; } #else mktemp(fname); return gp_fopentemp(fname, mode); #endif } /* Open a file with the given name, as a stream of uninterpreted bytes. */ FILE * gp_fopen(const char *fname, const char *mode) { return fopen(fname, mode); } /* Set a file into binary or text mode. */ int gp_setmode_binary(FILE * pfile, bool mode) { return 0; /* Noop under Unix */ } /* ------ File enumeration ------ */ /* Thanks to Fritz Elfert (Fritz_Elfert@wue.maus.de) for */ /* the original version of the following code, and Richard Mlynarik */ /* (mly@adoc.xerox.com) for an improved version. */ typedef struct dirstack_s dirstack; struct dirstack_s { dirstack *next; DIR *entry; }; gs_private_st_ptrs1(st_dirstack, dirstack, "dirstack", dirstack_enum_ptrs, dirstack_reloc_ptrs, next); struct file_enum_s { DIR *dirp; /* pointer to current open directory */ char *pattern; /* original pattern */ char *work; /* current path */ int worklen; /* strlen (work) */ dirstack *dstack; /* directory stack */ int patlen; int pathead; /* how much of pattern to consider * when listing files in current directory */ bool first_time; gs_memory_t *memory; }; gs_private_st_ptrs3(st_file_enum, struct file_enum_s, "file_enum", file_enum_enum_ptrs, file_enum_reloc_ptrs, pattern, work, dstack); /* Private procedures */ /* Do a wild-card match. */ #ifdef DEBUG private bool wmatch(const byte * str, uint len, const byte * pstr, uint plen, const string_match_params * psmp) { bool match = string_match(str, len, pstr, plen, psmp); if (gs_debug_c('e')) { int i; dlputs("[e]string_match(\""); for (i=0; i str) if (*--p == ch) return p; return 0; } /* Pop a directory from the enumeration stack. */ private bool popdir(file_enum * pfen) { dirstack *d = pfen->dstack; if (d == 0) return false; pfen->dirp = d->entry; pfen->dstack = d->next; gs_free_object(pfen->memory, d, "gp_enumerate_files(popdir)"); return true; } /* Initialize an enumeration. */ file_enum * gp_enumerate_files_init(const char *pat, uint patlen, gs_memory_t * mem) { file_enum *pfen; char *p; char *work; /* Reject attempts to enumerate paths longer than the */ /* system-dependent limit. */ if (patlen > MAXPATHLEN) return 0; /* Reject attempts to enumerate with a pattern containing zeroes. */ { const char *p1; for (p1 = pat; p1 < pat + patlen; p1++) if (*p1 == 0) return 0; } /* >>> Should crunch strings of repeated "/"'s in pat to a single "/" * >>> to match stupid unix filesystem "conventions" */ pfen = gs_alloc_struct(mem, file_enum, &st_file_enum, "gp_enumerate_files"); if (pfen == 0) return 0; /* pattern and work could be allocated as strings, */ /* but it's simpler for GC and freeing to allocate them as bytes. */ pfen->pattern = (char *)gs_alloc_bytes(mem, patlen + 1, "gp_enumerate_files(pattern)"); if (pfen->pattern == 0) return 0; memcpy(pfen->pattern, pat, patlen); pfen->pattern[patlen] = 0; work = (char *)gs_alloc_bytes(mem, MAXPATHLEN + 1, "gp_enumerate_files(work)"); if (work == 0) return 0; pfen->work = work; p = work; memcpy(p, pat, patlen); p += patlen; *p = 0; /* Remove directory specifications beyond the first wild card. */ /* Some systems don't have strpbrk, so we code it open. */ p = pfen->work; while (!(*p == '*' || *p == '?' || *p == 0)) p++; while (!(*p == '/' || *p == 0)) p++; if (*p == '/') *p = 0; /* Substring for first wildcard match */ pfen->pathead = p - work; /* Select the next higher directory-level. */ p = rchr(work, '/', p - work); if (!p) { /* No directory specification */ work[0] = 0; pfen->worklen = 0; } else { if (p == work) { /* Root directory -- don't turn "/" into "" */ p++; } *p = 0; pfen->worklen = p - work; } pfen->memory = mem; pfen->dstack = 0; pfen->first_time = true; pfen->patlen = patlen; return pfen; } /* Enumerate the next file. */ uint gp_enumerate_files_next(file_enum * pfen, char *ptr, uint maxlen) { const dir_entry *de; char *work = pfen->work; int worklen = pfen->worklen; char *pattern = pfen->pattern; int pathead = pfen->pathead; int len; struct stat stbuf; if (pfen->first_time) { pfen->dirp = ((worklen == 0) ? opendir(".") : opendir(work)); if_debug1('e', "[e]file_enum:First-Open '%s'\n", work); pfen->first_time = false; if (pfen->dirp == 0) { /* first opendir failed */ gp_enumerate_files_close(pfen); return ~(uint) 0; } } top:de = readdir(pfen->dirp); if (de == 0) { /* No more entries in this directory */ char *p; if_debug0('e', "[e]file_enum:Closedir\n"); closedir(pfen->dirp); /* Back working directory and matching pattern up one level */ p = rchr(work, '/', worklen); if (p != 0) { if (p == work) p++; *p = 0; worklen = p - work; } else worklen = 0; p = rchr(pattern, '/', pathead); if (p != 0) pathead = p - pattern; else pathead = 0; if (popdir(pfen)) { /* Back up the directory tree. */ if_debug1('e', "[e]file_enum:Dir popped '%s'\n", work); goto top; } else { if_debug0('e', "[e]file_enum:Dirstack empty\n"); gp_enumerate_files_close(pfen); return ~(uint) 0; } } /* Skip . and .. */ len = strlen(de->d_name); if (len <= 2 && (!strcmp(de->d_name, ".") || !strcmp(de->d_name, ".."))) goto top; if (len + worklen + 1 > MAXPATHLEN) /* Should be an error, I suppose */ goto top; if (worklen == 0) { /* "Current" directory (evil un*x kludge) */ memcpy(work, de->d_name, len + 1); } else if (worklen == 1 && work[0] == '/') { /* Root directory */ memcpy(work + 1, de->d_name, len + 1); len = len + 1; } else { work[worklen] = '/'; memcpy(work + worklen + 1, de->d_name, len + 1); len = worklen + 1 + len; } /* Test for a match at this directory level */ if (!string_match((byte *) work, len, (byte *) pattern, pathead, NULL)) goto top; /* Perhaps descend into subdirectories */ if (pathead < pfen->patlen) { DIR *dp; if (((stat(work, &stbuf) >= 0) ? !stat_is_dir(stbuf) /* Couldn't stat it. * Well, perhaps it's a directory and * we'll be able to list it anyway. * If it isn't or we can't, no harm done. */ : 0)) goto top; if (pfen->patlen == pathead + 1) { /* Listing "foo/?/" -- return this entry */ /* if it's a directory. */ if (!stat_is_dir(stbuf)) { /* Do directoryp test the hard way */ dp = opendir(work); if (!dp) goto top; closedir(dp); } work[len++] = '/'; goto winner; } /* >>> Should optimise the case in which the next level */ /* >>> of directory has no wildcards. */ dp = opendir(work); #ifdef DEBUG { char save_end = pattern[pathead]; pattern[pathead] = 0; if_debug2('e', "[e]file_enum:fname='%s', p='%s'\n", work, pattern); pattern[pathead] = save_end; } #endif /* DEBUG */ if (!dp) /* Can't list this one */ goto top; else { /* Advance to the next directory-delimiter */ /* in pattern */ char *p; dirstack *d; for (p = pattern + pathead + 1;; p++) { if (*p == 0) { /* No more subdirectories to match */ pathead = pfen->patlen; break; } else if (*p == '/') { pathead = p - pattern; break; } } /* Push a directory onto the enumeration stack. */ d = gs_alloc_struct(pfen->memory, dirstack, &st_dirstack, "gp_enumerate_files(pushdir)"); if (d != 0) { d->next = pfen->dstack; d->entry = pfen->dirp; pfen->dstack = d; } else DO_NOTHING; /* >>> e_VMerror!!! */ if_debug1('e', "[e]file_enum:Dir pushed '%s'\n", work); worklen = len; pfen->dirp = dp; goto top; } } winner: /* We have a winner! */ pfen->worklen = worklen; pfen->pathead = pathead; memcpy(ptr, work, len); return len; } /* Clean up the file enumeration. */ void gp_enumerate_files_close(file_enum * pfen) { gs_memory_t *mem = pfen->memory; if_debug0('e', "[e]file_enum:Cleanup\n"); while (popdir(pfen)) /* clear directory stack */ DO_NOTHING; gs_free_object(mem, (byte *) pfen->work, "gp_enumerate_close(work)"); gs_free_object(mem, (byte *) pfen->pattern, "gp_enumerate_files_close(pattern)"); gs_free_object(mem, pfen, "gp_enumerate_files_close"); } /* Test-cases: (../?*r*?/?*.ps) {==} 100 string filenameforall (../?*r*?/?*.ps*) {==} 100 string filenameforall (../?*r*?/) {==} 100 string filenameforall (/t*?/?*.ps) {==} 100 string filenameforall */