/* 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 */