filename.c 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497
  1. /* filename.c Copyright (C) 1995-2002 Codemist Ltd */
  2. /*
  3. * Map file-names to expand references to shell variables etc.
  4. * and to provide portability of names across operating systems.
  5. */
  6. /*
  7. * This code may be used and modified, and redistributed in binary
  8. * or source form, subject to the "CCL Public License", which should
  9. * accompany it. This license is a variant on the BSD license, and thus
  10. * permits use of code derived from this in either open and commercial
  11. * projects: but it does require that updates to this code be made
  12. * available back to the originators of the package.
  13. * Before merging other code in with this or linking this code
  14. * with other packages or libraries please check that the license terms
  15. * of the other material are compatible with those of this.
  16. */
  17. /* Signature: 1705ecf5 10-Oct-2002 */
  18. static char *look_in_lisp_variable(char *o, int prefix)
  19. {
  20. Lisp_Object nil, var;
  21. /*
  22. * I will start by tagging a '$' (or whatever) on in front of the
  23. * parameter name.
  24. */
  25. o[0] = (char)prefix;
  26. var = make_undefined_symbol(o);
  27. nil = C_nil;
  28. /*
  29. * make_undefined_symbol() could fail either if we had utterly run out
  30. * of memory or if somebody generated an interrupt (eg ^C) around now. Ugh.
  31. */
  32. if (exception_pending())
  33. { flip_exception();
  34. return NULL;
  35. }
  36. /*
  37. * If the variable $name was undefined then I use an empty replacement
  38. * text for it. Otherwise I need to look harder at its value.
  39. */
  40. if (qvalue(var) == unset_var) return o;
  41. else
  42. { Header h;
  43. intxx len;
  44. var = qvalue(var);
  45. /*
  46. * Mostly I expect that the value will be a string or symbol.
  47. */
  48. #ifdef COMMON
  49. if (complex_stringp(var))
  50. { var = simplify_string(var);
  51. nil = C_nil;
  52. if (exception_pending())
  53. { flip_exception();
  54. return NULL;
  55. }
  56. }
  57. #endif /* COMMON */
  58. if (symbolp(var))
  59. { var = get_pname(var);
  60. nil = C_nil;
  61. if (exception_pending())
  62. { flip_exception();
  63. return NULL;
  64. }
  65. h = vechdr(var);
  66. }
  67. else if (!is_vector(var) ||
  68. type_of_header(h = vechdr(var)) != TYPE_STRING)
  69. return NULL;
  70. len = length_of_header(h) - CELL;
  71. /*
  72. * Copy the characters from the string or from the name of the variable
  73. * into the file-name buffer. There could at present be a crash here
  74. * if the expansion was very very long and overflowed my buffer. Tough
  75. * luck for now - people doing that (maybe) get what they (maybe) deserve.
  76. */
  77. memcpy(o, (char *)var + (CELL - TAG_VECTOR), (size_t)len);
  78. o = o + len;
  79. return o;
  80. }
  81. }
  82. static void process_file_name(char *filename, char *old, size_t n)
  83. /*
  84. * This procedure maps filenames by expanding some environment
  85. * variables. It is very thoroughly system specific, which is why it
  86. * is in this file. See also LONGEST_LEGAL_FILENAME in "tags.h" for a
  87. * limit on the permitted size of an expanded filename.
  88. * The input (old) is not necessarily properly terminated as a C string,
  89. * so n says how many characters to inspect. Build a converted name
  90. * in filename.
  91. * At present the expansions I allow are:
  92. *
  93. * $xxx (terminated by '.', '/' or '\' with at least one char x)
  94. * ${xxx} (self-terminating)
  95. * First check for a Lisp variable $xxx. If this is set (and is
  96. * a string or a symbol) then its value is used. If not then
  97. * next inspect the environment variable xxx and dump its
  98. * value into the output. If the variable is unset then a check
  99. * is made for the value of a global lisp variable called @xxx,
  100. * and if that exists and is a string or symbol it is used.
  101. * If @xxx is undefined a null string is inserted.
  102. * If one of the variables is defined but has an improper value
  103. * then the whole file-translation fails.
  104. * The use of two Lisp variables makes it possible to control
  105. * precedence between these and shell variables.
  106. *
  107. * ~ ) followed by '.', '/' or '\'
  108. * ~xxx )
  109. * On Unix these try to find home directories using
  110. * getpwuid(getuid()) for '~' and getpwnam() for ~xxx.
  111. * If that fails ~ expands into nothing at all.
  112. * This syntax is only recognised at the very start of a file-name.
  113. * For systems other than Unix this syntax will not be useful and
  114. * should be avoided, however as an experimental place-holder I
  115. * may do things with environment variables called HOME etc.
  116. *
  117. *
  118. * I convert file-names of the form aaa/bbb/ccc.ddd into something
  119. * acceptable to the system being used, even though this may result in
  120. * some native file titles that include '/' characters becoming unavailable.
  121. * The reasoning here is that scripts and programs can then use Unix-like
  122. * names and non-Unix hosts will treat them forgivingly.
  123. *
  124. *
  125. */
  126. #ifdef __vmsvax__
  127. /*
  128. * This is maybe going to be a mess under VAX/VMS, but I will try
  129. * pretending that is still Unix for now since the VMS C runtime system
  130. * seems prepared to help a little in that case.
  131. */
  132. #endif /* __vmsvax__ */
  133. {
  134. int32 i, j;
  135. int c;
  136. char *o, *tail;
  137. if (n == 0)
  138. { *filename = 0;
  139. return; /* deem zero-length name to be illegal */
  140. }
  141. o = filename;
  142. c = *old;
  143. /*
  144. * First I deal with a leading "~"
  145. */
  146. if (c == '~')
  147. { old++;
  148. n--;
  149. while (n != 0)
  150. { c = *old;
  151. if (c == '.' || c == '/' || c == '\\') break;
  152. old++;
  153. n--;
  154. *o++ = (char)c;
  155. }
  156. *o = 0;
  157. /*
  158. * actually deciding what the home directory is is passed down to a
  159. * system-specific call, but it is not to be relied upon especially
  160. * on personal computers.
  161. */
  162. if (o == filename) /* '~' on its own */
  163. { get_home_directory(filename, LONGEST_LEGAL_FILENAME);
  164. o = filename + strlen(filename);
  165. }
  166. else
  167. { get_users_home_directory(filename, LONGEST_LEGAL_FILENAME);
  168. o = filename + strlen(filename);
  169. }
  170. }
  171. /*
  172. * Having copies a user-name across (if there was one) I now copy the
  173. * rest of the file-name, expanding $xxx and ${xxx} as necessary.
  174. */
  175. while (n != 0)
  176. { c = *old++;
  177. n--;
  178. /*
  179. * If I find a "$" that is either at the end of the file-name or that is
  180. * immediately followed by ".", "/" or "\" then I will not use it for
  181. * parameter expansion. This at least gives me some help with the RISCOS
  182. * file-name $.abc.def where the "$" is used to indicate the root of the
  183. * current disc.
  184. */
  185. if (c == '$' && n != 0 &&
  186. (c = *old) != '.' && c != '/' && c != '\\')
  187. { char *p = o, *w;
  188. /*
  189. * I collect the name of the parameter at the end of my file-name buffer,
  190. * but will over-write it later on when I actually do the expansion.
  191. */
  192. if (c == '{')
  193. { old++;
  194. n--;
  195. while (n != 0)
  196. { c = *old++;
  197. n--;
  198. if (c == '}') break;
  199. *p++ = (char)c;
  200. }
  201. }
  202. else
  203. { while (n != 0)
  204. { c = *old;
  205. if (c == '.' || c == '/' || c == '\\') break;
  206. old++;
  207. n--;
  208. *p++ = (char)c;
  209. }
  210. }
  211. *p = 0;
  212. i = strlen(o) + 2;
  213. while (i-- != 0) o[i] = o[i-1];
  214. if ((p = look_in_lisp_variable(o, '$')) != NULL &&
  215. p != o) o = p;
  216. else if ((w = my_getenv(o+1)) != NULL) /* Shell variable? */
  217. { strcpy(o, w);
  218. o = o + strlen(o);
  219. }
  220. else if ((p = look_in_lisp_variable(o, '@')) != NULL)
  221. o = p;
  222. else
  223. { *filename = 0; /* return reporting failure */
  224. return;
  225. }
  226. }
  227. else *o++ = (char)c;
  228. }
  229. *o = 0;
  230. #ifdef NOT_TOTALLY_DEBUGGED
  231. term_printf("[temp trace] File-name expands to \"%s\"\n", filename);
  232. #endif
  233. #ifdef MS_DOS
  234. /*
  235. * Now the filename has had $ and ~ prefix things expanded - I "just"
  236. * need to deal with sub-directory representation issues. Specifically I need
  237. * to map "/" separators into "\" so that if a user presents a file
  238. * name such as aaa/bbb/ccc.d it gets passed to the operating system
  239. * as aaa\bbb\ccc.d
  240. * NOte that I enable this code under the heading MS_DOS but really it
  241. * means any file-system (eg Windows too) that uses "\" as its main
  242. * directory separator character.
  243. */
  244. /*
  245. * I map / characters in MSDOS filenames into \s, so that users
  246. * can give file names with Unix-like slashes as separators if they want.
  247. * People who WANT to use filenames with '/' in them will be hurt.
  248. */
  249. tail = filename;
  250. while ((j = *tail) != 0)
  251. { if (j == '/') *tail = '\\';
  252. tail++;
  253. }
  254. /*
  255. * stat and friends do not like directories referred to as "\foo\", so check
  256. * for a trailing slash, being careful to respect directories with names
  257. * like "\" and "a:\".
  258. */
  259. j = strlen(filename);
  260. if (j > 0 && j != 1 && !(j == 3 && *(filename+1) == ':'))
  261. {
  262. if ( (*(tail - 1) == '\\')) *(tail - 1) = 0;
  263. }
  264. #ifdef __WATCOMC__
  265. /*
  266. * There is a bug in the stat function under some releases of Watcom C, where:
  267. * stat("\\foo\\..", ...);
  268. * fails with errno=-1. So we delete trailing ".." segments.
  269. */
  270. if (filename[0] == '\\' || filename[1] == ':')
  271. { j = strlen(filename);
  272. while (filename[j-1] == '.' && filename[j-2] == '.')
  273. { tail = strrchr(filename, '\\');
  274. /*
  275. * Warning - the aerror0() function sets an internal flag to indicate
  276. * that something went wrong, and then returns. Thus if further processing
  277. * is not valid in one of these cases some explicit control flow (maybe a
  278. * "return") is called for,
  279. */
  280. if (tail == NULL)
  281. aerror0("Unexpected pathname - this error should never happen");
  282. else *tail = '\0';
  283. tail = strrchr(filename, '\\');
  284. if (tail == NULL)
  285. aerror0("Unexpected pathname - this error should never happen");
  286. else *tail = '\0';
  287. j = strlen(filename);
  288. }
  289. /* Make sure we don't have an empty string or just a drive */
  290. if (j == 0) strcpy(filename,"\\");
  291. else if (j==2 && filename[1] == ':') strcat(filename,"\\");
  292. }
  293. #endif /* __WATCOMC__ */
  294. #ifdef EIGHT_PLUS_THREE
  295. /*
  296. * A *NASTY* hack here. I will explicitly truncate the name down to
  297. * and 8+3 format to keep as much DOS compatibility as I (in)conveniently can.
  298. * This is done here because if a user attempts to open a file with a long
  299. * name Windows 95 will try to honour the request and will then get confused
  300. * if old-style W3.x or DOS utilities made the file with a truncated name.
  301. * I rather think that this ought not to be wanted any more, especially if
  302. * it is possible to accept that raw DOS and Windows before 95 need not
  303. * be supported, but I will leave this in the code just in case!
  304. */
  305. tail = filename;
  306. eight_plus_three(tail);
  307. while ((j=*tail++)!=0) if (j=='\\' || j==':') eight_plus_three(tail);
  308. #endif /* EIGHT_PLUS_THREE */
  309. #endif /* MS_DOS */
  310. #ifdef MACINTOSH
  311. /*
  312. * Now the filename has had $ and ~ prefix things expanded - I "just"
  313. * need to deal with sub-directory representation issues. Specifically I need
  314. * to map "/" separators into "\" so that if a user presents a file
  315. * name such as aaa/bbb/ccc.d it gets passed to the operating system
  316. * as aaa\bbb\ccc.d
  317. * NOte that I enable this code under the heading MS_DOS but really it
  318. * means any file-system (eg Windows too) that uses "\" as its main
  319. * directory separator character.
  320. */
  321. /*
  322. * I map '/'characters in Macintosh filenames into ':'s, so that users
  323. * can give file names with Unix-like slashes as separators if they want.
  324. * People who WANT to use filenames with '/' in them will be hurt.
  325. * Furthermore if the name originally had no colons in it a leading colon is
  326. * added, and if it originally started with a '/' (for a Unix fully rooted name)
  327. * then the leading ':' or '/' is removed.
  328. */
  329. tail = filename;
  330. while ((j = *tail) != 0 && j != ':') tail++;
  331. if (j == 0)
  332. { memmove(&filename[1], filename, 1+strlen(filename));
  333. filename[0] = ':';
  334. }
  335. if (filename[0] == '/') memmove(filename, &filename[1], strlen(filename));
  336. tail = filename;
  337. while ((j = *tail) != 0)
  338. { if (j == '/') *tail = ':';
  339. tail++;
  340. }
  341. /*
  342. * I map the string :..: onto just :: to cope with Unix-format references to
  343. * the parent directory
  344. */
  345. i = 0;
  346. while ((c = filename[i]) != 0)
  347. { if (c == ':' &&
  348. filename[i+1] == '.' &&
  349. filename[i+2] == '.' &&
  350. filename[i+3] == ':')
  351. { j = i+1;
  352. do
  353. { c = filename[j+2];
  354. filename[j++] = c;
  355. } while (c != 0);
  356. }
  357. i++;
  358. }
  359. #endif /* MACINTOSH */
  360. #ifdef RISCOS
  361. /*
  362. * Now the filename has had $ and ~ prefix things expanded - I "just"
  363. * need to deal with sub-directory representation issues.
  364. */
  365. /*
  366. * The Archimedes is best coped with by re-mapping file names
  367. * so that xxxx.y sometimes becomes y.xxxx
  368. */
  369. i = strlen(filename);
  370. for (j=i-1; j>=0; j--) if (filename[j] == '.') break;
  371. if (j >= 0) /* No '.' => no possible conversion */
  372. { tail = &filename[j+1];
  373. if (j == i - 2 || /* one character suffix */
  374. /*
  375. * At present my policy is that any file with a one-character final
  376. * component gets mangled, and that as a special case files of
  377. * the form xxx.lsp, xxx.red, xxx.fsl and xxx.log are also flipped.
  378. */
  379. strcmp(tail, "lsp") == 0 ||
  380. strcmp(tail, "red") == 0 ||
  381. strcmp(tail, "fsl") == 0 ||
  382. strcmp(tail, "tst") == 0 ||
  383. strcmp(tail, "sl") == 0 ||
  384. strcmp(tail, "log") == 0)
  385. { int32 k;
  386. char suffix[8];
  387. for (k=j-1; k>=0; k--)
  388. if (filename[k] == '.' || filename[k] == '/') break;
  389. strcpy(suffix, tail);
  390. strcat(suffix, ".");
  391. do filename[--i] = filename[--j]; while (j > k);
  392. memcpy(&filename[k+1], suffix, (size_t)(i - j));
  393. }
  394. }
  395. /*
  396. * Now if in the Unix world I had a component '..' in the file it will
  397. * appear something like //.aaa.bbb or aaa.//.bbb
  398. * Similarly I map an isolated '.' (now an isolated '/') into '@'.
  399. */
  400. { int32 k = 0;
  401. j = -1;
  402. c = '/';
  403. for (;;)
  404. { if (c == '/' || c == 0)
  405. { if (j == k+1 && filename[k] == '.') filename[k] = '@';
  406. else if (j == k + 2 && filename[k] == '.' && filename[k+1] == '.')
  407. { int c1;
  408. filename[k++] = '^';
  409. do
  410. { c1 = filename[k+1];
  411. filename[k++] = c1;
  412. } while (c1 != 0);
  413. }
  414. k = j+1;
  415. }
  416. if (c == 0) break;
  417. j++;
  418. c = filename[j];
  419. }
  420. }
  421. /*
  422. * I map / characters in RISCOS filenames into dots, so that users
  423. * can give file names with Unix-like slashes as separators if they want.
  424. * People who WANT to use filenames with '/' in them will be hurt.
  425. * Note also that when files are created for output an attempt to open
  426. * (e.g.) "arthur.red" will fail unless the directory "red" already
  427. * exists.
  428. */
  429. tail = filename;
  430. while ((j = *tail) != 0)
  431. { if (j == '/') *tail = '.';
  432. tail++;
  433. }
  434. if (*filename == '.') /* Deal with fully-rooted Unix filenames */
  435. { tail[1] = 0;
  436. while (tail != filename)
  437. { tail--;
  438. tail[1] = tail[0];
  439. }
  440. tail[0] = '$';
  441. }
  442. #endif /* RISCOS */
  443. }
  444. FILE *open_file(char *filename, char *old, size_t n,
  445. char *mode, FILE *old_file)
  446. {
  447. /*
  448. * mode is something like "r" or "w" or "rb", as needed by fopen(),
  449. * and old_file is NULL normally, but can be a (FILE *) to indicate
  450. * the use of freopen rather than fopen.
  451. */
  452. process_file_name(filename, old, n);
  453. if (*filename == 0) return NULL;
  454. #ifdef NO_BINARY_FOPEN
  455. /*
  456. * On some Unix implementations (I mean DECs version on its MIPS workstations
  457. * and on the microvax I tried) the library does not support "rb" and "wb"
  458. * modes, so I work around that here. Explicit selection of binary file
  459. * access will be needed on some non-Unix operating systems, but should
  460. * never be relevant under Unix, hence my choice of a flag for the conditional
  461. * compilation here.
  462. */
  463. if (mode[0] == 'w')
  464. { if (mode[1] == '+') mode = "w+";
  465. else mode = "w";
  466. }
  467. else if (mode[1] == '+') mode = "r+";
  468. else mode = "r"; /* That ought to patch it up */
  469. #endif
  470. if (old_file == NULL) return fopen(filename, mode);
  471. else return freopen(filename, mode, old_file);
  472. }
  473. /* end of filename.c */