123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497 |
- /* filename.c Copyright (C) 1995-2002 Codemist Ltd */
- /*
- * Map file-names to expand references to shell variables etc.
- * and to provide portability of names across operating systems.
- */
- /*
- * This code may be used and modified, and redistributed in binary
- * or source form, subject to the "CCL Public License", which should
- * accompany it. This license is a variant on the BSD license, and thus
- * permits use of code derived from this in either open and commercial
- * projects: but it does require that updates to this code be made
- * available back to the originators of the package.
- * Before merging other code in with this or linking this code
- * with other packages or libraries please check that the license terms
- * of the other material are compatible with those of this.
- */
- /* Signature: 1705ecf5 10-Oct-2002 */
- static char *look_in_lisp_variable(char *o, int prefix)
- {
- Lisp_Object nil, var;
- /*
- * I will start by tagging a '$' (or whatever) on in front of the
- * parameter name.
- */
- o[0] = (char)prefix;
- var = make_undefined_symbol(o);
- nil = C_nil;
- /*
- * make_undefined_symbol() could fail either if we had utterly run out
- * of memory or if somebody generated an interrupt (eg ^C) around now. Ugh.
- */
- if (exception_pending())
- { flip_exception();
- return NULL;
- }
- /*
- * If the variable $name was undefined then I use an empty replacement
- * text for it. Otherwise I need to look harder at its value.
- */
- if (qvalue(var) == unset_var) return o;
- else
- { Header h;
- intxx len;
- var = qvalue(var);
- /*
- * Mostly I expect that the value will be a string or symbol.
- */
- #ifdef COMMON
- if (complex_stringp(var))
- { var = simplify_string(var);
- nil = C_nil;
- if (exception_pending())
- { flip_exception();
- return NULL;
- }
- }
- #endif /* COMMON */
- if (symbolp(var))
- { var = get_pname(var);
- nil = C_nil;
- if (exception_pending())
- { flip_exception();
- return NULL;
- }
- h = vechdr(var);
- }
- else if (!is_vector(var) ||
- type_of_header(h = vechdr(var)) != TYPE_STRING)
- return NULL;
- len = length_of_header(h) - CELL;
- /*
- * Copy the characters from the string or from the name of the variable
- * into the file-name buffer. There could at present be a crash here
- * if the expansion was very very long and overflowed my buffer. Tough
- * luck for now - people doing that (maybe) get what they (maybe) deserve.
- */
- memcpy(o, (char *)var + (CELL - TAG_VECTOR), (size_t)len);
- o = o + len;
- return o;
- }
- }
- static void process_file_name(char *filename, char *old, size_t n)
- /*
- * This procedure maps filenames by expanding some environment
- * variables. It is very thoroughly system specific, which is why it
- * is in this file. See also LONGEST_LEGAL_FILENAME in "tags.h" for a
- * limit on the permitted size of an expanded filename.
- * The input (old) is not necessarily properly terminated as a C string,
- * so n says how many characters to inspect. Build a converted name
- * in filename.
- * At present the expansions I allow are:
- *
- * $xxx (terminated by '.', '/' or '\' with at least one char x)
- * ${xxx} (self-terminating)
- * First check for a Lisp variable $xxx. If this is set (and is
- * a string or a symbol) then its value is used. If not then
- * next inspect the environment variable xxx and dump its
- * value into the output. If the variable is unset then a check
- * is made for the value of a global lisp variable called @xxx,
- * and if that exists and is a string or symbol it is used.
- * If @xxx is undefined a null string is inserted.
- * If one of the variables is defined but has an improper value
- * then the whole file-translation fails.
- * The use of two Lisp variables makes it possible to control
- * precedence between these and shell variables.
- *
- * ~ ) followed by '.', '/' or '\'
- * ~xxx )
- * On Unix these try to find home directories using
- * getpwuid(getuid()) for '~' and getpwnam() for ~xxx.
- * If that fails ~ expands into nothing at all.
- * This syntax is only recognised at the very start of a file-name.
- * For systems other than Unix this syntax will not be useful and
- * should be avoided, however as an experimental place-holder I
- * may do things with environment variables called HOME etc.
- *
- *
- * I convert file-names of the form aaa/bbb/ccc.ddd into something
- * acceptable to the system being used, even though this may result in
- * some native file titles that include '/' characters becoming unavailable.
- * The reasoning here is that scripts and programs can then use Unix-like
- * names and non-Unix hosts will treat them forgivingly.
- *
- *
- */
- #ifdef __vmsvax__
- /*
- * This is maybe going to be a mess under VAX/VMS, but I will try
- * pretending that is still Unix for now since the VMS C runtime system
- * seems prepared to help a little in that case.
- */
- #endif /* __vmsvax__ */
- {
- int32 i, j;
- int c;
- char *o, *tail;
- if (n == 0)
- { *filename = 0;
- return; /* deem zero-length name to be illegal */
- }
- o = filename;
- c = *old;
- /*
- * First I deal with a leading "~"
- */
- if (c == '~')
- { old++;
- n--;
- while (n != 0)
- { c = *old;
- if (c == '.' || c == '/' || c == '\\') break;
- old++;
- n--;
- *o++ = (char)c;
- }
- *o = 0;
- /*
- * actually deciding what the home directory is is passed down to a
- * system-specific call, but it is not to be relied upon especially
- * on personal computers.
- */
- if (o == filename) /* '~' on its own */
- { get_home_directory(filename, LONGEST_LEGAL_FILENAME);
- o = filename + strlen(filename);
- }
- else
- { get_users_home_directory(filename, LONGEST_LEGAL_FILENAME);
- o = filename + strlen(filename);
- }
- }
- /*
- * Having copies a user-name across (if there was one) I now copy the
- * rest of the file-name, expanding $xxx and ${xxx} as necessary.
- */
- while (n != 0)
- { c = *old++;
- n--;
- /*
- * If I find a "$" that is either at the end of the file-name or that is
- * immediately followed by ".", "/" or "\" then I will not use it for
- * parameter expansion. This at least gives me some help with the RISCOS
- * file-name $.abc.def where the "$" is used to indicate the root of the
- * current disc.
- */
- if (c == '$' && n != 0 &&
- (c = *old) != '.' && c != '/' && c != '\\')
- { char *p = o, *w;
- /*
- * I collect the name of the parameter at the end of my file-name buffer,
- * but will over-write it later on when I actually do the expansion.
- */
- if (c == '{')
- { old++;
- n--;
- while (n != 0)
- { c = *old++;
- n--;
- if (c == '}') break;
- *p++ = (char)c;
- }
- }
- else
- { while (n != 0)
- { c = *old;
- if (c == '.' || c == '/' || c == '\\') break;
- old++;
- n--;
- *p++ = (char)c;
- }
- }
- *p = 0;
- i = strlen(o) + 2;
- while (i-- != 0) o[i] = o[i-1];
- if ((p = look_in_lisp_variable(o, '$')) != NULL &&
- p != o) o = p;
- else if ((w = my_getenv(o+1)) != NULL) /* Shell variable? */
- { strcpy(o, w);
- o = o + strlen(o);
- }
- else if ((p = look_in_lisp_variable(o, '@')) != NULL)
- o = p;
- else
- { *filename = 0; /* return reporting failure */
- return;
- }
- }
- else *o++ = (char)c;
- }
- *o = 0;
- #ifdef NOT_TOTALLY_DEBUGGED
- term_printf("[temp trace] File-name expands to \"%s\"\n", filename);
- #endif
- #ifdef MS_DOS
- /*
- * Now the filename has had $ and ~ prefix things expanded - I "just"
- * need to deal with sub-directory representation issues. Specifically I need
- * to map "/" separators into "\" so that if a user presents a file
- * name such as aaa/bbb/ccc.d it gets passed to the operating system
- * as aaa\bbb\ccc.d
- * NOte that I enable this code under the heading MS_DOS but really it
- * means any file-system (eg Windows too) that uses "\" as its main
- * directory separator character.
- */
-
- /*
- * I map / characters in MSDOS filenames into \s, so that users
- * can give file names with Unix-like slashes as separators if they want.
- * People who WANT to use filenames with '/' in them will be hurt.
- */
- tail = filename;
- while ((j = *tail) != 0)
- { if (j == '/') *tail = '\\';
- tail++;
- }
- /*
- * stat and friends do not like directories referred to as "\foo\", so check
- * for a trailing slash, being careful to respect directories with names
- * like "\" and "a:\".
- */
- j = strlen(filename);
- if (j > 0 && j != 1 && !(j == 3 && *(filename+1) == ':'))
- {
- if ( (*(tail - 1) == '\\')) *(tail - 1) = 0;
- }
- #ifdef __WATCOMC__
- /*
- * There is a bug in the stat function under some releases of Watcom C, where:
- * stat("\\foo\\..", ...);
- * fails with errno=-1. So we delete trailing ".." segments.
- */
- if (filename[0] == '\\' || filename[1] == ':')
- { j = strlen(filename);
- while (filename[j-1] == '.' && filename[j-2] == '.')
- { tail = strrchr(filename, '\\');
- /*
- * Warning - the aerror0() function sets an internal flag to indicate
- * that something went wrong, and then returns. Thus if further processing
- * is not valid in one of these cases some explicit control flow (maybe a
- * "return") is called for,
- */
- if (tail == NULL)
- aerror0("Unexpected pathname - this error should never happen");
- else *tail = '\0';
- tail = strrchr(filename, '\\');
- if (tail == NULL)
- aerror0("Unexpected pathname - this error should never happen");
- else *tail = '\0';
- j = strlen(filename);
- }
- /* Make sure we don't have an empty string or just a drive */
- if (j == 0) strcpy(filename,"\\");
- else if (j==2 && filename[1] == ':') strcat(filename,"\\");
- }
- #endif /* __WATCOMC__ */
- #ifdef EIGHT_PLUS_THREE
- /*
- * A *NASTY* hack here. I will explicitly truncate the name down to
- * and 8+3 format to keep as much DOS compatibility as I (in)conveniently can.
- * This is done here because if a user attempts to open a file with a long
- * name Windows 95 will try to honour the request and will then get confused
- * if old-style W3.x or DOS utilities made the file with a truncated name.
- * I rather think that this ought not to be wanted any more, especially if
- * it is possible to accept that raw DOS and Windows before 95 need not
- * be supported, but I will leave this in the code just in case!
- */
- tail = filename;
- eight_plus_three(tail);
- while ((j=*tail++)!=0) if (j=='\\' || j==':') eight_plus_three(tail);
- #endif /* EIGHT_PLUS_THREE */
- #endif /* MS_DOS */
- #ifdef MACINTOSH
- /*
- * Now the filename has had $ and ~ prefix things expanded - I "just"
- * need to deal with sub-directory representation issues. Specifically I need
- * to map "/" separators into "\" so that if a user presents a file
- * name such as aaa/bbb/ccc.d it gets passed to the operating system
- * as aaa\bbb\ccc.d
- * NOte that I enable this code under the heading MS_DOS but really it
- * means any file-system (eg Windows too) that uses "\" as its main
- * directory separator character.
- */
-
- /*
- * I map '/'characters in Macintosh filenames into ':'s, so that users
- * can give file names with Unix-like slashes as separators if they want.
- * People who WANT to use filenames with '/' in them will be hurt.
- * Furthermore if the name originally had no colons in it a leading colon is
- * added, and if it originally started with a '/' (for a Unix fully rooted name)
- * then the leading ':' or '/' is removed.
- */
- tail = filename;
- while ((j = *tail) != 0 && j != ':') tail++;
- if (j == 0)
- { memmove(&filename[1], filename, 1+strlen(filename));
- filename[0] = ':';
- }
- if (filename[0] == '/') memmove(filename, &filename[1], strlen(filename));
- tail = filename;
- while ((j = *tail) != 0)
- { if (j == '/') *tail = ':';
- tail++;
- }
- /*
- * I map the string :..: onto just :: to cope with Unix-format references to
- * the parent directory
- */
- i = 0;
- while ((c = filename[i]) != 0)
- { if (c == ':' &&
- filename[i+1] == '.' &&
- filename[i+2] == '.' &&
- filename[i+3] == ':')
- { j = i+1;
- do
- { c = filename[j+2];
- filename[j++] = c;
- } while (c != 0);
- }
- i++;
- }
- #endif /* MACINTOSH */
- #ifdef RISCOS
- /*
- * Now the filename has had $ and ~ prefix things expanded - I "just"
- * need to deal with sub-directory representation issues.
- */
- /*
- * The Archimedes is best coped with by re-mapping file names
- * so that xxxx.y sometimes becomes y.xxxx
- */
-
- i = strlen(filename);
- for (j=i-1; j>=0; j--) if (filename[j] == '.') break;
- if (j >= 0) /* No '.' => no possible conversion */
- { tail = &filename[j+1];
- if (j == i - 2 || /* one character suffix */
- /*
- * At present my policy is that any file with a one-character final
- * component gets mangled, and that as a special case files of
- * the form xxx.lsp, xxx.red, xxx.fsl and xxx.log are also flipped.
- */
- strcmp(tail, "lsp") == 0 ||
- strcmp(tail, "red") == 0 ||
- strcmp(tail, "fsl") == 0 ||
- strcmp(tail, "tst") == 0 ||
- strcmp(tail, "sl") == 0 ||
- strcmp(tail, "log") == 0)
- { int32 k;
- char suffix[8];
- for (k=j-1; k>=0; k--)
- if (filename[k] == '.' || filename[k] == '/') break;
- strcpy(suffix, tail);
- strcat(suffix, ".");
- do filename[--i] = filename[--j]; while (j > k);
- memcpy(&filename[k+1], suffix, (size_t)(i - j));
- }
- }
- /*
- * Now if in the Unix world I had a component '..' in the file it will
- * appear something like //.aaa.bbb or aaa.//.bbb
- * Similarly I map an isolated '.' (now an isolated '/') into '@'.
- */
- { int32 k = 0;
- j = -1;
- c = '/';
- for (;;)
- { if (c == '/' || c == 0)
- { if (j == k+1 && filename[k] == '.') filename[k] = '@';
- else if (j == k + 2 && filename[k] == '.' && filename[k+1] == '.')
- { int c1;
- filename[k++] = '^';
- do
- { c1 = filename[k+1];
- filename[k++] = c1;
- } while (c1 != 0);
- }
- k = j+1;
- }
- if (c == 0) break;
- j++;
- c = filename[j];
- }
- }
- /*
- * I map / characters in RISCOS filenames into dots, so that users
- * can give file names with Unix-like slashes as separators if they want.
- * People who WANT to use filenames with '/' in them will be hurt.
- * Note also that when files are created for output an attempt to open
- * (e.g.) "arthur.red" will fail unless the directory "red" already
- * exists.
- */
- tail = filename;
- while ((j = *tail) != 0)
- { if (j == '/') *tail = '.';
- tail++;
- }
- if (*filename == '.') /* Deal with fully-rooted Unix filenames */
- { tail[1] = 0;
- while (tail != filename)
- { tail--;
- tail[1] = tail[0];
- }
- tail[0] = '$';
- }
- #endif /* RISCOS */
- }
- FILE *open_file(char *filename, char *old, size_t n,
- char *mode, FILE *old_file)
- {
- /*
- * mode is something like "r" or "w" or "rb", as needed by fopen(),
- * and old_file is NULL normally, but can be a (FILE *) to indicate
- * the use of freopen rather than fopen.
- */
- process_file_name(filename, old, n);
- if (*filename == 0) return NULL;
- #ifdef NO_BINARY_FOPEN
- /*
- * On some Unix implementations (I mean DECs version on its MIPS workstations
- * and on the microvax I tried) the library does not support "rb" and "wb"
- * modes, so I work around that here. Explicit selection of binary file
- * access will be needed on some non-Unix operating systems, but should
- * never be relevant under Unix, hence my choice of a flag for the conditional
- * compilation here.
- */
- if (mode[0] == 'w')
- { if (mode[1] == '+') mode = "w+";
- else mode = "w";
- }
- else if (mode[1] == '+') mode = "r+";
- else mode = "r"; /* That ought to patch it up */
- #endif
- if (old_file == NULL) return fopen(filename, mode);
- else return freopen(filename, mode, old_file);
- }
- /* end of filename.c */
|