1 /*
2 * Pathnames for CLISP
3 * Bruno Haible 1990-2013, 2016-2018
4 * Logical Pathnames: Marcus Daniels 16.9.1994
5 * ANSI compliance, bugs: Sam Steingold 1998-2013, 2016-2017
6 * German comments translated into English: Stefan Kain 2002-01-03
7 */
8
9 #include "lispbibl.c"
10 #ifdef WIN32_NATIVE
11 #include "w32shell.c"
12 #endif
13
14 /* enable the following #define to debug pathname translations
15 setting DEBUG_TRANSLATE_PATHNAME to a larger value results in more output
16 WARNING: PRIN1 can trigger GC! BEWARE! */
17 # define DEBUG_TRANSLATE_PATHNAME 1
18 #if DEBUG_TRANSLATE_PATHNAME
19 #define string_concat(x) (printf("[%d]string_concat(%d)\n",__LINE__,x),(string_concat)(x))
20 #define DOUT(label,obj) OBJECT_OUT(obj,label)
21 #define SDOUT(label,obj) printf("%d %s %s",__LINE__,label,STRING(obj));nobject_out(stdout,obj)
22 #else
23 #define DOUT(l,o)
24 #define SDOUT(l,o)
25 #endif
26
27 /* ========================================================================
28 Low level functions */
29
30 /* We need the realpath() function in two variants:
31 (1) The POSIX function, declared in <stdlib.h>.
32 <http://opengroup.org/onlinepubs/9699919799/functions/realpath.html>
33 (2) Our own implementation that returns information about the missing
34 directory component, in case of ENOENT return.
35 We need (1) because on some systems, notably on Cygwin,
36 we otherwise get screwed on /proc/self/exe -> lisp instead of lisp.exe.
37 Also, on some systems, (1) is implemented more efficiently than (2).
38 We need (2) because POSIX says:
39 "If the resolved_name argument is not a null pointer and the realpath()
40 function fails, the contents of the buffer pointed to by resolved_name
41 are undefined." */
42
43 /* TITLE
44 REALPATH(3)
45 SYNOPSIS
46 char* realpath (const char* path, char resolved_path[MAXPATHLEN]);
47 DESCRIPTION
48 realpath() expands all symbolic links and resolves refer-
49 ences to '/./', '/../' and extra '/' characters in the null
50 terminated string named by path and stores the canonicalized
51 absolute pathname in the buffer named by resolved_path. The
52 resulting path will have no symbolic links components, nor
53 any '/./' or '/../' components.
54 RETURN VALUES
55 realpath() returns a pointer to the resolved_path on suc-
56 cess. On failure, it returns NULL, sets errno to indicate
57 the error, and places in resolved_path the absolute pathname
58 of the path component which could not be resolved. */
my_realpath(const char * path,char * resolved_path)59 local char* my_realpath (const char* path, char* resolved_path) {
60 /* Method: use getwd and readlink. */
61 var char mypath[MAXPATHLEN];
62 var int symlinkcount = 0; /* the number of symbolic links so far */
63 var char* resolved_limit = &resolved_path[MAXPATHLEN-1];
64 /* Valid pointers are those with resolved_path <= ptr <= resolved_limit.
65 in *resolved_limit at most one null byte.
66 (similarly with mypath.) */
67 var char* resolve_start;
68 {
69 var char* resolved_ptr = resolved_path; /* always <= resolved_limit */
70 /* poss. use Working-Directory: */
71 if (!(path[0]=='/')) { /* not an absolute pathname? */
72 if (getwd(resolved_path) == NULL)
73 return NULL;
74 resolved_ptr = resolved_path;
75 while (*resolved_ptr) {
76 resolved_ptr++;
77 }
78 if (resolved_ptr < resolved_limit) {
79 *resolved_ptr++ = '/';
80 }
81 resolve_start = resolved_ptr;
82 } else {
83 resolve_start = resolved_ptr = &resolved_path[0];
84 }
85 /* copy the path: */
86 var const char* path_ptr = path;
87 while ((resolved_ptr < resolved_limit) && *path_ptr) {
88 *resolved_ptr++ = *path_ptr++;
89 }
90 /* finish with '/' and a null: */
91 if (resolved_ptr < resolved_limit) {
92 *resolved_ptr++ = '/';
93 }
94 *resolved_ptr = 0;
95 }
96 /* Now start in resolved_path at resolve_start. */
97 var char* from_ptr = resolve_start;
98 var char* to_ptr = resolve_start;
99 while ((to_ptr < resolved_limit) && (*from_ptr)) {
100 /* so far the path in resolved_path[0]...to_ptr[-1]
101 has the shape '/subdir1/subdir2/.../txt',
102 whereas 'txt' is poss. empty, but no subdir is empty. */
103 var char next = *from_ptr++; *to_ptr++ = next;
104 if ((next == '/') && (to_ptr > resolved_path+1)) {
105 /* to_ptr[-1]='/' -> resolve Directory ...to_ptr[-2] : */
106 var char* last_subdir_end = &to_ptr[-2];
107 switch (*last_subdir_end) {
108 case '/':
109 #ifdef PATHNAME_UNIX_UNC
110 if (to_ptr > resolved_path+2)
111 #endif
112 /* '//' is simplified to '/' : */
113 to_ptr--;
114 break;
115 case '.':
116 {
117 var char* last_subdir_ptr = &last_subdir_end[-1];
118 if (to_ptr > resolved_path+2) {
119 if (*last_subdir_ptr == '.') {
120 if ((to_ptr > resolved_path+4)
121 && (*--last_subdir_ptr == '/')) {
122 /* last subdir was '/../'
123 Therefore remove the subdir in front of it: */
124 while ((last_subdir_ptr > resolved_path)
125 && !(*--last_subdir_ptr == '/')) ;
126 to_ptr = last_subdir_ptr+1;
127 }
128 } else if (*last_subdir_ptr == '/') {
129 /* last subdir was '/./'
130 remove: */
131 to_ptr = last_subdir_end;
132 }
133 }
134 }
135 break;
136 default:
137 /* after a normal subdir */
138 #ifdef HAVE_READLINK
139 /* read symbolic link: */
140 to_ptr[-1]=0; /* replace '/' with 0 */
141 #ifdef UNIX_CYGWIN
142 /* readlink() does not work right on NFS mounted directories
143 (it returns -1,ENOENT or -1,EIO).
144 So check for a directory first. */
145 var struct stat statbuf;
146 if (lstat(resolved_path,&statbuf) < 0) {
147 /* We know that /dev/fd on Cygwin is a symlink to /proc/self/fd,
148 but the lstat() function does not know it: it returns -1,ENOENT.
149 Override this behaviour. */
150 if (asciz_equal(resolved_path,"/dev/fd")) {
151 statbuf.st_mode = S_IFLNK | (S_IRUSR | S_IWUSR | S_IXUSR);
152 } else {
153 /* Error. */
154 return NULL;
155 }
156 }
157 if (S_ISDIR(statbuf.st_mode)) {
158 /* directory, not a symbolic link */
159 to_ptr[-1] = '/'; /* insert the '/' again */
160 } else if (!S_ISLNK(statbuf.st_mode)) {
161 /* something else, but not a directory or symbolic link. */
162 errno = ENOTDIR;
163 return NULL;
164 } else
165 #endif
166 {
167 var int linklen;
168 #ifdef UNIX_CYGWIN
169 /* We know that /dev/fd on Cygwin is a symlink to /proc/self/fd,
170 but the readlink() function does not know it. */
171 if (asciz_equal(resolved_path,"/dev/fd")) {
172 memcpy(mypath,"/proc/self/fd",13); linklen = 13;
173 } else
174 #endif
175 {
176 linklen = readlink(resolved_path,mypath,sizeof(mypath)-1);
177 }
178 if (linklen >=0) { /* was a symbolic link */
179 if (++symlinkcount > MAXSYMLINKS) {
180 errno = ELOOP; return NULL;
181 }
182 { /* append the still to be resolved part of path
183 to the link-content: */
184 var char* mypath_ptr = &mypath[linklen]; /* here is room */
185 var char* mypath_limit = &mypath[MAXPATHLEN-1]; /* up to here */
186 if (mypath_ptr < mypath_limit) { *mypath_ptr++ = '/'; } /* first, append a '/' */
187 /* then the rest: */
188 while ((mypath_ptr <= mypath_limit)
189 && (*mypath_ptr = *from_ptr++))
190 { mypath_ptr++; }
191 *mypath_ptr = 0; /* and conclude wit 0 */
192 }
193 /* this replaces resp. completes the path: */
194 if (mypath[0] == '/') { /* replaces the path: */
195 from_ptr = &mypath[0]; to_ptr = resolved_path;
196 while ((*to_ptr++ = *from_ptr++)) ;
197 from_ptr = resolved_path;
198 } else { /* completes the path:
199 disrcard link-name. Therefore search for the last '/': */
200 {
201 var char* ptr = &to_ptr[-1];
202 while ((ptr > resolved_path) && !(ptr[-1] == '/')) { ptr--; }
203 from_ptr = ptr;
204 }
205 {
206 var char* mypath_ptr = &mypath[0]; to_ptr = from_ptr;
207 while ((to_ptr <= resolved_limit)
208 && (*to_ptr++ = *mypath_ptr++)) ;
209 }
210 }
211 to_ptr = from_ptr;
212 } else {
213 #if defined(UNIX_CYGWIN)
214 if ((errno == EINVAL) || (errno == EACCES))
215 #else
216 if (errno == EINVAL)
217 #endif
218 /* no symbolic link */
219 to_ptr[-1] = '/'; /* insert the '/' again */
220 else
221 return NULL; /* error */
222 }
223 }
224 #endif
225 break;
226 }
227 }
228 } /* go for the next subdir */
229 /* discard a '/' at the tail: */
230 if ((to_ptr[-1] == '/')
231 #ifdef PATHNAME_UNIX_UNC
232 && (to_ptr > resolved_path+2)
233 #else
234 && (to_ptr > resolved_path+1)
235 #endif
236 )
237 to_ptr--;
238 to_ptr[0] = 0; /* conclude with 0 */
239 return resolved_path; /* finished */
240 }
241
242 #if defined(UNIX) && !defined(HAVE_REALPATH)
243 /* We use realpath() from <stdlib.h>. */
244 #define realpath_is_my_realpath false
245 #else
246 #define realpath my_realpath
247 #define realpath_is_my_realpath true
248 #endif
249
250 /* Creates a new subdirectory.
251 make_directory(pathstring);
252 > pathstring: result of shorter_directory(...)
253 > STACK_0: pathname */
make_directory(char * pathstring)254 local maygc inline void make_directory (char* pathstring) {
255 #if defined(UNIX)
256 begin_blocking_system_call();
257 if (mkdir(pathstring,0777)) { /* create sub-directory */
258 end_blocking_system_call(); OS_file_error(STACK_0);
259 }
260 end_blocking_system_call();
261 #elif defined(WIN32_NATIVE)
262 begin_blocking_system_call();
263 if (! CreateDirectory(pathstring,NULL) ) { /* create sub-directory */
264 end_blocking_system_call(); OS_file_error(STACK_0);
265 }
266 end_blocking_system_call();
267 #else
268 #error make_directory is not defined
269 #endif
270 }
271
272 /* Deletes a subdirectory.
273 delete_directory(pathstring);
274 > pathstring: result of shorter_directory(...)
275 > STACK_0: pathname */
delete_directory(char * pathstring)276 local maygc inline void delete_directory (char* pathstring) {
277 #if defined(UNIX)
278 begin_blocking_system_call();
279 /* delete sub-directory - which might be a symlink! */
280 if (rmdir(pathstring) && (errno == ENOTDIR ? unlink(pathstring) : 1)) {
281 end_blocking_system_call(); OS_file_error(STACK_0);
282 }
283 end_blocking_system_call();
284 #elif defined(WIN32_NATIVE)
285 begin_blocking_system_call();
286 if (! RemoveDirectory(pathstring) ) { /* delete sub-directory */
287 end_blocking_system_call(); OS_file_error(STACK_0);
288 }
289 end_blocking_system_call();
290 #else
291 #error delete_directory is not defined
292 #endif
293 }
294
295 #ifdef WIN32_NATIVE
296 /* Changes the operating system's current directory.
297 change_directory(pathstring);
298 > pathstring: directory, ASCIZ-String
299 > STACK_0: pathname */
change_current_directory(char * pathstring)300 local maygc inline void change_current_directory (char* pathstring) {
301 begin_blocking_system_call();
302 if (!SetCurrentDirectory(pathstring)) {
303 end_blocking_system_call(); OS_file_error(STACK_0);
304 }
305 end_blocking_system_call();
306 }
307 #endif
308
309 /* Delete a file.
310 delete_existing_file(pathstring);
311 It is known that the file exists.
312 > pathstring: file name, ASCIZ-String
313 > STACK_0: pathname */
delete_existing_file(char * pathstring)314 local maygc inline void delete_existing_file (char* pathstring) {
315 #if defined(UNIX)
316 begin_blocking_system_call();
317 if (!( unlink(pathstring) ==0)) {
318 end_blocking_system_call(); OS_file_error(STACK_0);
319 }
320 end_blocking_system_call();
321 #elif defined(WIN32_NATIVE)
322 begin_blocking_system_call();
323 if (! DeleteFile(pathstring) ) {
324 end_blocking_system_call(); OS_file_error(STACK_0);
325 }
326 end_blocking_system_call();
327 #else
328 #error delete_existing_file is not defined
329 #endif
330 }
331
332 #ifdef WIN32_NATIVE
333 #define WIN32_ERROR_NOT_FOUND (GetLastError()==ERROR_FILE_NOT_FOUND || GetLastError()==ERROR_PATH_NOT_FOUND || GetLastError()==ERROR_BAD_NETPATH)
334 #endif
335
336 /* Delete a file.
337 delete_file_if_exists(pathstring);
338 No error is signaled if the file does not exist.
339 > pathstring: file name, ASCIZ-String
340 > STACK_0: pathname
341 < result: whether the file existed */
delete_file_if_exists(char * pathstring)342 local maygc inline bool delete_file_if_exists (char* pathstring) {
343 var bool exists = true;
344 #if defined(UNIX)
345 begin_blocking_system_call();
346 if (!( unlink(pathstring) ==0)) {
347 if (!(errno==ENOENT)) { /* not found -> OK */
348 end_blocking_system_call(); OS_file_error(STACK_0); /* report other error */
349 }
350 exists = false;
351 }
352 end_blocking_system_call();
353 #elif defined(WIN32_NATIVE)
354 begin_blocking_system_call();
355 if (! DeleteFile(pathstring) ) {
356 if (!WIN32_ERROR_NOT_FOUND) {
357 end_blocking_system_call(); OS_file_error(STACK_0);
358 }
359 exists = false;
360 }
361 end_blocking_system_call();
362 #else
363 #error delete_file_if_exists is not defined
364 #endif
365 return exists;
366 }
delete_file_if_exists_obj(object namestring)367 local maygc bool delete_file_if_exists_obj (object namestring) {
368 bool ret;
369 with_sstring_0(namestring,O(pathname_encoding),namestring_asciz, {
370 ret = delete_file_if_exists(namestring_asciz);
371 });
372 return ret;
373 }
374
375 /* Delete a file being the target of a subsequent rename.
376 delete_file_before_rename(pathstring);
377 No error is signaled if the file does not exist.
378 > pathstring: file name, ASCIZ-String
379 > STACK_0: pathname */
delete_file_before_rename(char * pathstring)380 local maygc inline void delete_file_before_rename (char* pathstring) {
381 #if defined(UNIX) /* rename() on Unix does it automatically */
382 unused(pathstring);
383 #else
384 delete_file_if_exists(pathstring);
385 #endif
386 }
387
388 #if defined(WIN32_NATIVE)
389 /* The default woe32 behavior is to barf if the destination exists.
390 Use MoveFileEx/MOVEFILE_REPLACE_EXISTING to emulate
391 the Unix behavior of atomic overwrite. */
392 #if defined(MOVEFILE_REPLACE_EXISTING)
393 static HMODULE k32 = (HMODULE)-1;
394 typedef BOOL (WINAPI * fMoveFileEx_t) (LPCSTR src,LPCSTR dst,DWORD flags);
395 static fMoveFileEx_t move_file_ex = (fMoveFileEx_t)-1;
move_file(char * src,char * dst)396 local BOOL move_file (char* src, char* dst) {
397 if (k32 == (HMODULE)-1) k32 = LoadLibrary("kernel32.dll");
398 if (k32 && move_file_ex == (fMoveFileEx_t)-1)
399 move_file_ex = (fMoveFileEx_t)GetProcAddress(k32,"MoveFileExA");
400 if (k32 && move_file_ex)
401 return move_file_ex(src,dst,MOVEFILE_REPLACE_EXISTING);
402 else return MoveFile(src,dst);
403 }
404 #else
405 #define move_file MoveFile
406 #endif
407 #endif
408
409 /* Rename a file.
410 rename_existing_file(old_pathstring,new_pathstring);
411 It is known that the old_pathstring exists.
412 On platforms except UNIX, it is known that new_pathstring does not exist.
413 > old_pathstring: old file name, ASCIZ-String
414 > new_pathstring: new file name, ASCIZ-String
415 > STACK_0: pathname */
rename_existing_file(char * old_pathstring,char * new_pathstring)416 local maygc inline void rename_existing_file (char* old_pathstring,
417 char* new_pathstring) {
418 #if defined(UNIX)
419 begin_blocking_system_call();
420 if ( rename(old_pathstring,new_pathstring) <0) { /* rename file */
421 end_blocking_system_call(); OS_file_error(STACK_0); /* report error */
422 }
423 end_blocking_system_call();
424 #elif defined(WIN32_NATIVE)
425 begin_blocking_system_call();
426 if (!move_file(old_pathstring,new_pathstring) ) {
427 end_blocking_system_call(); OS_file_error(STACK_0);
428 }
429 end_blocking_system_call();
430 #else
431 #error rename_existing_file is not defined
432 #endif
433 }
434
rename_existing_path(object old_pathstring,object new_pathstring)435 local maygc void rename_existing_path (object old_pathstring,
436 object new_pathstring) {
437 with_sstring_0(old_pathstring,O(pathname_encoding),oldnamestring_asciz, {
438 with_sstring_0(new_pathstring,O(pathname_encoding),newnamestring_asciz, {
439 rename_existing_file(oldnamestring_asciz,newnamestring_asciz);
440 });
441 });
442 }
443
444 /* ========================================================================
445 P A T H N A M E S
446
447 All simple-strings occurring in pathnames are in fact
448 normal-simple-strings.
449
450 #ifdef PATHNAME_UNIX
451 Components:
452 HOST always NIL
453 DEVICE always NIL
454 DIRECTORY (Startpoint . Subdirs) whereas
455 Startpoint = :RELATIVE | :ABSOLUTE
456 Subdirs = () | (subdir . Subdirs)
457 subdir = :WILD-INFERIORS (means "**" or "...", all subdirectories) or
458 subdir = Simple-String, poss. with wildcard-character ? and *
459 NAME NIL or
460 Simple-String, poss. with wildcard-character ? and *
461 (also :WILD on input)
462 TYPE NIL or
463 Simple-String, poss. with wildcard-character ? and *
464 (also :WILD on input)
465 VERSION always NIL (also :WILD or :NEWEST on input)
466 A UNIX-filename is split in Name and Type as follows:
467 if there is no '.' in Filename: Name = everything, Type = NIL,
468 if there is '.' in Filename: Name = everything in front of it, Type = everything behind the last '.' .
469 If a pathname must be completely specified (no wildcards),
470 :WILD, :WILD-INFERIORS are not allowed, no wildcard-characters in the
471 Strings, at NAME poss. also not NIL.
472 External Notation: server:/sub1.typ/sub2.typ/name.typ
473 with Defaults: /sub1.typ/sub2.typ/name.typ
474 or name.typ
475 or /sub1.typ/ ** /sub3.typ/x*.lisp (without Spaces!)
476 or similar.
477 If NAME starts with a dot, (parse-namestring (namestring pathname)) will not
478 be the same as pathname.
479 #endif
480
481 #ifdef PATHNAME_WIN32
482 Components:
483 HOST NIL or Simple-String (Wildcard-Characters are without meaning)
484 DEVICE NIL or :WILD or "A"|...|"Z"
485 DIRECTORY (Startpoint . Subdirs) whereas
486 Startpoint = :RELATIVE | :ABSOLUTE
487 Subdirs = () | (subdir . Subdirs)
488 subdir = :WILD-INFERIORS (means "**" or "...", all Subdirectories) or
489 subdir = Simple-String, poss. with Wildcard-Character ? and *
490 NAME NIL or
491 Simple-String, poss. with Wildcard-Character ? and *
492 (also :WILD on input)
493 TYPE NIL or
494 Simple-String, poss. with Wildcard-Character ? and *
495 (also :WILD on input)
496 VERSION always NIL (also :WILD or :NEWEST on input)
497 If HOST is non-NIL, DEVICE must be NIL.
498 A WIN32-Filename is split into Name and Type as follows:
499 if there is no '.' in Filename: Name = everything, Type = NIL,
500 if there is a '.' in Filename: Name = everything in front of, Type = everything behind the last '.' .
501 If a Pathname must be completely specified (no Wildcards),
502 then :WILD, :WILD-INFERIORS are not allowed, no Wildcard-Characters in the
503 Strings, at NAME poss. also not NIL.
504 External notation: A:\sub1.typ\sub2.typ\name.typ
505 with Defaults: \sub1.typ\sub2.typ\name.typ
506 or name.typ
507 or *:\sub1.typ\**\sub3.typ\x*.lisp
508 or similar.
509 Instead of '\' - traditionally on DOS - also '/' is allowed.
510 If HOST is non-NIL and the DIRECTORY's Startpoint is not :ABSOLUTE,
511 (parse-namestring (namestring pathname)) will not be the same as pathname.
512 #endif
513
514 Components of Logical Pathnames:
515 HOST Simple-String or NIL
516 DEVICE always NIL
517 DIRECTORY (Startpoint . Subdirs) whereas
518 Startpoint = :RELATIVE | :ABSOLUTE
519 Subdirs = () | (subdir . Subdirs)
520 subdir = :WILD-INFERIORS (means "**", all Subdirectories) or
521 subdir = :WILD (means "*") or
522 subdir = Simple-String, poss. with Wildcard-Character *
523 NAME NIL or
524 :WILD (means "*") or
525 Simple-String, poss. with Wildcard-Character *
526 TYPE NIL or
527 :WILD (means "*") or
528 Simple-String, poss. with Wildcard-Character *
529 VERSION NIL or :NEWEST or :WILD or Integer
530 External Notation: see CLtl2 p. 628-629.
531
532 access functions without case transforms:
533 xpathname_host(logical,pathname)
534 xpathname_device(logical,pathname)
535 xpathname_directory(logical,pathname)
536 xpathname_name(logical,pathname)
537 xpathname_type(logical,pathname)
538 xpathname_version(logical,pathname)
539 > pathname: pathname or logical pathname
540 > logical: flag = logpathnamep(pathname)
541 < result: the value of the requested component
542 pathname_*_maybe return the appropriate slot seen from the point of view of the
543 underlying physical file system, therefore, ever though pathname has the slot
544 version (for ANSI compliance reasons), pathname_version_maybe() returns NIL */
545 #if HAS_HOST
546 #define pathname_host_maybe(obj) (object)ThePathname(obj)->pathname_host
547 #else
548 #define pathname_host_maybe(obj) (unused(obj), NIL)
549 #endif
550 #if HAS_DEVICE
551 #define pathname_device_maybe(obj) (object)ThePathname(obj)->pathname_device
552 #else
553 #define pathname_device_maybe(obj) (unused(obj), NIL)
554 #endif
555 #if HAS_VERSION
556 #define pathname_version_maybe(obj) (object)ThePathname(obj)->pathname_version
557 #else
558 #define pathname_version_maybe(obj) (unused(obj), NIL)
559 #endif
560
561 #define xpathname_host(logical,pathname) \
562 (logical ? (object)TheLogpathname(pathname)->pathname_host : \
563 pathname_host_maybe(pathname))
564 #define xpathname_device(logical,pathname) \
565 (logical ? NIL : pathname_device_maybe(pathname))
566 #define xpathname_directory(logical,pathname) \
567 (logical ? (object)TheLogpathname(pathname)->pathname_directory : \
568 (object)ThePathname(pathname)->pathname_directory)
569 #define xpathname_name(logical,pathname) \
570 (logical ? (object)TheLogpathname(pathname)->pathname_name : \
571 (object)ThePathname(pathname)->pathname_name)
572 #define xpathname_type(logical,pathname) \
573 (logical ? (object)TheLogpathname(pathname)->pathname_type : \
574 (object)ThePathname(pathname)->pathname_type)
575 #define xpathname_version(logical,pathname) \
576 (logical ? (object)TheLogpathname(pathname)->pathname_version : \
577 (object)ThePathname(pathname)->pathname_version)
578
579 #define SUBST_RECURSE(atom_form,self_call) \
580 if (atomp(obj)) return atom_form; \
581 check_STACK(); check_SP(); \
582 pushSTACK(obj); \
583 { /* recursive call for CAR: */ \
584 object new_car = self_call(Car(obj)); \
585 pushSTACK(new_car); \
586 } \
587 { /* recursive call for CDR: */ \
588 object new_cdr = self_call(Cdr(STACK_1)); \
589 if (eq(new_cdr,Cdr(STACK_1)) && eq(STACK_0,Car(STACK_1))) { \
590 obj = STACK_1; skipSTACK(2); return obj; \
591 } else { /* (CONS new_car new_cdr) */ \
592 STACK_1 = new_cdr; \
593 {object new_cons = allocate_cons(); \
594 Car(new_cons) = popSTACK(); Cdr(new_cons) = popSTACK(); \
595 return new_cons; \
596 }} \
597 }
598
599 /* Converts capital-/small letters between :LOCAL and :COMMON .
600 common_case(string)
601 > string: Normal-Simple-String or Symbol/Number
602 < result: converted Normal-Simple-String or the same Symbol/Number
603 can trigger GC
604 Operating System with preference for small letters or Capitalize */
common_case(object string)605 local maygc object common_case (object string) {
606 if (!simple_string_p(string))
607 return string;
608 var uintL len = Sstring_length(string);
609 /* Search, if capital- or small letters (or both) occur: */
610 var bool all_upper = true;
611 var bool all_lower = true;
612 if (len > 0) {
613 var object storage = string; sstring_un_realloc(storage);
614 SstringDispatch(storage,X, {
615 var const cintX* ptr = &((SstringX)TheVarobject(storage))->data[0];
616 var uintL count;
617 dotimespL(count,len, {
618 var chart ch = as_chart(*ptr++);
619 if (!chareq(ch,up_case(ch)))
620 all_upper = false;
621 if (!chareq(ch,down_case(ch)))
622 all_lower = false;
623 if (!all_upper && !all_lower)
624 break;
625 });
626 });
627 }
628 if (all_upper == all_lower)
629 /* all_upper = all_lower = true: Nothing to convert.
630 all_upper = all_lower = false: "Mixed case represents itself." */
631 return string;
632 if (all_upper)
633 /* all_upper = true, all_lower = false: STRING-DOWNCASE */
634 return string_downcase(string);
635 else
636 /* all_upper = false, all_lower = true: STRING-UPCASE */
637 return string_upcase(string);
638 }
639 /* the same, recursive like with SUBST: */
subst_common_case(object obj)640 local object subst_common_case (object obj) {
641 SUBST_RECURSE(common_case(obj),subst_common_case);
642 }
643
legal_logical_word_char(chart ch)644 local bool legal_logical_word_char (chart ch) {
645 ch = up_case(ch);
646 var cint c = as_cint(ch);
647 return (((c >= 'A') && (c <= 'Z'))
648 || ((c >= '0') && (c <= '9'))
649 || (c == '-'));
650 }
651
652 #if HAS_HOST
653
654 /* UP: Determines, if a character is allowed as character in the host-part
655 of a namestring.
656 legal_hostchar(ch)
657 > chart ch: Character-Code
658 < result: true if allowed, else false
659 NB: legal_logical_word_char(ch) implies legal_hostchar(ch). */
legal_hostchar(chart ch)660 local bool legal_hostchar (chart ch) {
661 #if defined(PATHNAME_WIN32)
662 { /* This is just a guess. I do not know which characters are allowed in
663 Windows host names. */
664 var cint c = as_cint(ch);
665 return ((c >= ' ') && (c <= '~')
666 && (c != '"') && (c != '/') && (c != ':')
667 && (c != '<') && (c != '>') && (c != '\\'));
668 }
669 #else
670 return alphanumericp(ch) || chareq(ch,ascii('-'));
671 #endif
672 }
673
674 /* UP: check an optional HOST argument
675 test_optional_host(host,convert)
676 > host: Host-Argument
677 > convert: Flag, if case-conversion is undesired
678 < result: valid host-component
679 can trigger GC */
test_optional_host(object host,bool convert)680 local maygc object test_optional_host (object host, bool convert) {
681 if (!boundp(host) || eq(host,S(Kunspecific)))
682 return NIL;
683 if (nullp(host))
684 goto OK; /* NIL is OK */
685 /* Else, host must be a String, whose characters are alphanumeric: */
686 if (!stringp(host)) {
687 pushSTACK(host); /* TYPE-ERROR slot DATUM */
688 pushSTACK(O(type_host)); /* TYPE-ERROR slot EXPECTED-TYPE */
689 pushSTACK(host);
690 pushSTACK(TheSubr(subr_self)->name);
691 error(type_error,GETTEXT("~S: host should be NIL or a string, not ~S"));
692 }
693 host = coerce_normal_ss(host); /* as Normal-Simple-String */
694 if (convert)
695 host = common_case(host);
696 {
697 var uintL len = Sstring_length(host);
698 if (len > 0) {
699 var const chart* charptr = &TheSnstring(host)->data[0];
700 dotimespL(len,len, {
701 var chart ch = *charptr++;
702 if (!legal_hostchar(ch))
703 goto badhost;
704 });
705 }
706 }
707 OK: return host;
708 badhost:
709 pushSTACK(host);
710 pushSTACK(TheSubr(subr_self)->name);
711 error(parse_error,GETTEXT("~S: illegal hostname ~S"));
712 }
713
714 #else
715
716 /* UP: check an optional HOST argument
717 test_optional_host(host)
718 > host: Host-Argument
719 < result: valid host-component
720 can trigger GC */
test_optional_host(object host)721 local maygc object test_optional_host (object host) {
722 if (!boundp(host) || eq(host,S(Kunspecific)))
723 return NIL; /* not specified -> NIL */
724 if (nullp(host))
725 goto OK; /* NIL is OK */
726 /* Else, host must be a String, whose characters are alphanumeric: */
727 if (!stringp(host)) {
728 pushSTACK(host); /* TYPE-ERROR slot DATUM */
729 pushSTACK(O(type_host)); /* TYPE-ERROR slot EXPECTED-TYPE */
730 pushSTACK(host);
731 pushSTACK(TheSubr(subr_self)->name);
732 error(type_error,GETTEXT("~S: host should be NIL or a string, not ~S"));
733 }
734 host = coerce_normal_ss(host); /* as Normal-Simple-String */
735 {
736 var uintL len = Sstring_length(host);
737 if (len > 0) {
738 var object storage = host; sstring_un_realloc(storage);
739 SstringDispatch(storage,X, {
740 var const cintX* ptr = &((SstringX)TheVarobject(storage))->data[0];
741 dotimespL(len,len, {
742 var chart ch = as_chart(*ptr++);
743 if (!legal_logical_word_char(ch))
744 goto badhost;
745 });
746 });
747 }
748 }
749 OK: return host;
750 badhost:
751 pushSTACK(host);
752 pushSTACK(TheSubr(subr_self)->name);
753 error(parse_error,GETTEXT("~S: illegal hostname ~S"));
754 }
755
756 #endif
757
758 /* Determines, if two characters count as equal characters in pathnames.
759 equal_pathchar(ch1,ch2)
760 > chart ch1,ch2: Character-Codes
761 < result: true if equal, else false */
762 #if !defined(PATHNAME_WIN32)
763 #define equal_pathchar(ch1,ch2) chareq(ch1,ch2)
764 #else /* defined(PATHNAME_WIN32) */
765 /* Case-insensitive, but normally without conversion */
766 #define equal_pathchar(ch1,ch2) chareq(up_case(ch1),up_case(ch2))
767 #endif
768
769 /* UP: check whether a given byte is a valid element of NAME or TYPE
770 component in a Namestring
771 legal_namebyte(ch)
772 > uintB: byte
773 < return: true if valid, else false */
legal_namebyte(uintB ch)774 local inline bool legal_namebyte (uintB ch) {
775 if (ch >= 0x80) {
776 /* For non-ASCII characters, the byte sequence passed to the operating
777 system depends on *PATHNAME-ENCODING*. Therefore consider them all
778 as valid here. */
779 return true;
780 } else {
781 /* For ASCII characters, the VALID_FILENAME_CHAR makes sense.
782 In order to ensure that a binary built in one locale works also in
783 the other locales, hard-code here the known locale independent
784 configuration results. */
785 #if defined(PATHNAME_UNIX)
786 return (ch >= 1) && (ch != '/');
787 #elif defined(PATHNAME_WIN32)
788 return ((ch >= 32) && (ch <= 127)
789 && (ch != '"') /*&& (ch != '*')*/
790 && (ch != '/') && (ch != ':')
791 && (ch != '<') && (ch != '>') /*&& (ch != '?')*/
792 && (ch != '\\') && (ch != '|'));
793 #else
794 /* VALID_FILENAME_CHAR is defined in config.h */
795 return VALID_FILENAME_CHAR || (ch=='*') || (ch=='?');
796 #endif
797 }
798 }
799
800 /* UP: check whether the character is a valid element of NAME or TYPE
801 component in a Namestring
802 legal_namechar(ch)
803 > chart ch: character-code
804 < return: true if valid, else false */
legal_namechar(chart ch)805 local bool legal_namechar (chart ch) {
806 #ifdef ENABLE_UNICODE
807 var uintB buf[4]; /* are there characters longer than 4 bytes?! */
808 var uintL char_len = cslen(O(pathname_encoding),&ch,1);
809 cstombs(O(pathname_encoding),&ch,1,buf,char_len);
810 while (char_len > 0) {
811 char_len--;
812 if (!legal_namebyte(buf[char_len])) return false;
813 }
814 return true;
815 #else
816 return legal_namebyte(as_cint(ch));
817 #endif
818 }
819
820 /* Determines, if a character is a wildcard for a single
821 character.
822 singlewild_char_p(ch)
823 > chart ch: Character-Code
824 < result: true if yes, else false */
825 #define singlewild_char_p(ch) chareq(ch,ascii('?'))
826 #define multiwild_char_p(ch) chareq(ch,ascii('*'))
827 #define wild_char_p(ch) (multiwild_char_p(ch) || singlewild_char_p(ch))
828
829 /* Converts an object into a pathname. */
830 local object coerce_xpathname (object obj); /* later */
831
832 /* Converts an object into a non-logical pathname. */
833 local object coerce_pathname (object obj); /* later */
834
835 /* Returns a default-pathname. */
836 local object defaults_pathname (void); /* later */
837
838 /* checks a default-pathname.
839 test_default_pathname(defaults)
840 > defaults: defaults-argument
841 < result: value of the defaults-argument, a pathname
842 can trigger GC */
test_default_pathname(object defaults)843 local maygc object test_default_pathname (object defaults) {
844 if (missingp(defaults))
845 /* not specified -> take value of *DEFAULT-PATHNAME-DEFAULTS* : */
846 return defaults_pathname();
847 else
848 /* specified -> turn into a pathname: */
849 return coerce_xpathname(defaults);
850 }
851
852 /* <http://www.ai.mit.edu/projects/iiip/doc/CommonLISP/HyperSpec/Body/sec_19-2-3.html>:
853 "for functions that manipulate or inquire about files in the file system,
854 the pathname argument to such a function is merged with
855 *DEFAULT-PATHNAME-DEFAULTS* before accessing the file system"
856 When pathname comes from a file stream, this is NOT done because
857 that pathname has already been "transfered from the world of the abstract
858 Lisp pathname algebra to the real world of computer file system"
859 Another option is to ensure that all slots of *DEFAULT-PATHNAME-DEFAULTS*
860 are non-NIL (use :UNSPECIFIC instead): then merge_defaults() becomes
861 an idempotent operation -- assuming trivial directory or non-ANSI merging.
862
863 merge_defaults(pathname)
864 > pathname: a pathname
865 < result: a pathname derived from it, with *DEFAULT-PATHNAME-DEFAULTS* merged
866 in.
867 can trigger GC */
merge_defaults(object pathname)868 local maygc object merge_defaults (object pathname) {
869 pushSTACK(pathname); pushSTACK(defaults_pathname());
870 funcall(L(merge_pathnames),2);
871 return value1;
872 }
873
874 /* error-message because of illegal pathname-argument.
875 error_pathname_designator(thing); ( error_... )
876 > thing: (erroneous) argument */
error_pathname_designator(object thing)877 local _Noreturn void error_pathname_designator (object thing) {
878 pushSTACK(thing); /* TYPE-ERROR slot DATUM */
879 pushSTACK(O(type_designator_pathname)); /* TYPE-ERROR slot EXPECTED-TYPE */
880 pushSTACK(O(type_designator_pathname));
881 pushSTACK(thing);
882 pushSTACK(TheSubr(subr_self)->name);
883 error(type_error,GETTEXT("~S: Argument ~S should be a pathname designator ~S"));
884 }
885
886 /* Tracks a chain of Synonym-Streams, so long as a File-Stream
887 is reached.
888 as_file_stream(stream)
889 > stream: Builtin-Stream
890 < stream: File-Stream */
as_file_stream(object stream)891 local object as_file_stream (object stream) {
892 var object s = stream;
893 while (1) {
894 if (TheStream(s)->strmtype == strmtype_file)
895 return s;
896 if (!(TheStream(s)->strmtype == strmtype_synonym))
897 break;
898 s = Symbol_value(TheStream(stream)->strm_synonym_symbol);
899 if (!builtin_stream_p(s))
900 break;
901 }
902 error_pathname_designator(stream);
903 }
904
905 /* Signal an error if a file-stream does not have
906 a file-name associated with it.
907 test_file_stream_named(stream)
908 > stream: File-Stream */
909 #define test_file_stream_named(stream) \
910 do { if (nullp(TheStream(stream)->strm_file_name)) \
911 error_file_stream_unnamed(stream); \
912 } while(0)
error_file_stream_unnamed(object stream)913 local _Noreturn void error_file_stream_unnamed (object stream) {
914 pushSTACK(stream); /* FILE-ERROR slot PATHNAME */
915 pushSTACK(stream); pushSTACK(TheSubr(subr_self)->name);
916 error(file_error,GETTEXT("~S: Filename for ~S is unknown"));
917 }
918 /* Ensure that IF the file stream has a name, THEN it also has truename
919 > file-stream (open or closed) - no type check is done!
920 < truename of the file associated with the stream
921 for syscall module */
file_stream_truename(object stream)922 modexp maygc object file_stream_truename (object stream) {
923 test_file_stream_named(stream);
924 if (nullp(TheStream(stream)->strm_file_truename)) {
925 pushSTACK(stream);
926 pushSTACK(TheStream(stream)->strm_file_name); funcall(L(truename),1);
927 stream = popSTACK();
928 TheStream(stream)->strm_file_truename = value1;
929 }
930 return TheStream(stream)->strm_file_truename;
931 }
932
933 #if defined(UNIX)
934 #define slash '/'
935 #elif defined(WIN32_NATIVE)
936 #define slash '\\'
937 #else
938 #error slash is not defined
939 #endif
940 /* physical slash */
941 #ifdef PATHNAME_WIN32
942 #define pslashp(c) (chareq(c,ascii('\\')) || chareq(c,ascii('/')))
943 #define cpslashp(c) ((c) == '\\' || (c) == '/')
944 #else /* PATHNAME_UNIX */
945 #define pslashp(c) chareq(c,ascii(slash))
946 #define cpslashp(c) ((c) == slash)
947 #endif
948 #define colonp(c) chareq(c,ascii(':'))
949 #define dotp(c) chareq(c,ascii('.'))
950
951 /* UP: add a character to an ASCII string and return as a Lisp string
952 can trigger GC */
953 #ifdef ENABLE_UNICODE
asciz_add_char(const char * chars,uintL len,char ch,object encoding)954 local /*maygc*/ object asciz_add_char (const char* chars, uintL len, char ch,
955 object encoding)
956 #else
957 #define asciz_add_char(chars,len,ch,encoding) asciz_add_char_(chars,len,ch)
958 local /*maygc*/ object asciz_add_char_ (const char* chars, uintL len, char ch)
959 #endif
960 {
961 #ifdef ENABLE_UNICODE
962 GCTRIGGER1(encoding);
963 #else
964 GCTRIGGER();
965 #endif
966 var DYNAMIC_ARRAY(buf,char,len+1);
967 begin_system_call(); memcpy(buf,chars,len); end_system_call();
968 buf[len] = ch;
969 var object s = n_char_to_string(buf,len+1,encoding);
970 FREE_DYNAMIC_ARRAY(buf);
971 return s;
972 }
973
974 /* UP: Converts a Unix-Directory-Specification into a pathname.
975 asciz_dir_to_pathname(path,encoding)
976 > const char* path: path as ASCIZ-String
977 > encoding: Encoding
978 < result: as a pathname without name and type
979 can trigger GC */
980 #ifdef ENABLE_UNICODE
asciz_dir_to_pathname(const char * path,object encoding)981 local /*maygc*/ object asciz_dir_to_pathname(const char* path, object encoding)
982 #else
983 #define asciz_dir_to_pathname(path,encoding) asciz_dir_to_pathname_(path)
984 local /*maygc*/ object asciz_dir_to_pathname_(const char* path)
985 #endif
986 {
987 #ifdef ENABLE_UNICODE
988 GCTRIGGER1(encoding);
989 #else
990 GCTRIGGER();
991 #endif
992 var object pathname;
993 var uintL len = asciz_length(path); /* string length */
994 /* if the String does not end with a '/' already, a '/' is added: */
995 if ((len>0) && cpslashp(path[len-1]))
996 pathname = n_char_to_string(path,len,encoding);
997 else
998 pathname = asciz_add_char(path,len,slash,encoding);
999 /* and convert into a pathname: */
1000 return coerce_pathname(pathname);
1001 }
1002
1003 /* Type for PARSE-NAMESTRING:
1004 State while the string is being parsed character by character. */
1005 typedef struct {
1006 uintL index; /* index (incl. offset) */
1007 object FNindex; /* index as a fixnum */
1008 uintL count; /* number of the remaining characters */
1009 } zustand; /* "state" */
1010
1011 /* Skip s characters. */
1012 #define Z_SHIFT(z,s) \
1013 do { (z).index += (s); (z).FNindex = fixnum_inc((z).FNindex,(s)); (z).count -= (s); } while(0)
1014
1015 /* Tests whether the current character at Z satisfies pred. */
1016 #define Z_AT_SLASH(z,pred,st) \
1017 (((z).count != 0) && pred(schar(st,(z).index)))
1018
1019 /* Replace this string with a substring. */
1020 #define Z_SUB(z,s) ((s) = subsstring((s),(z).index,(z).index+(z).count), (z).index = 0)
1021
1022 /* Parsing of logical pathnames. */
1023
1024 /* separator between subdirs */
1025 #define semicolonp(c) (chareq(c,ascii(';')))
1026 #define lslashp(c) semicolonp(c)
1027
1028 /* Copy LEN characters in string ORIG starting at ORIG_OFFSET to string DEST,
1029 starting at DEST_OFFSET, up-casing all characters. LEN is > 0. */
copy_upcase(object dest,uintL dest_offset,object orig,uintL orig_offset,uintL len)1030 local void copy_upcase (object dest, uintL dest_offset,
1031 object orig, uintL orig_offset, uintL len) {
1032 sstring_un_realloc(orig);
1033 SstringDispatch(orig,X1, {
1034 var cintX1* ptr1 = &((SstringX1)TheVarobject(orig))->data[orig_offset];
1035 sstring_un_realloc(dest);
1036 SstringDispatch(dest,X2, {
1037 var cintX2* ptr2 = &((SstringX2)TheVarobject(dest))->data[dest_offset];
1038 dotimespL(len,len, { *ptr2++ = as_cint(up_case(as_chart(*ptr1++))); });
1039 });
1040 });
1041 }
1042
1043 /* Parses the name/type/version part (if subdirp=false) or a subdir part
1044 (if subdirp=true) of a logical pathname.
1045 parse_logical_word(&z,subdirp)
1046 > STACK_2: storage vector, a normal-simple-string
1047 > zustand z: start state
1048 < zustand z: updated
1049 < result: a normal-simple-string or :WILD or :WILD-INFERIORS or NIL
1050 can trigger GC */
parse_logical_word(zustand * z,bool subdirp)1051 local maygc object parse_logical_word (zustand* z, bool subdirp) {
1052 ASSERT(sstring_normal_p(STACK_2));
1053 var zustand startz = *z; /* start-state */
1054 var chart ch;
1055 /* Is there a sequence of alphanumeric characters or '*',
1056 no two '*' adjacent (except "**", if subdirp),
1057 and, if subdirp, a ';' ? */
1058 var bool last_was_star = false;
1059 var bool seen_starstar = false;
1060 while (z->count) {
1061 ch = schar(STACK_2,z->index); /* next character */
1062 if (!legal_logical_word_char(ch)) {
1063 if (multiwild_char_p(ch)) {
1064 if (last_was_star) {
1065 if (subdirp && (z->index - startz.index == 1))
1066 seen_starstar = true;
1067 else
1068 break; /* adjacent '*' are forbidden */
1069 } else
1070 last_was_star = true;
1071 } else
1072 break;
1073 }
1074 /* skip character: */
1075 Z_SHIFT(*z,1);
1076 }
1077 var uintL len = z->index - startz.index;
1078 if (subdirp) {
1079 if ((z->count == 0) || !lslashp(ch)) {
1080 *z = startz; return NIL; /* no ';' -> no subdir */
1081 }
1082 /* skip character ';' : */
1083 Z_SHIFT(*z,1);
1084 }
1085 if (len==0)
1086 return NIL;
1087 else if ((len==1) && multiwild_char_p(schar(STACK_2,startz.index)))
1088 return S(Kwild);
1089 else if ((len==2) && seen_starstar)
1090 return S(Kwild_inferiors);
1091 else {
1092 var object result = allocate_string(len);
1093 copy_upcase(result,0,STACK_2,startz.index,len);
1094 return result;
1095 }
1096 }
1097
1098 /* Test whether a string is a digit sequence.
1099 all_digits(string)
1100 > string: a normal-simple-string
1101 < true if the string consists entirely of digits, else false */
all_digits(object string)1102 local bool all_digits (object string) {
1103 var uintL len = Sstring_length(string);
1104 if (len > 0) {
1105 var object storage = string; sstring_un_realloc(storage);
1106 SstringDispatch(storage,X, {
1107 var const cintX* ptr = &((SstringX)TheVarobject(storage))->data[0];
1108 dotimespL(len,len, {
1109 var cintX c = *ptr++;
1110 if (!((c >= '0') && (c <= '9')))
1111 return false;
1112 });
1113 });
1114 }
1115 return true;
1116 }
1117
1118 /* test whether the string contains semicolons (and the rest being valid!),
1119 thus appearing to be a logical pathname
1120 > string: storage vector, a normal-simple-string
1121 < result: true if the string contains semicolons */
looks_logical_p(object string)1122 local bool looks_logical_p (object string) {
1123 var uintL len = Sstring_length(string);
1124 var bool logical_p = false;
1125 if (len > 0) {
1126 SstringDispatch(string,X, {
1127 var const cintX* charptr = &((SstringX)TheVarobject(string))->data[0];
1128 do {
1129 var chart ch = up_case(as_chart(*charptr++));
1130 if (!legal_logical_word_char(ch)) {
1131 if (semicolonp(ch))
1132 logical_p = true;
1133 else if (!colonp(ch) && !dotp(ch) && !multiwild_char_p(ch))
1134 return false; /* invalid logical pathname char */
1135 }
1136 } while (--len);
1137 });
1138 }
1139 return logical_p;
1140 }
1141
1142 /* Attempt to parse a logical host name string, starting at a given state.
1143 parse_logical_host_prefix(&z,string)
1144 > string: storage vector, a normal-simple-string
1145 > state z: start state
1146 < state z: updated to point past the colon after the logical host
1147 < result: logical host, or NIL
1148 can trigger GC */
parse_logical_host_prefix(zustand * zp,object string)1149 local maygc object parse_logical_host_prefix (zustand* zp, object string) {
1150 ASSERT(sstring_normal_p(string));
1151 var object host;
1152 var uintL startindex = zp->index;
1153 var chart ch;
1154 /* a sequence of alphanumeric characters and then ':' */
1155 while (1) {
1156 if (zp->count==0)
1157 return NIL; /* string already ended -> no host */
1158 ch = schar(string,zp->index); /* next character */
1159 if (!legal_logical_word_char(ch))
1160 break;
1161 /* go past alphanumeric character: */
1162 Z_SHIFT(*zp,1);
1163 }
1164 if (!colonp(ch))
1165 return NIL; /* no ':' -> no host */
1166 { /* make host-string: */
1167 var uintL len = zp->index - startindex;
1168 pushSTACK(string);
1169 host = allocate_string(len);
1170 string = popSTACK();
1171 /* and fill it: */
1172 if (len > 0)
1173 copy_upcase(host,0,string,startindex,len);
1174 }
1175 /* skip ':' */
1176 Z_SHIFT(*zp,1);
1177 return host;
1178 }
1179
1180 /* CLHS for MAKE-PATHNAME: "Whenever a pathname is constructed the
1181 components may be canonicalized if appropriate."
1182 simplify the subdirectory list
1183 strings are coerced to normal simple strings
1184 the list should start with a valid startpoint (not checked!)
1185 > dir : pathname directory list
1186 < dir : the same list, destructively modified:
1187 ".." or :back ==> :up
1188 ... x "foo" :up y ... ==> ... x y ...
1189 ... x ""/"." y ... ==> ... x y ...
1190 :absolute :up ==> error
1191 :wild-inferiors :up ==> error
1192 can trigger GC */
simplify_directory(object dir)1193 local maygc object simplify_directory (object dir) {
1194 if (!consp(dir)) return dir;
1195 DOUT("simplify_directory:< ",dir);
1196 pushSTACK(dir);
1197 { /* kill ".", ".."->:up, coerce to normal simple strings */
1198 var object curr = dir;
1199 while (consp(curr) && consp(Cdr(curr))) {
1200 var object next = Cdr(curr);
1201 var object here = Car(next);
1202 if (stringp(here)) {
1203 if (vector_length(here)==0 || string_equal(here,O(dot_string))) {
1204 Cdr(curr) = Cdr(next); /* drop "." and "" */
1205 continue;
1206 } else if (string_equal(here,O(wild_string))) {
1207 Car(next) = S(Kwild);
1208 curr = next;
1209 continue;
1210 } else if (string_equal(here,O(wildwild_string))) {
1211 Car(next) = S(Kwild_inferiors);
1212 curr = next;
1213 continue;
1214 } else if (!consp(next))
1215 break;
1216 if (string_equal(here,O(dotdot_string)))
1217 Car(next) = S(Kup); /* ".." --> :UP */
1218 else { /* coerce to normal */
1219 pushSTACK(next);
1220 var object element = coerce_normal_ss(here);
1221 next = popSTACK();
1222 Car(next) = element;
1223 }
1224 } else if (eq(here,S(Kback)))
1225 Car(next) = S(Kup); /* :BACK --> :UP (ANSI) */
1226 curr = next;
1227 }
1228 }
1229 dir = popSTACK();
1230 /* collapse "foo/../" (quadratic algorithm) */
1231 var bool changed_p;
1232 do {
1233 changed_p = false;
1234 var object curr = dir;
1235 while (consp(curr) && consp(Cdr(curr))) {
1236 var object next = Cdr(curr);
1237 var object here = Car(next);
1238 var object next_next = Cdr(next);
1239 if (consp(next_next)) {
1240 var object next_here = Car(next_next);
1241 /* :BACK has been converted to :UP */
1242 if (!eq(here,S(Kup)) && eq(next_here,S(Kup))) {
1243 if (eq(here,S(Kwild_inferiors)) || eq(here,S(Kabsolute))) {
1244 goto error_absolute_up;
1245 } else {
1246 Cdr(curr) = Cdr(next_next); /* collapse ( "foo" :UP ) */
1247 changed_p = true;
1248 }
1249 } else
1250 curr = next;
1251 } else
1252 curr = next;
1253 }
1254 } while (changed_p);
1255 if (eq(Car(dir),S(Kabsolute)) && consp(Cdr(dir)))
1256 if (eq(Car(Cdr(dir)),S(Kup)))
1257 goto error_absolute_up;
1258 DOUT("simplify_directory:> ",dir);
1259 return dir;
1260 error_absolute_up:
1261 /* <http://www.ai.mit.edu/projects/iiip/doc/CommonLISP/HyperSpec/Body/sec_19-2-2-4-3.html> */
1262 pushSTACK(O(empty_string)); /* FILE-ERROR slot PATHNAME */
1263 pushSTACK(dir); pushSTACK(S(Kdirectory));
1264 pushSTACK(TheSubr(subr_self)->name);
1265 error(file_error,GETTEXT("~S: Illegal ~S argument ~S"));
1266 }
1267
1268 /* Parses a logical pathname.
1269 parse_logical_pathnamestring(z)
1270 > STACK_1: storage vector, a normal-simple-string
1271 > STACK_0: freshly allocated logical pathname
1272 > state z: start state
1273 < STACK_0: same logical pathname, filled
1274 < result: number of remaining characters
1275 can trigger GC */
parse_logical_pathnamestring(zustand z)1276 local maygc uintL parse_logical_pathnamestring (zustand z) {
1277 DOUT("parse_logical_pathnamestring:<0",STACK_0);
1278 DOUT("parse_logical_pathnamestring:<1",STACK_1);
1279 { /* parse Host-Specification: */
1280 var zustand startz = z;
1281 var object host = parse_logical_host_prefix(&z,STACK_1);
1282 if (nullp(host)) {
1283 z = startz; /* back to the start */
1284 host = STACK_(3+2); /* Default-Host */
1285 } else { /* enter host: */
1286 TheLogpathname(STACK_0)->pathname_host = host;
1287 }
1288 }
1289 { /* enter Directory-Start: */
1290 var object new_cons = allocate_cons(); /* new Cons for Startpoint */
1291 TheLogpathname(STACK_0)->pathname_directory = new_cons;
1292 pushSTACK(new_cons); /* new (last (pathname-directory Pathname)) */
1293 }
1294 /* stack layout:
1295 data-vector, pathname, (last (pathname-directory Pathname)).
1296 parse subdirectories:
1297 If ";" is the first char, it is turned into :RELATIVE
1298 (otherwise :ABSOLUTE) as the first subdir
1299 for a reason that escapes me, ANSI CL specifies that
1300 "foo:;bar;baz.zot" is a :RELATIVE logical pathname while
1301 "foo:/bar/baz.zot" is an :ABSOLUTE physical pathname.
1302 see "19.3.1.1.3 The Directory part of a Logical Pathname Namestring"
1303 http://www.ai.mit.edu/projects/iiip/doc/CommonLISP/HyperSpec/Body/sec_19-3-1-1-3.html */
1304 if (Z_AT_SLASH(z,lslashp,STACK_2)) {
1305 Z_SHIFT(z,1);
1306 Car(STACK_0) = S(Krelative);
1307 } else {
1308 Car(STACK_0) = S(Kabsolute);
1309 }
1310 while (1) {
1311 /* try to parse the next subdir */
1312 var object subdir = parse_logical_word(&z,true);
1313 if (nullp(subdir))
1314 break;
1315 /* lengthen (pathname-directory pathname) by Subdir: */
1316 pushSTACK(subdir);
1317 var object new_cons = allocate_cons(); /* new Cons */
1318 Car(new_cons) = popSTACK(); /* = (cons subdir NIL) */
1319 Cdr(STACK_0) = new_cons; /* lengthens (pathname-directory Pathname) */
1320 STACK_0 = new_cons; /* new (last (pathname-directory Pathname)) */
1321 }
1322 { /* parse Name: */
1323 var object name = parse_logical_word(&z,false);
1324 TheLogpathname(STACK_1)->pathname_name = name;
1325 if ((z.count > 0) && dotp(schar(STACK_2,z.index))) {
1326 var zustand z_name = z;
1327 /* skip Character '.' : */
1328 Z_SHIFT(z,1);
1329 /* parse Type: */
1330 var object type = parse_logical_word(&z,false);
1331 TheLogpathname(STACK_1)->pathname_type = type;
1332 if (!nullp(type)) {
1333 if ((z.count > 0) && dotp(schar(STACK_2,z.index))) {
1334 var zustand z_type = z;
1335 /* skip Character '.' : */
1336 Z_SHIFT(z,1);
1337 /* parse Version: */
1338 var object version = parse_logical_word(&z,false);
1339 if (eq(version,S(Kwild))) {
1340 } else if (equal(version,Symbol_name(S(Knewest)))) {
1341 version = S(Knewest);
1342 } else if (stringp(version) && all_digits(version)) {
1343 pushSTACK(version); funcall(L(parse_integer),1);
1344 version = value1; /* version: string -> integer */
1345 } else {
1346 version = NIL;
1347 }
1348 TheLogpathname(STACK_1)->pathname_version = version;
1349 if (nullp(version))
1350 z = z_type; /* restore character '.' */
1351 } else {
1352 TheLogpathname(STACK_1)->pathname_version = NIL;
1353 }
1354 } else {
1355 z = z_name; /* restore character '.' */
1356 TheLogpathname(STACK_1)->pathname_version = NIL;
1357 }
1358 } else {
1359 TheLogpathname(STACK_1)->pathname_type = NIL;
1360 TheLogpathname(STACK_1)->pathname_version = NIL;
1361 }
1362 }
1363 skipSTACK(1);
1364 TheLogpathname(STACK_0)->pathname_directory =
1365 simplify_directory(TheLogpathname(STACK_0)->pathname_directory);
1366 DOUT("parse_logical_pathnamestring:>0",STACK_0);
1367 DOUT("parse_logical_pathnamestring:>1",STACK_1);
1368 return z.count;
1369 }
1370
1371 /* recognition of a logical host, cf. CLtL2 p. 631
1372 (defun logical-host-p (host)
1373 (and (simple-string-p host)
1374 (gethash host sys::*logical-pathname-translations*) ; :test #'equalp !
1375 t)) */
logical_host_p(object host)1376 local bool logical_host_p (object host) {
1377 return (simple_string_p(host)
1378 /* No need to string-upcase host, because it's tested via EQUALP. */
1379 && !eq(gethash(host,Symbol_value(S(logpathname_translations)),false),
1380 nullobj));
1381 }
1382
1383 #define string2wild(str) (equal(str,O(wild_string)) ? S(Kwild) : (object)(str))
1384 #define wild2string(obj) (eq(obj,S(Kwild)) ? (object)O(wild_string) : (obj))
1385
1386 #ifdef PATHNAME_NOEXT
1387 /* can trigger GC */
fix_parse_namestring_dot_file(void)1388 local maygc void fix_parse_namestring_dot_file (void)
1389 { /* make sure *PARSE-NAMESTRING-DOT-FILE* is valid */
1390 Symbol_value(S(parse_namestring_dot_file)) = S(Ktype); /*CLISP default*/
1391 pushSTACK(NIL);
1392 pushSTACK(S(parse_namestring_dot_file));
1393 pushSTACK(S(parse_namestring_dot_file));
1394 pushSTACK(Symbol_value(S(parse_namestring_dot_file)));
1395 STACK_3 = CLSTEXT("The variable ~S had an illegal value.\n"
1396 "~S has been reset to ~S.");
1397 funcall(S(warn),4);
1398 }
1399
1400 /* auxiliary function for PARSE-NAMESTRING:
1401 splits a string (at the last dot) into Name and Type.
1402 split_name_type(skip);
1403 > STACK_0: Normal-Simple-String
1404 > skip: 1 if a dot at the beginning should not trigger the splitting, else 0
1405 < STACK_1: Name
1406 < STACK_0: Type
1407 decrements STACK by 1
1408 can trigger GC */
split_name_type(uintL skip)1409 local maygc void split_name_type (uintL skip) {
1410 if (skip == 0) {
1411 if (eq(Symbol_value(S(parse_namestring_dot_file)),S(Ktype))) { /* OK */
1412 } else if (eq(Symbol_value(S(parse_namestring_dot_file)),S(Kname))) {
1413 skip = 1; /* always have a name! */
1414 } else
1415 fix_parse_namestring_dot_file();
1416 }
1417 var object string = STACK_0;
1418 var uintL length = Sstring_length(string);
1419 /* Search for the last dot: */
1420 var uintL index = length;
1421 if (index > skip) {
1422 SstringDispatch(string,X, {
1423 var const cintX* ptr = &((SstringX)TheVarobject(string))->data[index];
1424 do {
1425 if (*--ptr == '.') goto punkt;
1426 index--;
1427 } while (index > skip);
1428 });
1429 }
1430 /* no dot found -> Type := NIL */
1431 { pushSTACK(NIL); }
1432 goto name_type_ok;
1433 punkt: /* dot found at index */
1434 /* type := (substring string index) */
1435 pushSTACK(subsstring(string,index,length));
1436 /* name := (substring string 0 (1- index)) */
1437 STACK_1 = subsstring(STACK_1,0,index-1);
1438 name_type_ok:
1439 STACK_0 = string2wild(STACK_0);
1440 STACK_1 = string2wild(STACK_1);
1441 }
1442 #endif
1443
1444 /* (PARSE-NAMESTRING thing [host [defaults [:start] [:end] [:junk-allowed]]]),
1445 CLTL p. 414 */
1446 LISPFUN(parse_namestring,seclass_rd_sig,1,2,norest,key,3,
1447 (kw(start),kw(end),kw(junk_allowed)) ) {
1448 /* stack layout: thing, host, defaults, start, end, junk-allowed. */
1449 var bool junk_allowed;
1450 var bool parse_logical = false;
1451 DOUT("parse-namestring:[thng]",STACK_5);
1452 DOUT("parse-namestring:[host]",STACK_4);
1453 DOUT("parse-namestring:[dflt]",STACK_3);
1454 DOUT("parse-namestring:[beg]",STACK_2);
1455 DOUT("parse-namestring:[end]",STACK_1);
1456 DOUT("parse-namestring:[junk]",STACK_0);
1457 { /* 1. check junk-allowed: */
1458 var object obj = popSTACK(); /* junk-allowed-Argument */
1459 junk_allowed = !missingp(obj);
1460 }
1461 /* stack layout: thing, host, defaults, start, end.
1462 2. default-value for start is 0: */
1463 if (!boundp(STACK_1))
1464 STACK_1 = Fixnum_0;
1465 { /* 3. check host: */
1466 var object host = STACK_3;
1467 #if HAS_HOST
1468 host = test_optional_host(host,false);
1469 #else
1470 host = test_optional_host(host);
1471 #endif
1472 if (nullp(host)) {
1473 /* host := (PATHNAME-HOST defaults) */
1474 var object defaults = test_default_pathname(STACK_2);
1475 if (logpathnamep(defaults))
1476 parse_logical = true;
1477 host = xpathname_host(parse_logical,defaults);
1478 } else if (logical_host_p(host)) {
1479 parse_logical = true; host = string_upcase(host);
1480 }
1481 STACK_3 = host;
1482 }
1483 /* 4. thing must be a String: */
1484 DOUT("parse-namestring:[thng]",STACK_4);
1485 DOUT("parse-namestring:[host]",STACK_3);
1486 DOUT("parse-namestring:[dflt]",STACK_2);
1487 var object thing = STACK_4;
1488 if (xpathnamep(thing)) { /* Pathname? */
1489 value1 = thing; /* 1. value thing */
1490 done:
1491 DOUT("parse-namestring:[done]",value1);
1492 value2 = STACK_1; mv_count=2; /* 2. value start */
1493 skipSTACK(5); return;
1494 }
1495 if (builtin_stream_p(thing)) { /* Stream? */
1496 thing = as_file_stream(thing);
1497 test_file_stream_named(thing);
1498 value1 = TheStream(thing)->strm_file_name; /* 1. value: Filename */
1499 goto done; /* 2. value like above */
1500 }
1501 /* thing should now be at least a String or a Symbol: */
1502 var bool thing_symbol = false;
1503 if (!stringp(thing)) {
1504 if (!symbolp(thing) || !nullpSv(parse_namestring_ansi))
1505 error_pathname_designator(thing);
1506 thing = Symbol_name(thing); /* Symbol -> use symbol name */
1507 thing_symbol = true;
1508 STACK_4 = thing; /* and write back into the Stack */
1509 }
1510 /* thing = STACK_4 is now a String.
1511 it will be traversed. */
1512 var zustand z; /* running state */
1513 {
1514 var object string; /* String thing */
1515 { /* check boundaries, with thing, start, end as arguments: */
1516 var stringarg arg;
1517 pushSTACK(thing); pushSTACK(STACK_(1+1)); pushSTACK(STACK_(0+2));
1518 test_string_limits_ro(&arg);
1519 string = arg.string;
1520 z.index = arg.offset+arg.index; /* z.index = start-argument, */
1521 z.count = arg.len; /* z.count = number of characters. */
1522 z.FNindex = fixnum(arg.index); /* z.FNindex = start-Index as Fixnum. */
1523 }
1524 if (!parse_logical) {
1525 /* Check whether *PARSE-NAMESTRING-ANSI* is true and the string
1526 starts with a logical hostname. */
1527 if (!nullpSv(parse_namestring_ansi)) {
1528 /* Coerce string to be a normal-simple-string. */
1529 #ifdef HAVE_SMALL_SSTRING
1530 SstringCase(string,{ Z_SUB(z,string); },{ Z_SUB(z,string); },{},{ Z_SUB(z,string); });
1531 #endif
1532 pushSTACK(string);
1533 var zustand tmp = z;
1534 var object host = parse_logical_host_prefix(&tmp,string);
1535 string = popSTACK();
1536 DOUT("parse-namestring:",string);
1537 DOUT("parse-namestring:",host);
1538 if (!nullp(host)
1539 /* Test whether the given hostname is valid. This is not
1540 strictly what ANSI specifies, but is better than giving
1541 an error for Win32 pathnames like "C:\\FOOBAR". */
1542 && logical_host_p(host))
1543 parse_logical = true;
1544 else
1545 /* ANSI CL specifies that we should look at the entire string, using
1546 parse_logical_pathnamestring, not only parse_logical_host_prefix. */
1547 parse_logical = looks_logical_p(string);
1548 }
1549 }
1550 if (thing_symbol && !parse_logical) {
1551 #if defined(PATHNAME_UNIX) || defined(PATHNAME_WIN32)
1552 /* operating system with preference for small letters */
1553 Z_SUB(z,string); /* yes -> convert with STRING-DOWNCASE */
1554 pushSTACK(string);
1555 nstring_downcase(string,0,Sstring_length(string));
1556 string = popSTACK();
1557 sstring_un_realloc(string);
1558 #endif
1559 }
1560 /* Coerce string to be a normal-simple-string. */
1561 #ifdef HAVE_SMALL_SSTRING
1562 SstringCase(string,{ Z_SUB(z,string); },{ Z_SUB(z,string); },{},{ Z_SUB(z,string); });
1563 #endif
1564 pushSTACK(string);
1565 }
1566 if (parse_logical) {
1567 pushSTACK(allocate_logpathname());
1568 /* stack layout: ..., data-vector, pathname. */
1569 var uintL remaining = parse_logical_pathnamestring(z);
1570 z.index += z.count-remaining; z.FNindex = fixnum_inc(z.FNindex,z.count-remaining); z.count = remaining;
1571 } else {
1572 pushSTACK(allocate_pathname());
1573 /* stack layout: ..., data-vector, pathname.
1574 separator between subdirs is on WIN32 both '\' and '/': */
1575 #if HAS_HOST
1576 { /* parse Host-Specification: */
1577 var object host;
1578 {
1579 var zustand startz = z; /* start-state */
1580 var chart ch;
1581 #if defined(PATHNAME_WIN32)
1582 /* Look for two slashes, then a sequence of characters. */
1583 if (z.count==0) goto no_hostspec;
1584 ch = TheSnstring(STACK_1)->data[z.index];
1585 if (!pslashp(ch)) goto no_hostspec;
1586 Z_SHIFT(z,1);
1587 if (z.count==0) goto no_hostspec;
1588 ch = TheSnstring(STACK_1)->data[z.index];
1589 if (!pslashp(ch)) goto no_hostspec;
1590 Z_SHIFT(z,1);
1591 while (z.count) {
1592 ch = TheSnstring(STACK_1)->data[z.index];
1593 if (!legal_hostchar(ch))
1594 break;
1595 /* Skip past valid host char. */
1596 Z_SHIFT(z,1);
1597 }
1598 /* Create host string. */
1599 if (z.index - startz.index - 2 == 0)
1600 goto no_hostspec;
1601 host = subsstring(STACK_1,startz.index+2,z.index);
1602 /* Note: The next character in the string is not a letter or '*';
1603 therefore the device of the resulting pathname will be NIL. */
1604 goto hostspec_ok;
1605 #else
1606 /* is it a sequence of alphanumeric characters and then a ':' resp. '::' ? */
1607 while (1) {
1608 if (z.count==0)
1609 goto no_hostspec; /* string already through -> no Host */
1610 ch = TheSnstring(STACK_1)->data[z.index]; /* next character */
1611 if (!alphanumericp(ch))
1612 break;
1613 /* skip alphanumeric character: */
1614 Z_SHIFT(z,1);
1615 }
1616 if (!colonp(ch))
1617 goto no_hostspec; /* no ':' -> no host */
1618 /* build host-string: */
1619 host = subsstring(STACK_1,startz.index,z.index);
1620 /* skip character ':' : */
1621 Z_SHIFT(z,1);
1622 goto hostspec_ok;
1623 #endif
1624 no_hostspec: /* no host-specification */
1625 z = startz; /* back to start */
1626 host = STACK_(3+2); /* Default-Host */
1627 }
1628 hostspec_ok: /* enter host: */
1629 ThePathname(STACK_0)->pathname_host = host;
1630 }
1631 #endif /* HAS_HOST */
1632 #if HAS_DEVICE
1633 #ifdef PATHNAME_WIN32
1634 { /* parse one-letter Device-Specification: */
1635 var object device = NIL; /* Device := NIL */
1636 /* parse Drive-Specification:
1637 Is there a letter ('*','A'-'Z','a'-'z') and then a ':' ? */
1638 {
1639 var zustand startz = z; /* start-state */
1640 var chart ch;
1641 if (z.count==0)
1642 goto no_drivespec; /* string already through ? */
1643 ch = TheSnstring(STACK_1)->data[z.index]; /* next character */
1644 ch = up_case(ch); /* as capital letter */
1645 if (multiwild_char_p(ch)) { /* ch = '*' -> Device := :WILD */
1646 device = S(Kwild);
1647 } else if ((as_cint(ch) >= 'A') && (as_cint(ch) <= 'Z')) {
1648 /* 'A' <= ch <= 'Z' -> Device := "ch" */
1649 var object string = allocate_string(1); /* String of length 1 */
1650 TheSnstring(string)->data[0] = ch; /* with ch as sole letter */
1651 device = string;
1652 } else
1653 goto no_device;
1654 /* Device OK, skip character: */
1655 Z_SHIFT(z,1);
1656 if (z.count==0)
1657 goto no_drivespec; /* string already through ? */
1658 ch = TheSnstring(STACK_1)->data[z.index]; /* next character */
1659 ch = up_case(ch); /* as capital letter */
1660 no_device:
1661 /* concluded with colon? */
1662 if (!colonp(ch))
1663 goto no_drivespec;
1664 /* skip character: */
1665 Z_SHIFT(z,1);
1666 goto drivespec_ok;
1667 no_drivespec:
1668 /* parsing a Drive-Specification did not succeed. */
1669 z = startz; /* restore start-state */
1670 device = NIL; /* Device := NIL */
1671 }
1672 drivespec_ok: /* enter Device */
1673 ThePathname(STACK_0)->pathname_device = device;
1674 }
1675 #endif /* PATHNAME_WIN32 */
1676 #endif /* HAS_DEVICE */
1677 /* enter Directory-Start: */
1678 ThePathname(STACK_0)->pathname_directory = NIL;
1679 pushSTACK(NIL); /* new (last (pathname-directory Pathname)) */
1680 /* stack layout:
1681 ..., Datenvektor, Pathname, (last (pathname-directory Pathname)).
1682 parse subdirectories: */
1683 {
1684 #if defined(PATHNAME_UNIX)
1685 /* if there is a '~' immediately, a username is read up to the next '/'
1686 or string-end and the Home-Directory of this user is inserted: */
1687 if ((z.count != 0) && chareq(schar(STACK_2,z.index),ascii('~'))) {
1688 /* there is a '~' immediately.
1689 skip character: */
1690 Z_SHIFT(z,1);
1691 var object userhomedir; /* Pathname of the User-Homedir */
1692 /* search next '/' : */
1693 var uintL charcount = 0;
1694 if (z.count > 0) {
1695 SstringDispatch(STACK_2,X, {
1696 var const cintX* charptr =
1697 &((SstringX)TheVarobject(STACK_2))->data[z.index];
1698 var uintL count;
1699 dotimespL(count,z.count, {
1700 if (*charptr++ == '/') break;
1701 charcount++;
1702 });
1703 });
1704 }
1705 /* Username has charcount characters */
1706 if (charcount==0) {
1707 userhomedir = O(user_homedir); /* only '~' -> User-Homedir */
1708 } else { /* build username: */
1709 var object username =
1710 subsstring(STACK_2,z.index,z.index+charcount);
1711 /* fetch his/her Home-Directory from the password-file: */
1712 with_sstring_0(username,O(misc_encoding),username_asciz, {
1713 begin_system_call();
1714 errno = 0;
1715 var struct passwd * userpasswd = getpwnam(username_asciz);
1716 if (userpasswd == (struct passwd *)NULL) { /* unsuccessful? */
1717 if (!(errno==0)) { OS_error(); } /* report error */
1718 end_system_call();
1719 /* else: error */
1720 pushSTACK(username);
1721 pushSTACK(S(parse_namestring));
1722 error(parse_error,GETTEXT("~S: there is no user named ~S"));
1723 }
1724 end_system_call();
1725 userhomedir = /* homedir as pathname */
1726 asciz_dir_to_pathname(userpasswd->pw_dir,O(misc_encoding));
1727 });
1728 }
1729 /* copy directory from the pathname userhomedir:
1730 (copy-list dir) = (nreconc (reverse dir) nil),
1731 after it memorize its last Cons. */
1732 userhomedir = reverse(ThePathname(userhomedir)->pathname_directory);
1733 userhomedir = nreconc(userhomedir,NIL);
1734 ThePathname(STACK_1)->pathname_directory = userhomedir;
1735 while (mconsp(Cdr(userhomedir))) { userhomedir = Cdr(userhomedir); }
1736 STACK_0 = userhomedir;
1737 /* skip username-characters: */
1738 Z_SHIFT(z,charcount);
1739 /* if the string is through: finished,
1740 otherwise a '/' follows immediately , it will be skipped: */
1741 if (z.count==0) { /* Name and Type := NIL */
1742 pushSTACK(NIL); pushSTACK(NIL); goto after_name_type;
1743 }
1744 /* skip character: */
1745 Z_SHIFT(z,1);
1746 } else
1747 #endif /* PATHNAME_UNIX */
1748 #if defined(PATHNAME_UNIX) && 0
1749 /* What is this needed for, except for $HOME ?
1750 If a '$' follows immediately, an Environment-Variable is read up
1751 to the next '/' or string-end and its value is inserted: */
1752 if ((z.count != 0)
1753 && chareq(TheSnstring(STACK_2)->data[z.index],ascii('$'))) {
1754 /* A '$' follows immediately.
1755 skip character: */
1756 Z_SHIFT(z,1);
1757 var object envval_dir;
1758 /* search next '/' : */
1759 var uintL charcount = 0;
1760 {
1761 var const chart* charptr = &TheSnstring(STACK_2)->data[z.index];
1762 var uintL count;
1763 dotimesL(count,z.count, {
1764 if (chareq(*charptr++,ascii('/')))
1765 break;
1766 charcount++;
1767 });
1768 }
1769 { /* Environment-Variable has charcount characters. */
1770 var object envvar =
1771 subsstring(STACK_2,z.index,z.index+charcount);
1772 /* fetch its value: */
1773 with_sstring_0(envvar,O(misc_encoding),envvar_asciz, {
1774 begin_system_call();
1775 var const char* envval = getenv(envvar_asciz);
1776 end_system_call();
1777 if (envval==NULL) {
1778 pushSTACK(envvar);
1779 pushSTACK(S(parse_namestring));
1780 error(parse_error,
1781 GETTEXT("~S: there is no environment variable ~S"));
1782 }
1783 envval_dir = /* value of the variable as pathname */
1784 asciz_dir_to_pathname(envval,O(misc_encoding));
1785 });
1786 }
1787 /* copy directory from the pathname envval_dir:
1788 (copy-list dir) = (nreconc (reverse dir) nil),
1789 afterwards memorize its last Cons. */
1790 envval_dir = reverse(ThePathname(envval_dir)->pathname_directory);
1791 envval_dir = nreconc(envval_dir,NIL);
1792 ThePathname(STACK_1)->pathname_directory = envval_dir;
1793 while (mconsp(Cdr(envval_dir))) { envval_dir = Cdr(envval_dir); }
1794 STACK_0 = envval_dir;
1795 /* skip envvar-characters: */
1796 Z_SHIFT(z,charcount);
1797 /* if the string is through: finished,
1798 otherwise a '/' follows immediately , it will be skipped: */
1799 if (z.count==0) { /* Name and Type := NIL */
1800 pushSTACK(NIL); pushSTACK(NIL); goto after_name_type;
1801 }
1802 /* skip character: */
1803 Z_SHIFT(z,1);
1804 } else
1805 #endif /* PATHNAME_UNIX & 0 */
1806 #if defined(PATHNAME_UNIX) || defined(PATHNAME_WIN32)
1807 #if defined(UNIX_CYGWIN)
1808 if (z.count > 1 && !nullpSv(device_prefix)
1809 && colonp(schar(STACK_2,z.index+1))) {
1810 /* if string starts with 'x:', treat it as a device */
1811 var chart ch = down_case(schar(STACK_2,z.index));
1812 if ((as_cint(ch) >= 'a') && (as_cint(ch) <= 'z')) {
1813 pushSTACK(allocate_string(1)); /* drive */
1814 TheSnstring(STACK_0)->data[0] = ch;
1815 var object new_cons = allocate_cons();
1816 Car(new_cons) = popSTACK(); /* drive */
1817 ThePathname(STACK_1)->pathname_directory = new_cons;
1818 STACK_0 = new_cons;
1819 Z_SHIFT(z,2);
1820 if (Z_AT_SLASH(z,pslashp,STACK_2)) Z_SHIFT(z,1);
1821 } else goto continue_parsing_despite_colon;
1822 } else
1823 continue_parsing_despite_colon:
1824 #endif
1825 /* if 1st char is a slash, start with :ABSOLUTE (otherwise :RELATIVE): */
1826 if (Z_AT_SLASH(z,pslashp,STACK_2)) {
1827 Z_SHIFT(z,1);
1828 var object new_cons = allocate_cons();
1829 Car(new_cons) = S(Kabsolute);
1830 ThePathname(STACK_1)->pathname_directory = new_cons;
1831 STACK_0 = new_cons;
1832 }
1833 #endif
1834 while (1) {
1835 /* try to parse another subdirectory. */
1836 #ifdef PATHNAME_NOEXT
1837 {
1838 var uintL z_start_index = z.index; /* index at the start */
1839 while (1) {
1840 var chart ch;
1841 if (z.count == 0)
1842 break;
1843 ch = schar(STACK_2,z.index); /* next character */
1844 if (!legal_namechar(ch)) /* valid character ? */
1845 break;
1846 /* yes -> part of the name
1847 skip character: */
1848 Z_SHIFT(z,1);
1849 }
1850 /* reached end of the name.
1851 Name := substring of STACK_2 from z_start_index (inclusive)
1852 to z.index (exclusive). */
1853 var object string = subsstring(STACK_2,z_start_index,z.index);
1854 /* name finished. */
1855 pushSTACK(string);
1856 }
1857 /* if a '/' resp. '\' follows immediately, then it was a subdirectory,
1858 else the pathname is finished: */
1859 if (!Z_AT_SLASH(z,pslashp,STACK_3))
1860 /* no -> it was the name and no subdir. */
1861 break;
1862 /* a '/' resp. '\' follows. skip character: */
1863 Z_SHIFT(z,1);
1864 /* stack layout: ...,
1865 data-vector, pathname, (last (pathname-directory Pathname)),
1866 subdir. */
1867 /* was it '**' or '...' ? */
1868 if (equal(STACK_0,O(wildwild_string))
1869 || equal(STACK_0,O(dotdotdot_string))) {
1870 STACK_0 = S(Kwild_inferiors); /* replace with :WILD-INFERIORS */
1871 }
1872 #endif /* PATHNAME_NOEXT */
1873 if (nullp(STACK_1)) {
1874 var object new_cons = allocate_cons();
1875 Car(new_cons) = S(Krelative);
1876 ThePathname(STACK_2)->pathname_directory = new_cons;
1877 STACK_1 = new_cons;
1878 }
1879 /* lengthen (pathname-directory pathname) by subdir STACK_0: */
1880 var object new_cons = allocate_cons(); /* new Cons */
1881 Car(new_cons) = popSTACK(); /* = (cons subdir NIL) */
1882 Cdr(STACK_0) = new_cons; /* lengthened (pathname-directory Pathname) */
1883 STACK_0 = new_cons; /* new (last (pathname-directory Pathname)) */
1884 }
1885 #ifdef PATHNAME_NOEXT
1886 /* stack layout: ..., data-vector, pathname,
1887 (last (pathname-directory Pathname)), string. */
1888 split_name_type(0); /* split string STACK_0 in name and type */
1889 after_name_type:
1890 /* stack layout: ..., data-vector, pathname,
1891 (last (pathname-directory Pathname)), name, type. */
1892 { /* enter name and type in pathname: */
1893 var object type = popSTACK();
1894 var object name = popSTACK();
1895 skipSTACK(1); /* directory is already entered */
1896 /* replace name="" with name=NIL: */
1897 if (equal(name,O(empty_string)))
1898 name = NIL;
1899 var object pathname = STACK_0;
1900 ThePathname(pathname)->pathname_name = name;
1901 ThePathname(pathname)->pathname_type = type;
1902 }
1903 #endif
1904 #ifdef WIN32_NATIVE
1905 var object pathname = STACK_0;
1906 var object dir = ThePathname(pathname)->pathname_directory;
1907 var object dev = Symbol_value(S(device_prefix));
1908 if (nullp(ThePathname(pathname)->pathname_device)
1909 /* actually, we already know that dir is a cons */
1910 && consp(dir) && eq(Car(dir),S(Kabsolute))
1911 /* Cdr(dir) might not be a cons, e.g., "/foo" ==
1912 #S(pathname :directory (:absolute) :name "foo") */
1913 && consp(Cdr(dir)) && consp(Cdr(Cdr(dir)))
1914 && stringp(dev) && stringp(Car(Cdr(dir)))
1915 && string_eqcomp_ci(Car(Cdr(dir)),0,dev,0,vector_length(dev))) {
1916 /* path = (:ABSOLUTE "cygdrive" "drive" "dir1" ...) ===>
1917 path = (:ABSOLUTE "dir1" ...); device = "DRIVE" */
1918 var object device = Car(Cdr(Cdr(dir)));
1919 Cdr(dir) = Cdr(Cdr(Cdr(dir)));
1920 device = string_upcase(device);
1921 ThePathname(STACK_0)->pathname_device = device;
1922 }
1923 #endif
1924 #ifdef UNIX_CYGWIN
1925 var object dir = ThePathname(STACK_0)->pathname_directory;
1926 if (consp(dir) && stringp(Car(dir))) {
1927 /* dir = ("c" ...) --> (:absolute *device-prefix* "c" ...)*/
1928 pushSTACK(S(Kabsolute));
1929 pushSTACK(Symbol_value(S(device_prefix)));
1930 dir = listof(2);
1931 Cdr(Cdr(dir)) = ThePathname(STACK_0)->pathname_directory;
1932 ThePathname(STACK_0)->pathname_directory = dir;
1933 }
1934 #endif
1935 ThePathname(STACK_0)->pathname_directory =
1936 simplify_directory(ThePathname(STACK_0)->pathname_directory);
1937 }
1938 }
1939 /* Pathname is finished.
1940 stack layout: ..., data-vector, pathname. */
1941 if (!junk_allowed)
1942 /* Check whether no more characters remain */
1943 if (!(z.count == 0)) {
1944 pushSTACK(z.FNindex); /* last index */
1945 pushSTACK(STACK_(4+2+1)); /* thing */
1946 pushSTACK(S(parse_namestring));
1947 error(parse_error,
1948 GETTEXT("~S: syntax error in filename ~S at position ~S"));
1949 }
1950 /* Check that if a :host argument (or :host component of the :defaults
1951 argument) was present and the parsed pathname has a host component,
1952 they agree; and set the :host component of the result otherwise */
1953 if (!missingp(STACK_(3+2))) {
1954 if (parse_logical) {
1955 var object parsed_host = TheLogpathname(STACK_0)->pathname_host;
1956 if (!nullp(parsed_host)) {
1957 if (!equal(STACK_(3+2),parsed_host)) {
1958 pushSTACK(STACK_0);
1959 pushSTACK(parsed_host);
1960 pushSTACK(STACK_(3+2+2));
1961 pushSTACK(S(parse_namestring));
1962 error(error_condition,GETTEXT("~S: hosts ~S and ~S of ~S should coincide"));
1963 }
1964 } else
1965 TheLogpathname(STACK_0)->pathname_host = STACK_(3+2);
1966 } else {
1967 #if HAS_HOST
1968 var object parsed_host = ThePathname(STACK_0)->pathname_host;
1969 if (!nullp(parsed_host)) {
1970 if (!equal(STACK_(3+2),parsed_host)) {
1971 pushSTACK(STACK_0);
1972 pushSTACK(parsed_host);
1973 pushSTACK(STACK_(3+2+2));
1974 pushSTACK(S(parse_namestring));
1975 error(error_condition,GETTEXT("~S: hosts ~S and ~S of ~S should coincide"));
1976 }
1977 } else
1978 ThePathname(STACK_0)->pathname_host = STACK_(3+2);
1979 #endif
1980 }
1981 }
1982 value1 = STACK_0; /* pathname as 1st value */
1983 value2 = z.FNindex; /* index as 2nd value */
1984 mv_count=2; /* 2 values */
1985 DOUT("parse-namestring:[end ret]",value1);
1986 skipSTACK(5+2); return;
1987 }
1988 #undef colonp
1989 #undef Z_SUB
1990 #undef Z_AT_SLASH
1991 #undef Z_SHIFT
1992
1993 /* UP: Converts an object into a pathname.
1994 coerce_xpathname(object)
1995 > object: object
1996 < result: (PATHNAME Objekt)
1997 can trigger GC */
coerce_xpathname(object obj)1998 local maygc object coerce_xpathname (object obj) {
1999 if (xpathnamep(obj)) {
2000 /* nothing to do for pathnames. */
2001 return obj;
2002 } else {
2003 /* else: call PARSE-NAMESTRING: */
2004 pushSTACK(obj); funcall(L(parse_namestring),1);
2005 return value1;
2006 }
2007 }
2008
2009 LISPFUNNR(pathname,1) { /* (PATHNAME pathname), CLTL p. 413 */
2010 VALUES1(coerce_xpathname(popSTACK()));
2011 }
2012
2013 #define PATH_VALUE(slot,common) do { \
2014 object ret = ThePathname(pathname)->slot; \
2015 value1 = eq(STACK_0,S(Kcommon)) ? common(ret) : ret; \
2016 } while(0)
2017 #define LOG_PATH_VALUE(slot) \
2018 if (logpathnamep(pathname)) value1 = TheLogpathname(pathname)->slot; else
2019
2020 /* (PATHNAME-HOST pathname [:case]), CLTL p. 417, CLtL2 p. 644 */
2021 LISPFUN(pathnamehost,seclass_read,1,0,norest,key,1, (kw(case))) {
2022 var object pathname = coerce_xpathname(STACK_1);
2023 LOG_PATH_VALUE(pathname_host)
2024 #if HAS_HOST
2025 PATH_VALUE(pathname_host,common_case);
2026 #else
2027 value1 = NIL;
2028 #endif
2029 mv_count = 1; skipSTACK(2);
2030 }
2031
2032 /* (PATHNAME-DEVICE pathname [:case]), CLTL p. 417, CLtL2 p. 644 */
2033 LISPFUN(pathnamedevice,seclass_read,1,0,norest,key,1, (kw(case))) {
2034 var object pathname = coerce_xpathname(STACK_1);
2035 if (logpathnamep(pathname)) {
2036 /* http://www.ai.mit.edu/projects/iiip/doc/CommonLISP/HyperSpec/Body/sec_19-3-2-1.html */
2037 value1 = S(Kunspecific);
2038 } else
2039 #if HAS_DEVICE
2040 PATH_VALUE(pathname_device,common_case);
2041 #else
2042 value1 = NIL;
2043 #endif
2044 mv_count = 1; skipSTACK(2);
2045 }
2046
2047 #define PATH_SLOT(slot,common) \
2048 object pathname = coerce_xpathname(STACK_1); \
2049 LOG_PATH_VALUE(slot) PATH_VALUE(slot,common); \
2050 mv_count = 1; skipSTACK(2)
2051
2052 /* (PATHNAME-DIRECTORY pathname [:case]), CLTL p. 417, CLtL2 p. 644 */
2053 LISPFUN(pathnamedirectory,seclass_read,1,0,norest,key,1, (kw(case)))
2054 { PATH_SLOT(pathname_directory,subst_common_case); }
2055
2056 /* (PATHNAME-NAME pathname [:case]), CLTL p. 417, CLtL2 p. 644 */
2057 LISPFUN(pathnamename,seclass_read,1,0,norest,key,1, (kw(case)))
2058 { PATH_SLOT(pathname_name,common_case); }
2059
2060 /* (PATHNAME-TYPE pathname [:case]), CLTL p. 417, CLtL2 p. 644 */
2061 LISPFUN(pathnametype,seclass_read,1,0,norest,key,1, (kw(case)))
2062 { PATH_SLOT(pathname_type,common_case); }
2063
2064 /* (PATHNAME-VERSION pathname), CLTL p. 417, CLtL2 p. 644 */
2065 LISPFUNNR(pathnameversion,1) {
2066 var object pathname = coerce_xpathname(popSTACK());
2067 VALUES1(xpathname_version(logpathnamep(pathname),pathname));
2068 }
2069
2070 /* Converts obj to a pathname. If obj is a string, it is even converted to a
2071 logical pathname.
2072 can trigger GC */
parse_as_logical(object obj)2073 local maygc object parse_as_logical (object obj) {
2074 /* The value of (PARSE-NAMESTRING obj nil empty-logical-pathname) is always
2075 a logical pathname, if obj is a string. (But not if it is a stream!) */
2076 pushSTACK(obj); pushSTACK(NIL);
2077 pushSTACK(O(empty_logical_pathname));
2078 funcall(L(parse_namestring),3);
2079 return value1;
2080 }
2081
2082 /* Handler: Signals a TYPE-ERROR with the same error message as the current
2083 condition. */
signal_type_error(void * sp,gcv_object_t * frame,object label,object condition)2084 local void signal_type_error (void* sp, gcv_object_t* frame, object label,
2085 object condition) {
2086 unused(sp); unused(label);
2087 /* Fetch the thing. It was in STACK_0 before the frame was established. */
2088 var gcv_object_t* FRAME = frame;
2089 FRAME = topofframe(FRAME_(0));
2090 var object thing = FRAME_(0);
2091 /* (SYS::ERROR-OF-TYPE 'TYPE-ERROR
2092 :DATUM thing
2093 :EXPECTED-TYPE '(AND STRING (SATISFIES SYSTEM::VALID-LOGICAL-PATHNAME-STRING-P))
2094 "~A" condition) */
2095 pushSTACK(S(type_error));
2096 pushSTACK(S(Kdatum)); pushSTACK(thing);
2097 pushSTACK(S(Kexpected_type)); pushSTACK(O(type_logical_pathname_string));
2098 pushSTACK(O(tildeA)); pushSTACK(condition);
2099 funcall(L(error_of_type),7);
2100 }
2101
2102 LISPFUNNS(logical_pathname,1)
2103 { /* (LOGICAL-PATHNAME thing), CLtL2 p. 631 */
2104 var object thing = STACK_0;
2105 if (logpathnamep(thing)) {
2106 /* nothing to do for logical pathnames. */
2107 VALUES1(thing);
2108 } else if (pathnamep(thing)) {
2109 /* normal pathnames cannot be converted into logical pathnames. */
2110 pushSTACK(thing); /* TYPE-ERROR slot DATUM */
2111 pushSTACK(O(type_logical_pathname)); /* TYPE-ERROR slot EXPECTED-TYPE */
2112 pushSTACK(thing);
2113 pushSTACK(S(logical_pathname));
2114 error(type_error,GETTEXT("~S: argument ~S is not a logical pathname, string, stream or symbol"));
2115 } else if (builtin_stream_p(thing)) { /* Stream? */
2116 thing = as_file_stream(thing);
2117 test_file_stream_named(thing);
2118 var object pathname = TheStream(thing)->strm_file_name;
2119 if (!logpathnamep(pathname)) {
2120 /* Normal pathnames cannot be converted into logical pathnames. */
2121 pushSTACK(pathname); /* TYPE-ERROR slot DATUM */
2122 pushSTACK(O(type_logical_pathname)); /* TYPE-ERROR slot EXPECTED-TYPE */
2123 pushSTACK(thing); pushSTACK(S(logical_pathname));
2124 error(type_error,GETTEXT("~S: the stream ~S was not opened with a logical pathname"));
2125 }
2126 VALUES1(pathname);
2127 } else {
2128 /* ANSI CL requires that we transform PARSE-ERROR into TYPE-ERROR. */
2129 make_C_HANDLER_frame(O(handler_for_parse_error),&signal_type_error,NULL);
2130 var object pathname = parse_as_logical(thing);
2131 unwind_C_HANDLER_frame();
2132 /* Check that a host was given. This makes it hard to create relative
2133 logical pathnames, but it is what ANSI CL specifies. */
2134 if (nullp(TheLogpathname(pathname)->pathname_host)) {
2135 pushSTACK(TheLogpathname(pathname)->pathname_host); /* TYPE-ERROR slot DATUM */
2136 pushSTACK(S(string)); /* TYPE-ERROR slot EXPECTED-TYPE */
2137 pushSTACK(STACK_(0+2)); pushSTACK(S(logical_pathname));
2138 error(type_error,GETTEXT("~S: argument ~S does not contain a host specification"));
2139 }
2140 VALUES1(pathname);
2141 }
2142 skipSTACK(1);
2143 }
2144
2145 /* forward declaration */
2146 local object use_default_dir (object pathname);
2147
2148 /* (TRANSLATE-LOGICAL-PATHNAME pathname &key [:absolute]), CLtL2 p. 631 */
2149 LISPFUN(translate_logical_pathname,seclass_default,1,0,norest,key,1,
2150 (kw(absolute))) {
2151 var bool absolute_p = !missingp(STACK_0);
2152 var object pathname;
2153 skipSTACK(1); /* drop :ABSOLUTE */
2154 /* It is not clear from the ANSI CL spec how the argument shall be coerced
2155 to a pathname. But the examples in the spec indicate that if the
2156 argument is a string, it should be converted to a logical pathname,
2157 by calling LOGICAL-PATHNAME, not by calling PATHNAME. */
2158 if (stringp(STACK_0)) {
2159 funcall(L(logical_pathname),1); pathname = value1;
2160 } else {
2161 pathname = coerce_xpathname(popSTACK());
2162 }
2163 if (logpathnamep(pathname)) {
2164 /* Conversion of a logical into a normal pathname:
2165 (let ((ht (make-hash-table :key-type 'logical-pathname :value-type '(eql t)
2166 :test #'equal)))
2167 (loop
2168 (when (gethash pathname ht) (error "Translation loop"))
2169 (setf (gethash pathname ht) t)
2170 (let ((host (or (pathname-host pathname) "SYS")))
2171 (unless (logical-host-p host) (error "No translation for host"))
2172 (let* ((translations
2173 (gethash host sys::*logical-pathname-translations*))
2174 (translation
2175 (assoc pathname translations :test #'pathname-match-p)))
2176 (unless (and translation (consp translation)
2177 (consp (cdr translation)))
2178 (error "No translation for pathname"))
2179 (setq pathname (translate-pathname pathname (first translation)
2180 (second translation)))))
2181 (unless (sys::logical-pathname-p pathname) (return)))
2182 pathname) */
2183 pushSTACK(pathname);
2184 DOUT("translate-logical-pathname: <",pathname);
2185 pushSTACK(S(Ktest)); pushSTACK(L(equal)); funcall(L(make_hash_table),2);
2186 pushSTACK(value1);
2187 /* stack layout: pathname, ht. */
2188 while (1) {
2189 if (!nullp(shifthash(STACK_0,STACK_1,T,true))) {
2190 /* STACK_1 = pathname; -- FILE-ERROR slot PATHNAME */
2191 STACK_0 = STACK_1;
2192 pushSTACK(S(translate_logical_pathname));
2193 error(file_error,GETTEXT("~S: endless loop while resolving ~S"));
2194 }
2195 if (nullp(TheLogpathname(STACK_1)->pathname_host)) {
2196 /* replace host NIL with default-host: */
2197 var object newp = allocate_logpathname();
2198 var object oldp = STACK_1;
2199 TheLogpathname(newp)->pathname_host
2200 = O(default_logical_pathname_host); /* Default "SYS" */
2201 TheLogpathname(newp)->pathname_directory
2202 = TheLogpathname(oldp)->pathname_directory;
2203 TheLogpathname(newp)->pathname_name
2204 = TheLogpathname(oldp)->pathname_name;
2205 TheLogpathname(newp)->pathname_type
2206 = TheLogpathname(oldp)->pathname_type;
2207 TheLogpathname(newp)->pathname_version
2208 = TheLogpathname(oldp)->pathname_version;
2209 STACK_1 = newp;
2210 }
2211 var object host = TheLogpathname(STACK_1)->pathname_host;
2212 DOUT("translate-logical-pathname:",host);
2213 var object translations =
2214 gethash(host,Symbol_value(S(logpathname_translations)),false);
2215 if (eq(translations,nullobj)) {
2216 /* STACK_1 = pathname; -- FILE-ERROR slot PATHNAME */
2217 STACK_0 = STACK_1;
2218 pushSTACK(host);
2219 pushSTACK(S(translate_logical_pathname));
2220 error(file_error,GETTEXT("~S: unknown logical host ~S in ~S"));
2221 }
2222 /* (ASSOC pathname translations :test #'pathname-match-p): */
2223 pushSTACK(STACK_1); pushSTACK(translations);
2224 DOUT("translate-logical-pathname:[path_name_s1]",STACK_1);
2225 DOUT("translate-logical-pathname:",translations);
2226 pushSTACK(S(Ktest)); pushSTACK(L(pathname_match_p));
2227 funcall(L(assoc),4);
2228 if (atomp(value1) || matomp(Cdr(value1))) {
2229 /* STACK_1 = pathname; -- FILE-ERROR slot PATHNAME */
2230 STACK_0 = STACK_1;
2231 pushSTACK(S(translate_logical_pathname));
2232 error(file_error,GETTEXT("~S: No replacement rule for ~S is known."));
2233 }
2234 /* (TRANSLATE-PATHNAME pathname (first rule) (second rule) :MERGE NIL):*/
2235 pushSTACK(STACK_1); pushSTACK(Car(value1)); pushSTACK(Car(Cdr(value1)));
2236 pushSTACK(S(Kmerge)); pushSTACK(NIL);
2237 funcall(L(translate_pathname),5);
2238 STACK_1 = pathname = value1;
2239 DOUT("translate-logical-pathname:",pathname);
2240 if (!logpathnamep(pathname))
2241 break;
2242 }
2243 DOUT("translate-logical-pathname: >",pathname);
2244 skipSTACK(2);
2245 }
2246 if (absolute_p)
2247 pathname = use_default_dir(pathname); /* insert default-directory */
2248 VALUES1(pathname);
2249 }
2250
2251 /* UP: Change an object into a non-logical pathname.
2252 coerce_pathname(object)
2253 > object: object
2254 < return: (TRANSLATE-LOGICAL-PATHNAME (PATHNAME Objekt))
2255 can trigger GC */
coerce_pathname(object obj)2256 local maygc object coerce_pathname (object obj) {
2257 obj = coerce_xpathname(obj);
2258 if (pathnamep(obj)) {
2259 return obj;
2260 } else if (logpathnamep(obj)) {
2261 /* call TRANSLATE-LOGICAL-PATHNAME: */
2262 pushSTACK(obj); funcall(L(translate_logical_pathname),1);
2263 return value1;
2264 } else
2265 NOTREACHED;
2266 }
2267
2268 /* UP: Pushes substrings for STRING_CONCAT on the STACK, that together yield
2269 the string for a subdirectory (car path) .
2270 subdir_namestring_parts(path,logicalp)
2271 > path: a Cons
2272 > logicalp: boolean
2273 < result: number of strings pushed on the stack
2274 changes STACK */
2275
2276 #define SUBDIR_PUSHSTACK(subdir) \
2277 do { if (eq(subdir,S(Kwild_inferiors))) pushSTACK(O(wildwild_string)); \
2278 else if (eq(subdir,S(Kwild))) pushSTACK(O(wild_string)); \
2279 else if (eq(subdir,S(Kup)) || eq(subdir,S(Kback))) \
2280 pushSTACK(O(dotdot_string)); \
2281 else if (stringp(subdir)) pushSTACK(subdir); \
2282 else NOTREACHED; \
2283 } while(0)
2284
subdir_namestring_parts(object path,bool logicalp)2285 local uintC subdir_namestring_parts (object path,bool logicalp) {
2286 unused(logicalp);
2287 var object subdir = Car(path);
2288 #if defined(PATHNAME_UNIX) || defined(PATHNAME_WIN32)
2289 SUBDIR_PUSHSTACK(subdir); return 1;
2290 #endif
2291 }
2292
2293 /* UP: Pushes substrings for STRING_CONCAT on the STACK, that together yield
2294 the String for the host of the Pathname pathname.
2295 host_namestring_parts(pathname)
2296 > pathname: non-logical pathname
2297 < result: number of strings pushed on the stack
2298 changes STACK */
host_namestring_parts(object pathname)2299 local uintC host_namestring_parts (object pathname) {
2300 var bool logp = logpathnamep(pathname);
2301 var object host = xpathname_host(logp,pathname);
2302 if (nullp(host)) {
2303 return 0; /* no String */
2304 } else {
2305 #ifdef PATHNAME_WIN32
2306 if (!logp) {
2307 pushSTACK(O(backslashbackslash_string));
2308 pushSTACK(host);
2309 return 2;
2310 }
2311 #endif
2312 pushSTACK(host);
2313 pushSTACK(O(colon_string)); /* ":" */
2314 return 2;
2315 }
2316 }
2317
2318 /* UP: Pushes substrings for STRING_CONCAT on the STACK, that together
2319 yield the String for the Device and Directory of the Pathname pathname.
2320 directory_namestring_parts(pathname)
2321 > pathname: non-logical pathname
2322 < result: number of strings pushed on the stack
2323 changes STACK */
directory_namestring_parts(object pathname)2324 local uintC directory_namestring_parts (object pathname) {
2325 var uintC stringcount = 0; /* number of strings so far = 0 */
2326 var bool logp = logpathnamep(pathname);
2327 #if defined(PATHNAME_WIN32)
2328 { /* Device: */
2329 var object device = xpathname_device(logp,pathname);
2330 if (!(nullp(device))) { /* NIL -> no string */
2331 var object string = wild2string(device);
2332 pushSTACK(string);
2333 stringcount++; /* and count */
2334 pushSTACK(O(colon_string));
2335 stringcount++; /* ":" */
2336 }
2337 }
2338 #endif
2339 #if defined(PATHNAME_WIN32) || defined(PATHNAME_UNIX)
2340 if (stringcount == 0) /* only if there's no device already */
2341 /* no check for both host and device being present:
2342 this can never happen in CLISP */
2343 stringcount += host_namestring_parts(pathname);
2344 #endif
2345 { /* Directory: */
2346 var object directory = xpathname_directory(logp,pathname);
2347 if (logp) {
2348 if (consp(directory) && eq(Car(directory),S(Krelative))) {
2349 pushSTACK(O(semicolon_string)); stringcount++; /* ";" on the Stack */
2350 }
2351 } else {
2352 #if defined(PATHNAME_WIN32)
2353 #define push_slash pushSTACK(O(backslash_string))
2354 #elif defined(PATHNAME_UNIX)
2355 #define push_slash pushSTACK(O(slash_string))
2356 #else
2357 #error what is the directory separator on your platform?
2358 #endif
2359 if (!mconsp(directory)) return stringcount; /* no directory */
2360 /* is the first subdir = :ABSOLUTE or = :RELATIVE ? */
2361 if (eq(Car(directory),S(Kabsolute))) {
2362 push_slash; stringcount++; /* "/" */
2363 } else if (nullp(Cdr(directory))) { /* (:RELATIVE) ==> "./" */
2364 pushSTACK(O(dot_string)); stringcount++; /* "." */
2365 push_slash; stringcount++; /* "/" */
2366 return stringcount;
2367 }
2368 }
2369 directory = Cdr(directory); /* skip */
2370 /* other subdirs on the stack: */
2371 while (consp(directory)) {
2372 stringcount += subdir_namestring_parts(directory,logp);
2373 if (logp) {
2374 pushSTACK(O(semicolon_string)); stringcount++; /* ";" */
2375 } else {
2376 #ifdef PATHNAME_WIN32
2377 pushSTACK(O(backslash_string)); stringcount++; /* "\\" */
2378 #endif
2379 #ifdef PATHNAME_UNIX
2380 pushSTACK(O(slash_string)); stringcount++; /* "/" */
2381 #endif
2382 }
2383 directory = Cdr(directory);
2384 }
2385 }
2386 #undef push_slash
2387 return stringcount;
2388 }
2389
2390 /* UP: Pushes substrings for STRING_CONCAT on the STACK, that together yield
2391 the string for Name and Type of the pathname.
2392 nametype_namestring_parts(name,type,version)
2393 > name, type, poss. version: components of the pathname
2394 < result: number of the strings pushed on the stack
2395 can trigger GC
2396 changes STACK */
nametype_namestring_parts(object name,object type,object version)2397 local maygc uintC nametype_namestring_parts (object name, object type, object version)
2398 {
2399 var uintC stringcount = 0;
2400 /* Name: */
2401 if (!nullp(name)) { /* name=NIL -> do not print */
2402 var object string = wild2string(name);
2403 pushSTACK(string);
2404 stringcount++; /* and count */
2405 }
2406 /* Type: */
2407 if (!nullp(type)) { /* type=NIL -> do not print */
2408 pushSTACK(O(dot_string)); /* "." */
2409 stringcount++; /* and count */
2410 var object string = wild2string(type);
2411 pushSTACK(string);
2412 stringcount++; /* and count */
2413 }
2414 if (!nullp(version)) { /* version=NIL -> do not print */
2415 pushSTACK(O(dot_string)); /* "." */
2416 stringcount++; /* and count */
2417 if (eq(version,S(Knewest)))
2418 /* http://www.ai.mit.edu/projects/iiip/doc/CommonLISP/HyperSpec/Body/sec_19-3-1.html */
2419 pushSTACK(Symbol_name(S(Knewest))); /* :NEWEST -> "NEWEST" */
2420 else if (eq(version,S(Kwild)))
2421 pushSTACK(O(wild_string));
2422 else
2423 /* version (integer >0) ==> string: (sys::decimal-string version) */
2424 pushSTACK(decimal_string(version));
2425 stringcount++; /* and count */
2426 }
2427 return stringcount;
2428 }
2429
2430 /* UP: Pushes substrings for STRING_CONCAT on the STACK, that together yield
2431 the string for name and type of the pathname.
2432 file_namestring_parts(pathname)
2433 > pathname: non-logical pathname
2434 < result: number of the strings pushed on the stack
2435 can trigger GC
2436 changes STACK */
file_namestring_parts(object pathname)2437 local maygc uintC file_namestring_parts (object pathname) {
2438 if (logpathnamep(pathname))
2439 return nametype_namestring_parts
2440 (TheLogpathname(pathname)->pathname_name,
2441 TheLogpathname(pathname)->pathname_type,
2442 TheLogpathname(pathname)->pathname_version);
2443 else
2444 /* do not print version when the underlying physical file system
2445 does not support it */
2446 return nametype_namestring_parts(ThePathname(pathname)->pathname_name,
2447 ThePathname(pathname)->pathname_type,
2448 pathname_version_maybe(pathname));
2449 }
2450
2451 /* UP: Converts pathname into string.
2452 whole_namestring(pathname)
2453 > pathname: non-logical pathname
2454 < result: Normal-Simple-String
2455 can trigger GC */
whole_namestring(object pathname)2456 local maygc object whole_namestring (object pathname) {
2457 var uintC stringcount = 0;
2458 stringcount += directory_namestring_parts(pathname);
2459 stringcount += file_namestring_parts(pathname);
2460 return string_concat(stringcount);
2461 }
2462
2463 /* UP: Returns the string for the directory of a pathname.
2464 directory_namestring(pathname)
2465 > pathname: non-logical pathname
2466 < result: Normal-Simple-String
2467 can trigger GC */
directory_namestring(object pathname)2468 local maygc object directory_namestring (object pathname) {
2469 /* The function DIRECTORY-NAMESTRING is totally underspecified.
2470 It could return
2471 a. just the string for the directory portion,
2472 b. the string for the device + directory portions,
2473 c. the string for the host + device + directory portions.
2474 Before we used hosts, we have traditionally returned (b).
2475 Now, with hosts, we still return (b) since HOST-NAMESTRING returns
2476 the host part, while there is no way to return just the device
2477 This makes most sense, given that CLHS says that programs
2478 should not attempt to concatenate the resulting string with anything. */
2479 return string_concat(directory_namestring_parts(pathname));
2480 }
2481
2482 /* UP: Returns the string identifying a file in its directory.
2483 file_namestring(pathname)
2484 > pathname: non-logical pathname
2485 < result: normal-simple-string
2486 can trigger GC */
file_namestring(object pathname)2487 local maygc inline object file_namestring (object pathname) {
2488 return string_concat(file_namestring_parts(pathname));
2489 }
2490
2491 LISPFUNNR(file_namestring,1)
2492 { /* (FILE-NAMESTRING pathname), CLTL p. 417 */
2493 VALUES1(file_namestring(coerce_xpathname(popSTACK())));
2494 }
2495
2496 LISPFUNNR(directory_namestring,1)
2497 { /* (DIRECTORY-NAMESTRING pathname), CLTL p. 417 */
2498 VALUES1(directory_namestring(coerce_xpathname(popSTACK())));
2499 }
2500
2501 LISPFUNNR(host_namestring,1)
2502 { /* (HOST-NAMESTRING pathname), CLTL p. 417 */
2503 var object pathname = coerce_xpathname(popSTACK());
2504 VALUES1(xpathname_host(logpathnamep(pathname),pathname));
2505 }
2506
2507 /* UP: check an optional VERSION argument.
2508 test_optional_version(def);
2509 > STACK_0: VERSION-Argument
2510 > def: default value for it
2511 < result: valid version-component */
test_optional_version(object def)2512 local object test_optional_version (object def) {
2513 var object version = STACK_0;
2514 if (!boundp(version)) {
2515 STACK_0 = def; /* not specified -> Default */
2516 } else if (nullp(version)) { /* NIL is OK */
2517 } else if (eq(version,S(Kwild))) { /* :WILD is OK */
2518 } else if (eq(version,S(Knewest))) { /* :NEWEST is OK */
2519 } else if (posfixnump(version) && !eq(version,Fixnum_0)) {/*Fixnum>0 is OK*/
2520 } else if (pathnamep(version)) { /* Pathname -> its Version */
2521 STACK_0 = ThePathname(version)->pathname_version;
2522 } else if (logpathnamep(version)) { /* Logical Pathname -> its Version */
2523 STACK_0 = TheLogpathname(version)->pathname_version;
2524 } else { /* None of the desired cases -> error: */
2525 pushSTACK(version); /* TYPE-ERROR slot DATUM */
2526 pushSTACK(O(type_version)); /* TYPE-ERROR slot EXPECTED-TYPE */
2527 pushSTACK(version);
2528 pushSTACK(TheSubr(subr_self)->name);
2529 error(type_error,GETTEXT("~S: :VERSION-argument should be NIL or a positive fixnum or :WILD or :NEWEST, not ~S"));
2530 }
2531 return STACK_0;
2532 }
2533
2534 #ifdef PATHNAME_WIN32
2535
2536 /* the operating system manages a default-drive.
2537 the operating system manages a default-directory on each drive. This
2538 can change, if another floppy disk is inserted. */
2539
2540 /* a default-drive is kept: DEFAULT_DRIVE = O(default_drive). */
2541
2542 /* the variable *DEFAULT-PATHNAME-DEFAULTS* contains (as pathname) the
2543 default value for each MERGE-operation. It is the one, which the system
2544 "interpretes into" the pathnames entered by the user.
2545 It is kept up to date with the DEFAULT_DRIVE: On
2546 initialization the current device (in terms of DOS), on
2547 change of DEFAULT_DRIVE via CD. */
2548
2549 #endif /* PATHNAME_WIN32 */
2550
2551 #ifdef PATHNAME_UNIX
2552
2553 /* The variable *DEFAULT-PATHNAME-DEFAULTS* contains (as pathname) the
2554 default value for each MERGE-operation. It is the one, which the system
2555 "interpretes into" the pathnames entered by the user. */
2556
2557 #endif
2558
2559 #ifdef UNIX
2560
2561 /* the operating system manages a default-directory ("working directory")
2562 for this process. It can be changed with chdir and queried with getwd.
2563 See CHDIR(2) and GETWD(3). */
2564
2565 #endif
2566
2567 /* UP: Re-calculation of *DEFAULT-PATHNAME-DEFAULTS* */
2568 #ifdef PATHNAME_WIN32
2569 /* from DEFAULT_DRIVE */
2570 #endif
2571 /* recalc_defaults_pathname();
2572 < result: value of *DEFAULT-PATHNAME-DEFAULTS*, a pathname
2573 can trigger GC */
recalc_defaults_pathname(void)2574 local maygc object recalc_defaults_pathname (void) {
2575 #ifdef PATHNAME_WIN32
2576 /* execute (MAKE-PATHNAME :DEVICE default-drive) : */
2577 pushSTACK(S(Kdevice)); pushSTACK(O(default_drive));
2578 funcall(L(make_pathname),2);
2579 #endif
2580 #ifdef PATHNAME_UNIX
2581 /* execute (MAKE-PATHNAME) : */
2582 funcall(L(make_pathname),0);
2583 #endif
2584 /* and assign *DEFAULT-PATHNAME-DEFAULTS* : */
2585 return Symbol_value(S(default_pathname_defaults)) = value1;
2586 }
2587
2588 /* UP: Returns the default-pathname.
2589 defaults_pathname()
2590 < result: value of *DEFAULT-PATHNAME-DEFAULTS*, a pathname
2591 can trigger GC */
defaults_pathname(void)2592 local maygc object defaults_pathname (void) {
2593 var object pathname = Symbol_value(S(default_pathname_defaults)); /* value of *DEFAULT-PATHNAME-DEFAULTS* */
2594 if (xpathnamep(pathname)) { /* is a pathname -> OK */
2595 return pathname;
2596 } else { /* else warning: */
2597 pushSTACK(CLSTEXT("The value of ~S was not a pathname. ~:*~S is being reset."));
2598 pushSTACK(S(default_pathname_defaults));
2599 funcall(S(warn),2);
2600 /* and re-calculate: */
2601 return recalc_defaults_pathname();
2602 }
2603 }
2604
2605 /* merge two directories
2606 > p_directory: pathname directory list
2607 > d_directory: defaults directory list
2608 > p_log: flag, whether pathname is logical
2609 > wildp: flag, from MERGE-PATHNAMES
2610 > called_from_make_pathname: flag, from MERGE-PATHNAMES
2611 < result: merges directory list
2612 can trigger GC */
merge_dirs(object p_directory,object d_directory,bool p_log,bool wildp,bool called_from_make_pathname)2613 local maygc object merge_dirs (object p_directory, object d_directory, bool p_log,
2614 bool wildp, bool called_from_make_pathname) {
2615 var object new_subdirs = p_directory;
2616 #if DEBUG_TRANSLATE_PATHNAME
2617 printf("[%d] merge_dirs: log: %d; wild: %d; cfmp: %d\n",
2618 __LINE__,p_log,wildp,called_from_make_pathname);
2619 #endif
2620 SDOUT("merge_dirs:",p_directory);
2621 SDOUT("merge_dirs:",d_directory);
2622 if (called_from_make_pathname) {
2623 if (!boundp(p_directory)) /* pathname-subdirs not given? */
2624 new_subdirs = d_directory; /* use defaults-subdirs */
2625 } else if (!wildp) {
2626 if (nullp(p_directory) /* is pathname-subdirs trivial? */
2627 || (eq(Car(p_directory),p_log ? S(Kabsolute) : S(Krelative))
2628 && matomp(Cdr(p_directory)))) {
2629 new_subdirs = d_directory; /* use defaults-subdirs */
2630 } else if (eq(Car(p_directory),S(Krelative))
2631 /* PATHNAME = :ABSOLUTE ==> merge is not needed */
2632 && consp(d_directory) /* DEFAULT = NIL ==> nothing to merge */
2633 && (eq(Car(d_directory),S(Kabsolute))
2634 || !nullpSv(merge_pathnames_ansi))) {
2635 /* (append defaults-subdirs (cdr pathname-subdirs)) =
2636 (nreconc (reverse defaults-subdirs) (cdr pathname-subdirs)) : */
2637 pushSTACK(Cdr(p_directory));
2638 var object temp = reverse(d_directory);
2639 new_subdirs = simplify_directory(nreconc(temp,popSTACK()));
2640 }
2641 }
2642 return new_subdirs;
2643 }
2644
2645 /* (MERGE-PATHNAMES pathname [defaults [default-version]] [:wild]), CLTL p. 415
2646 Definition assuming that HAS_HOST and HAS_DEVICE are exclusive:
2647 (defun merge-pathnames (pathname &optional (defaults *default-pathname-defaults*) default-version)
2648 (setq pathname (pathname pathname))
2649 (setq defaults (pathname defaults))
2650 (multiple-value-call #'make-pathname
2651 #if HAS_HOST
2652 (if (or (equal (pathname-host pathname) (pathname-host defaults))
2653 (null (pathname-host pathname)))
2654 (values
2655 :host (or (pathname-host pathname) (pathname-host defaults))
2656 #endif
2657 #if HAS_DEVICE
2658 (if (or (equal (pathname-device pathname) (pathname-device defaults))
2659 (null (pathname-device pathname)))
2660 (values
2661 :device (or (pathname-device pathname) (pathname-device defaults))
2662 #endif
2663 :directory
2664 (let ((pathname-dir (pathname-directory pathname))
2665 (defaults-dir (pathname-directory defaults)))
2666 (if (eq (car pathname-dir) ':RELATIVE)
2667 (cond ((null (cdr pathname-dir)) defaults-dir)
2668 ((or *merge-pathnames-ansi*
2669 (not (eq (car defaults-dir) ':RELATIVE))) ; <----
2670 (append defaults-dir (cdr pathname-dir)))
2671 (t pathname-dir))
2672 pathname-dir)))
2673 (values
2674 #if HAS_HOST
2675 :host (pathname-host pathname)
2676 #endif
2677 #if HAS_DEVICE
2678 :device (pathname-device pathname)
2679 #endif
2680 :directory (pathname-directory pathname)))
2681 :name (or (pathname-name pathname) (pathname-name defaults))
2682 :type (or (pathname-type pathname) (pathname-type defaults))))
2683
2684 If HAS_HOST and HAS_DEVICE are both true, the semantics are more
2685 complicated; see CLHS for details.
2686
2687 If the :WILD argument is specified, :WILD components are replaced,
2688 instead of missing components.
2689
2690 Explanation of the "<----" line:
2691
2692 Roger Kehr <kehr@iti.informatik.th-darmstadt.de> asks why in CLISP
2693
2694 (merge-pathnames (make-pathname :directory '(:relative "x"))
2695 (make-pathname :directory '(:relative "y")))
2696 => #"x/"
2697
2698 where he expects to get #"y/x/".
2699
2700 Bruno: There are two reasons for this behaviour:
2701
2702 1. An informal one: I found the latter behaviour confusing and changed
2703 CLISP to do it the former way. It seems to work better this way.
2704
2705 2. A formal one: MERGE-PATHNAMES is used to specify default components
2706 for pathnames, so there is some analogy between (MERGE-PATHNAMES a b)
2707 and (or a b). Obviously putting in the same default a second time
2708 should do the same as putting it in once:
2709
2710 (or a b b) is the same as (or a b), so
2711
2712 (MERGE-PATHNAMES (MERGE-PATHNAMES a b) b) should be the same as
2713 (MERGE-PATHNAMES a b).
2714
2715 (This question actually matters because in Common Lisp there is
2716 no distinction between "pathnames with defaults merged-in" and
2717 "pathnames with defaults not yet applied". For example, you do not
2718 know whether COMPILE-FILE will merge in some defaults.)
2719
2720 Now, (MERGE-PATHNAMES (MERGE-PATHNAMES '#"x/" '#"y/") '#"y/")
2721 and (MERGE-PATHNAMES '#"x/" '#"y/")
2722 are equal in CLISP's implementation, but not in implementations
2723 that strictly follow the Common Lisp spec. In fact, the above
2724 twice-default = once-default rule holds for all pathnames in CLISP. */
2725 LISPFUN(merge_pathnames,seclass_read,1,2,norest,key,1, (kw(wild))) {
2726 /* :wild #'make-pathname causes NIL components to be considered specified,
2727 only #<unbound> components are considered unspecified. */
2728 var bool called_from_make_pathname = eq(STACK_0,L(make_pathname));
2729 /* :wild t causes only wild components to be considered unspecified. */
2730 var bool wildp = !missingp(STACK_0);
2731 skipSTACK(1);
2732
2733 #define SPECIFIED(obj) \
2734 !(called_from_make_pathname ? !boundp(obj) : \
2735 (wildp ? eq(obj,S(Kwild)) : nullp(obj)))
2736 #define NAMETYPE_MATCH(acc,slot) \
2737 { var object tmp = x##slot(p_log,p); \
2738 acc(newp)->slot = (SPECIFIED(tmp) ? tmp : (object)x##slot(d_log,d)); \
2739 }
2740
2741 /* check pathname (STACK_2) and defaults (STACK_1):
2742 (coerce defaults 'pathname): */
2743 STACK_1 = test_default_pathname(STACK_1);
2744 /* (coerce pathname 'pathname): */
2745 if (logpathnamep(STACK_1)) {
2746 if (!xpathnamep(STACK_2)) { /* pathname */
2747 STACK_2 = parse_as_logical(STACK_2);
2748 DOUT("merge-pathnames:[log_pathname]",STACK_2);
2749 }
2750 } else
2751 STACK_2 = coerce_xpathname(STACK_2); /* pathname */
2752 var bool d_log = logpathnamep(STACK_1);
2753 var bool p_log = logpathnamep(STACK_2);
2754
2755 { /* check default-version (STACK_0): */
2756 var object v = test_optional_version(unbound);
2757 var object p_version = xpathname_version(p_log,STACK_2);
2758 var object d_version = xpathname_version(d_log,STACK_1);
2759 var object p_name = xpathname_name(p_log,STACK_2);
2760 if (SPECIFIED(p_version))
2761 v = p_version;
2762 if (missingp(v) && !SPECIFIED(p_name) && SPECIFIED(d_version))
2763 v = d_version;
2764 if (!boundp(v)) v = S(Knewest);
2765 STACK_0 = STACK_1; STACK_1 = STACK_2; STACK_2 = v;
2766 DOUT("merge-pathnames:",v);
2767 }
2768 /* stack layout: default-version, pathname, defaults. */
2769
2770 /* do the merge */
2771 DOUT("merge-pathnames:[defaults]",STACK_0);
2772 DOUT("merge-pathnames:[pathname]",STACK_1);
2773 if (d_log || p_log) {
2774 /* MERGE-PATHNAMES for logical pathnames */
2775 var object newp = allocate_logpathname(); /* fetch new pathname */
2776 var object d = popSTACK(); /* defaults */
2777 var object p = popSTACK(); /* pathname */
2778 { /* match hosts: */
2779 var object p_host = xpathname_host(p_log,p);
2780 var object d_host = xpathname_host(d_log,d);
2781 TheLogpathname(newp)->pathname_host = p_host; /* initially, new-host := pathname-host */
2782 if (equal(p_host,d_host))
2783 goto lmatch_directories;
2784 if (wildp ? !boundp(p_host) : nullp(p_host)) {
2785 /* pathname-host not specified, but defaults-host specified: */
2786 TheLogpathname(newp)->pathname_host = d_host; /* new-host := defaults-host */
2787 goto lmatch_directories;
2788 }
2789 }
2790 { /* directories do not match: new-directory := pathname-directory */
2791 var object dir = xpathname_directory(p_log,p);
2792 TheLogpathname(newp)->pathname_directory =
2793 (!SPECIFIED(dir) ? xpathname_directory(d_log,d) : dir);
2794 goto ldirectories_OK;
2795 }
2796 lmatch_directories:
2797 { /* match directories: */
2798 pushSTACK(p); pushSTACK(d); pushSTACK(newp);
2799 TheLogpathname(STACK_0)->pathname_directory =
2800 merge_dirs(xpathname_directory(p_log,p),
2801 xpathname_directory(d_log,d),
2802 p_log,wildp,called_from_make_pathname);
2803 newp = popSTACK(); d = popSTACK(); p = popSTACK();
2804 }
2805 ldirectories_OK:
2806 /* the directories are OK now */
2807 NAMETYPE_MATCH(TheLogpathname,pathname_name);
2808 NAMETYPE_MATCH(TheLogpathname,pathname_type);
2809 TheLogpathname(newp)->pathname_version = popSTACK();
2810 DOUT("merge-pathnames:[ret]",newp);
2811 VALUES1(newp);
2812 return;
2813 }
2814 /* not both are logical pathnames -> first, convert into normal pathnames: */
2815 STACK_1 = coerce_pathname(STACK_1);
2816 STACK_0 = coerce_pathname(STACK_0);
2817 var object newp = allocate_pathname(); /* fetch new pathname */
2818 var object d = popSTACK(); /* defaults */
2819 var object p = popSTACK(); /* pathname */
2820 #if HAS_HOST
2821 { /* match hosts: */
2822 var object p_host = ThePathname(p)->pathname_host;
2823 var object d_host = ThePathname(d)->pathname_host;
2824 ThePathname(newp)->pathname_host = p_host; /* initially, new-host := pathname-host */
2825 /* both hosts equal -> match devices: */
2826 if (equal(p_host,d_host))
2827 goto match_devices;
2828 if (!(wildp ? false : nullp(p_host)))
2829 goto notmatch_devices;
2830 #ifdef PATHNAME_WIN32
2831 var object p_device = ThePathname(p)->pathname_device;
2832 /* On Win32, a non-null p_device implicitly designates p_host as the
2833 local machine. It must not be overridden by d_host. */
2834 if (SPECIFIED(p_device))
2835 goto notmatch_devices;
2836 #endif
2837 /* pathname-host not specified, but defaults-host specified: */
2838 ThePathname(newp)->pathname_host = d_host; /* new-host := defaults-host */
2839 goto match_devices;
2840 }
2841 #endif /* HAS_HOST */
2842 match_devices:
2843 #if HAS_DEVICE
2844 { /* match devices: */
2845 var object p_device = ThePathname(p)->pathname_device;
2846 var object d_device = ThePathname(d)->pathname_device;
2847 ThePathname(newp)->pathname_device = p_device; /* initially, new-device := pathname-device */
2848 /* both devices equal -> match directories: */
2849 if (equal(p_device,d_device))
2850 goto match_directories;
2851 if (!SPECIFIED(p_device)) {
2852 /* pathname-device not given, but defaults-device is given: */
2853 ThePathname(newp)->pathname_device = d_device; /* new-device := defaults-device */
2854 goto match_directories;
2855 }
2856 goto notmatch_directories;
2857 }
2858 #endif /* HAS_DEVICE */
2859 match_directories: { /* match directories: */
2860 var object tmp;
2861 pushSTACK(p); pushSTACK(d); pushSTACK(newp);
2862 tmp = merge_dirs(ThePathname(p)->pathname_directory,
2863 ThePathname(d)->pathname_directory,
2864 false,wildp,called_from_make_pathname);
2865 newp = popSTACK(); d = popSTACK(); p = popSTACK();
2866 ThePathname(newp)->pathname_directory = tmp;
2867 }
2868 goto directories_OK;
2869 /* do not match devices: */
2870 notmatch_devices:
2871 #if HAS_DEVICE
2872 { /* new-device := pathname-device : */
2873 ThePathname(newp)->pathname_device = ThePathname(p)->pathname_device;
2874 }
2875 #endif
2876 notmatch_directories:
2877 { /* directories do not match: new-directory := pathname-directory */
2878 var object dir = xpathname_directory(p_log,p);
2879 ThePathname(newp)->pathname_directory =
2880 (missingp(dir) ? xpathname_directory(d_log,d) : dir);
2881 }
2882 directories_OK:
2883 /* the directories are OK now */
2884 NAMETYPE_MATCH(ThePathname,pathname_name);
2885 NAMETYPE_MATCH(ThePathname,pathname_type);
2886 ThePathname(newp)->pathname_version = popSTACK();
2887 DOUT("merge-pathnames:[ret]",newp);
2888 VALUES1(newp);
2889 }
2890 #undef SPECIFIED
2891 #undef NAMETYPE_MATCH
2892
2893 /* (ENOUGH-NAMESTRING pathname [defaults]), CLTL p. 417
2894 Definition assuming that HAS_HOST and HAS_DEVICE are exclusive:
2895 (defun enough-namestring (pathname &optional (defaults *default-pathname-defaults*))
2896 (setq pathname (pathname pathname))
2897 (setq defaults (pathname defaults))
2898 (namestring
2899 (multiple-value-call #'make-pathname
2900 #if HAS_HOST
2901 (if (equal (pathname-host pathname) (pathname-host defaults))
2902 (values
2903 :host nil
2904 #endif
2905 #if HAS_DEVICE
2906 (if (equal (pathname-device pathname) (pathname-device defaults))
2907 (values
2908 :device nil
2909 #endif
2910 :directory
2911 (let ((pathname-dir (pathname-directory pathname))
2912 (defaults-dir (pathname-directory defaults)))
2913 (if (equal pathname-dir defaults-dir)
2914 (list ':RELATIVE)
2915 (if (and (not (eq (car pathname-dir) ':RELATIVE))
2916 (not (eq (car defaults-dir) ':RELATIVE))
2917 (equal (subseq pathname-dir 0 (min (length pathname-dir) (length defaults-dir)))
2918 defaults-dir
2919 ) )
2920 (cons ':RELATIVE (nthcdr (length defaults-dir) pathname-dir))
2921 pathname-dir
2922 ) ) )
2923 )
2924 (values
2925 #if HAS_HOST
2926 :host (pathname-host pathname)
2927 #endif
2928 #if HAS_DEVICE
2929 :device (pathname-device pathname)
2930 #endif
2931 :directory (pathname-directory pathname)))
2932 :name (if (equal (pathname-name pathname) (pathname-name defaults))
2933 nil
2934 (pathname-name pathname))
2935 :type (if (equal (pathname-type pathname) (pathname-type defaults))
2936 nil
2937 (pathname-type pathname)))))
2938
2939 If HAS_HOST and HAS_DEVICE are both true, the semantics are more
2940 complicated; see CLHS for details. */
2941 #define SET_NEWP(slot,value) \
2942 if (log2) TheLogpathname(newp)->slot = value; \
2943 else ThePathname(newp)->slot = value;
2944 LISPFUN(enough_namestring,seclass_read,1,1,norest,nokey,0,NIL) {
2945 /* check pathname and defaults:
2946 turn pathname into a Pathname: */
2947 STACK_1 = coerce_xpathname(STACK_1);
2948 var bool log2 = logpathnamep(STACK_1);
2949 /* turn defaults into a Pathname: */
2950 STACK_0 = test_default_pathname(STACK_0);
2951 var bool log1 = logpathnamep(STACK_0);
2952 /* fetch new Pathname: */
2953 var object newp = (log2 ? allocate_logpathname() : allocate_pathname());
2954 pushSTACK(newp);
2955 /* stack layout: pathname, defaults, new. */
2956 #if HAS_HOST
2957 { /* compare hosts: */
2958 var object p_host = xpathname_host(log2,STACK_2); /* pathname-host */
2959 var object d_host = xpathname_host(log1,STACK_1); /* defaults-host */
2960 if (equal(p_host,d_host)) { /* both hosts equal? */
2961 SET_NEWP(pathname_host,NIL); /* new-host := NIL */
2962 #endif
2963 #if HAS_DEVICE
2964 { /* compare devices: */
2965 var object p_device = xpathname_device(log2,STACK_2);
2966 var object d_device = xpathname_device(log1,STACK_1);
2967 if (equal(p_device,d_device)) { /* both devices equal? */
2968 if (!log2) ThePathname(newp)->pathname_device = NIL;
2969 #endif
2970 {
2971 var object p_directory = xpathname_directory(log2,STACK_2);
2972 var object d_directory = xpathname_directory(log1,STACK_1);
2973 var object new_subdirs;
2974 /* compare pathname-subdirs and defaults-subdirs: */
2975 if (equal(p_directory,d_directory)) { /* ==> use NIL : */
2976 new_subdirs = NIL;
2977 } else {
2978 /* Does neither pathname-subdirs nor defaults-subdirs
2979 start with :RELATIVE ? */
2980 if ( consp(p_directory) && (eq(Car(p_directory),S(Kabsolute)))
2981 && consp(d_directory) && (eq(Car(d_directory),S(Kabsolute)))) {
2982 /* yes -> test, if defaults-subdirs is a starting piece
2983 of the list pathname-subdirs: */
2984 var object Lp = p_directory;
2985 var object Ld = d_directory;
2986 /* Is Ld a starting piece of Lp ? */
2987 while (1) {
2988 if (atomp(Ld)) { /* Ld finished -> yes */
2989 new_subdirs = Lp;
2990 /* new-subdirs := (cons :RELATIVE new-subdirs) : */
2991 pushSTACK(new_subdirs);
2992 new_subdirs = allocate_cons();
2993 Cdr(new_subdirs) = popSTACK();
2994 Car(new_subdirs) = S(Krelative);
2995 goto subdirs_ok;
2996 }
2997 if (atomp(Lp))
2998 break; /* Lp finished -> no */
2999 if (!equal(Car(Ld),Car(Lp))) /* different list-elements? */
3000 break; /* -> no */
3001 Ld = Cdr(Ld); Lp = Cdr(Lp); /* advance lists */
3002 }
3003 }
3004 new_subdirs = p_directory; /* new-subdirs := pathname-subdirs */
3005 }
3006 subdirs_ok: /* new-subdirs is the new subdir-list. */
3007 /* new-directory := new-subdirs : */
3008 newp = STACK_0;
3009 SET_NEWP(pathname_directory,new_subdirs);
3010 }
3011 #if HAS_DEVICE
3012 } else {
3013 /* different devices
3014 (Note for PATHNAME_WIN32: If we have different devices, the common
3015 host must have been NIL.)
3016 new-device := pathname-device
3017 new-directory := pathname-directory */
3018 if (log2) {
3019 TheLogpathname(newp)->pathname_directory =
3020 TheLogpathname(STACK_2)->pathname_directory;
3021 } else {
3022 ThePathname(newp)->pathname_device = p_device;
3023 ThePathname(newp)->pathname_directory =
3024 ThePathname(STACK_2)->pathname_directory;
3025 }
3026 }
3027 }
3028 #endif
3029 #if HAS_HOST
3030 } else { /* different hosts */
3031 /* new-host := pathname-host
3032 new-device := pathname-device
3033 new-directory := pathname-directory */
3034 if (log2) {
3035 TheLogpathname(newp)->pathname_host = p_host;
3036 TheLogpathname(newp)->pathname_directory =
3037 TheLogpathname(STACK_2)->pathname_directory;
3038 } else {
3039 ThePathname(newp)->pathname_host = p_host;
3040 #if HAS_DEVICE
3041 ThePathname(newp)->pathname_device =
3042 ThePathname(STACK_2)->pathname_device;
3043 #endif
3044 ThePathname(newp)->pathname_directory =
3045 ThePathname(STACK_2)->pathname_directory;
3046 }
3047 }
3048 }
3049 #endif
3050 { /* fill in name: */
3051 var object p_name = xpathname_name(log2,STACK_2); /* pathname-name */
3052 var object d_name = xpathname_name(log1,STACK_1); /* defaults-name */
3053 var object r_name = (equal(p_name,d_name) ? NIL : p_name);
3054 SET_NEWP(pathname_name,r_name);
3055 }
3056 { /* fill in type: */
3057 var object p_type = xpathname_type(log2,STACK_2); /* pathname-type */
3058 var object d_type = xpathname_type(log1,STACK_1); /* defaults-type */
3059 var object r_type = (equal(p_type,d_type) ? NIL : p_type);
3060 SET_NEWP(pathname_type,r_type);
3061 }
3062 skipSTACK(3);
3063 /* build (namestring new) : */
3064 with_saved_back_trace_subr(L(namestring),STACK STACKop -1,-1,
3065 VALUES1(whole_namestring(newp)); );
3066 }
3067 #undef SET_NEWP
3068
3069 /* UP: checks, if object is an admissible name:
3070 :WILD or a Simple-String made of valid characters, without adjacent '*'.
3071 legal_logical_word(object)
3072 > object: if a simple-string, a normal-simple-string */
legal_logical_word(object obj)3073 local bool legal_logical_word (object obj) {
3074 if (eq(obj,S(Kwild)))
3075 return true;
3076 if (!simple_string_p(obj))
3077 return false;
3078 ASSERT(sstring_normal_p(obj));
3079 var uintL len = Sstring_length(obj);
3080 if (len==0)
3081 return false; /* empty word is forbidden */
3082 SstringDispatch(obj,X, {
3083 var const cintX* charptr = &((SstringX)TheVarobject(obj))->data[0];
3084 var bool last_was_star = false;
3085 dotimespL(len,len, {
3086 var chart cc = as_chart(*charptr++);
3087 if (!(legal_logical_word_char(cc) || multiwild_char_p(cc)))
3088 return false;
3089 if (multiwild_char_p(cc)) {
3090 if (last_was_star)
3091 return false; /* adjacent '*' are forbidden */
3092 last_was_star = true;
3093 } else {
3094 last_was_star = false;
3095 }
3096 });
3097 });
3098 return true;
3099 }
3100
3101 #ifdef PATHNAME_NOEXT
3102
3103 /* UP: checks, if object is an admissible name:
3104 a Simple-String made of valid characters
3105 legal_name(object)
3106 > object: any object */
3107 #define legal_name(obj) check_name(obj,NULL)
3108 /* also, return the _BASE ONE_ index of the first dot in the string */
check_name(object obj,uintL * dot_pos_)3109 local bool check_name (object obj, uintL *dot_pos_) {
3110 if (dot_pos_) *dot_pos_ = 0;
3111 if (!stringp(obj)) return false;
3112 var uintL len, offset;
3113 obj = unpack_string_ro(obj,&len,&offset);
3114 if (len > 0) {
3115 SstringDispatch(obj,X, {
3116 var const cintX* start = ((SstringX)TheVarobject(obj))->data + offset;
3117 var const cintX* charptr = start;
3118 do { var chart cc = as_chart(*charptr++);
3119 if (!legal_namechar(cc)) return false;
3120 if (dot_pos_ && *dot_pos_==0 && dotp(cc))
3121 *dot_pos_ = charptr - start;
3122 } while(--len);
3123 });
3124 }
3125 return true;
3126 }
3127
3128
3129 /* UP: checks, if object is an admissible name:
3130 a Simple-String made of valid characters, without '.'
3131 legal_type(object)
3132 > object: if a simple-string, a normal-simple-string */
3133 local bool legal_type (object obj);
3134 #ifdef PATHNAME_NOEXT
legal_type(object obj)3135 local bool legal_type (object obj) {
3136 if (!simple_string_p(obj))
3137 return false;
3138 ASSERT(sstring_normal_p(obj));
3139 var uintL len = Sstring_length(obj);
3140 if (len > 0) {
3141 SstringDispatch(obj,X, {
3142 var const cintX* charptr = &((SstringX)TheVarobject(obj))->data[0];
3143 dotimespL(len,len, {
3144 var chart cc = as_chart(*charptr++);
3145 if (dotp(cc) || !legal_namechar(cc))
3146 return false;
3147 });
3148 });
3149 }
3150 return true;
3151 }
3152
3153 /* Check that the namestring for path will be parsed into a similar object
3154 used by pr_orecord() in io.d
3155 can trigger GC */
namestring_correctly_parseable_p(gcv_object_t * path_)3156 global maygc bool namestring_correctly_parseable_p (gcv_object_t *path_)
3157 {
3158 /* #p".foo" can be either :name ".foo" or :type "foo" */
3159 var object name = ThePathname(*path_)->pathname_name;
3160 var object type = ThePathname(*path_)->pathname_type;
3161 var uintL dot_position;
3162 check_name(name,&dot_position); /* we know it's valid! */
3163 if (eq(Symbol_value(S(parse_namestring_dot_file)),S(Ktype))) {
3164 parse_namestring_dot_file_type: /* ".foo" ==> :type "foo" */
3165 if (nullp(type) && dot_position>0) return false; /* name has '.' => bad */
3166 } else if (eq(Symbol_value(S(parse_namestring_dot_file)),S(Kname))) {
3167 /* ".foo" ==> :name ".foo" */
3168 if (nullp(name) && !nullp(type)) return false;
3169 /* has dots _inside_ the name, and type=nil */
3170 if (nullp(type) && dot_position>1) return false;
3171 } else {
3172 fix_parse_namestring_dot_file(); /* set to :TYPE */
3173 name = ThePathname(*path_)->pathname_name; /* restore after posible GC */
3174 type = ThePathname(*path_)->pathname_type;
3175 goto parse_namestring_dot_file_type;
3176 }
3177 /* name cannot be "": it is replaced with NIL by MAKE-PATHNAME; */
3178 #if HAS_VERSION
3179 /* when the underlying physical file system DOES support version,
3180 we are confident - for no good reason so far! -
3181 that we will be able to print the pathname properly */
3182 return true;
3183 #else
3184 /* when the underlying physical file system does NOT support version,
3185 pathname version is not printed, so cannot be read back! */
3186 return nullp(ThePathname(*path_)->pathname_version);
3187 #endif
3188 }
3189 #endif
3190
3191 #endif /* PATHNAME_NOEXT */
3192
3193 local object copy_pathname (object pathname);
3194
3195 /* check whether the list is a valid directory list */
directory_list_valid_p(bool logical,object dirlist)3196 local bool directory_list_valid_p (bool logical, object dirlist) {
3197 { /* CAR must be either :RELATIVE or :ABSOLUTE ? */
3198 var object startpoint = Car(dirlist);
3199 if (!(eq(startpoint,S(Krelative)) || eq(startpoint,S(Kabsolute))))
3200 return false;
3201 }
3202 dirlist = Cdr(dirlist);
3203 /* check subdir list: */
3204 while (consp(dirlist)) {
3205 /* check the next subdir = POP(dirlist); */
3206 var object subdir = Car(dirlist); dirlist = Cdr(dirlist);
3207 if (logical) {
3208 if (!(eq(subdir,S(Kwild_inferiors)) || eq(subdir,S(Kwild))
3209 || legal_logical_word(subdir) || eq(subdir,S(Kup))))
3210 return false;
3211 } else {
3212 #ifdef PATHNAME_NOEXT
3213 #if defined(PATHNAME_UNIX) || defined(PATHNAME_WIN32)
3214 if (!(eq(subdir,S(Kwild_inferiors)) || eq(subdir,S(Kwild))
3215 || legal_name(subdir) || eq(subdir,S(Kup))))
3216 return false;
3217 #endif
3218 #endif
3219 }
3220 }
3221 return true;
3222 }
3223
3224 #define COERCE_PATHNAME_SLOT(slot,obj,stack_res) \
3225 stack_res = ThePathname(coerce_pathname(obj))->pathname_##slot
3226
3227 /* (MAKE-PATHNAME [:host] [:device] [:directory] [:name] [:type] [:version]
3228 [:defaults] [:case]),
3229 CLTL p. 416, CLtL2 p. 643 */
3230 LISPFUN(make_pathname,seclass_read,0,0,norest,key,8,
3231 (kw(defaults),kw(case),kw(host),kw(device),kw(directory),
3232 kw(name),kw(type),kw(version)) )
3233 { /* stack layout: defaults, case, host, device, directory,
3234 name, type, version. */
3235 var bool logical = false;
3236 var bool convert = eq(STACK_6,S(Kcommon));
3237 /* 0. check defaults (STACK_7): */
3238 if (boundp(STACK_7)) {
3239 if (!nullpSv(parse_namestring_ansi)
3240 && stringp(STACK_7) && looks_logical_p(STACK_7))
3241 STACK_7 = parse_as_logical(STACK_7);
3242 else
3243 STACK_7 = coerce_xpathname(STACK_7);
3244 }
3245 /* 1. check host: */
3246 if (logpathnamep(STACK_5)) {
3247 STACK_5 = TheLogpathname(STACK_5)->pathname_host;
3248 logical = true;
3249 }
3250 if (!boundp(STACK_5)) {
3251 var object d_path = defaults_pathname();
3252 STACK_5 = (!boundp(STACK_7) ?
3253 xpathname_host(logpathnamep(d_path),d_path) :
3254 xpathname_host(logpathnamep(STACK_7),STACK_7));
3255 } else {
3256 #if HAS_HOST
3257 STACK_5 = test_optional_host(STACK_5,convert);
3258 #else
3259 STACK_5 = test_optional_host(STACK_5);
3260 #endif
3261 }
3262 if (!nullp(STACK_5) && logical_host_p(STACK_5)) {
3263 logical = true; STACK_5 = string_upcase(STACK_5);
3264 }
3265 DOUT("make-pathname:[version]",STACK_0);
3266 DOUT("make-pathname:[type]",STACK_1);
3267 DOUT("make-pathname:[name]",STACK_2);
3268 DOUT("make-pathname:[directory]",STACK_3);
3269 DOUT("make-pathname:[device]",STACK_4);
3270 DOUT("make-pathname:[host]",STACK_5);
3271 DOUT("make-pathname:[case]",STACK_6);
3272 DOUT("make-pathname:[defaults]",STACK_7);
3273 #if HAS_DEVICE
3274 { /* 2. check device: */
3275 var object device = STACK_4;
3276 if (!boundp(device)) {
3277 if (!boundp(STACK_7)) /* no defaults? */
3278 STACK_4 = NIL; /* -> use NIL */
3279 } else {
3280 if (stringp(device))
3281 STACK_4 = device = coerce_normal_ss(device);
3282 if (convert)
3283 STACK_4 = device = common_case(device);
3284 if (nullp(device)) /* = NIL ? */
3285 goto device_ok;
3286 else if (logical) {
3287 if (logpathnamep(device) /* Pathname -> its device */
3288 || (eq(device,S(Kunspecific)))) { /* :UNSPECIFIC -> NIL */
3289 STACK_4 = NIL; goto device_ok;
3290 }
3291 }
3292 #ifdef PATHNAME_WIN32
3293 else if (eq(device,S(Kwild))) /* = :WILD ? */
3294 goto device_ok;
3295 else if (simple_string_p(device)) { /* Simple-String ? */
3296 if (Sstring_length(device) == 1) { /* of length 1 ? */
3297 var chart ch = schar(device,0);
3298 if ((as_cint(ch) >= 'A') && (as_cint(ch) <= 'Z')) /* with letters >='A' and <='Z' ? */
3299 goto device_ok;
3300 }
3301 }
3302 #endif
3303 else if (xpathnamep(device)) { /* Pathname -> its Device */
3304 COERCE_PATHNAME_SLOT(device,device,STACK_4);
3305 goto device_ok;
3306 }
3307 /* None of the desired cases -> error: */
3308 pushSTACK(STACK_4); pushSTACK(S(Kdevice)); goto error_arg;
3309 device_ok: ;
3310 #ifdef PATHNAME_WIN32
3311 if (!nullp(STACK_5) && !nullp(STACK_4)) {
3312 pushSTACK(STACK_4);
3313 pushSTACK(STACK_(5+1));
3314 pushSTACK(TheSubr(subr_self)->name);
3315 error(error_condition,
3316 GETTEXT("~S: on host ~S, device ~S is invalid, should be NIL"));
3317 }
3318 #endif
3319 }
3320 }
3321 #else /* HAS_DEVICE */
3322 {
3323 var object device = STACK_4;
3324 if (boundp(device)) { /* specified ? */
3325 if (!(nullp(device) || eq(device,S(Kunspecific))
3326 || xpathnamep(device))) { /* NIL or :UNSPECIFIC or Pathname -> OK */
3327 /* None of the desired cases -> error: */
3328 pushSTACK(STACK_4); pushSTACK(S(Kdevice)); goto error_arg;
3329 }
3330 }
3331 }
3332 #endif
3333 { /* 3. check directory: */
3334 DOUT("make-pathname:[directory]",STACK_3);
3335 var object directory = STACK_3;
3336 if (!boundp(directory) && boundp(STACK_7)) {
3337 /* not specified but defaults is supplied */
3338 goto directory_ok;
3339 } else if (missingp(directory)) { /* not specified or NIL */
3340 STACK_3 = NIL; /* default_directory == NIL */
3341 goto directory_ok;
3342 } else if (eq(directory,S(Kwild)) || eq(directory,S(Kwild_inferiors))) {
3343 directory = S(Kwild_inferiors);
3344 goto directory_add_absolute;
3345 } else if (stringp(directory)) {
3346 if (!legal_name(directory)) goto directory_bad;
3347 STACK_3 = directory = coerce_normal_ss(directory);
3348 directory_add_absolute:
3349 pushSTACK(S(Kabsolute));
3350 pushSTACK(directory);
3351 directory = listof(2); STACK_3 = directory;
3352 goto directory_ok;
3353 } else if (consp(directory)) { /* a Cons? */
3354 STACK_3 = directory = simplify_directory(copy_list(directory));
3355 if (convert)
3356 STACK_3 = directory = subst_common_case(directory);
3357 if (!directory_list_valid_p(logical,directory))
3358 goto directory_bad;
3359 else
3360 goto directory_ok;
3361 } else if (logical) {
3362 if (logpathnamep(directory)) { /* Pathname -> its Directory */
3363 STACK_3 = TheLogpathname(directory)->pathname_directory;
3364 goto directory_ok;
3365 }
3366 } else if (xpathnamep(directory)) { /* Pathname -> its Directory */
3367 COERCE_PATHNAME_SLOT(directory,directory,STACK_3);
3368 goto directory_ok;
3369 }
3370 /* None of the desired cases -> error: */
3371 directory_bad:
3372 { pushSTACK(STACK_3); pushSTACK(S(Kdirectory)); } goto error_arg;
3373 directory_ok: ;
3374 }
3375 { /* 4. check name: */
3376 DOUT("make-pathname:[name]",STACK_2);
3377 var object name = STACK_2;
3378 if (stringp(name))
3379 STACK_2 = name = coerce_normal_ss(name);
3380 if (convert)
3381 STACK_2 = name = common_case(name);
3382 if (!boundp(name)) { /* not specified */
3383 if (!boundp(STACK_7)) /* no defaults? */
3384 STACK_2 = NIL; /* -> use NIL */
3385 } else if (equal(name,O(empty_string))) { /* name = "" ? */
3386 STACK_2 = NIL; /* -> use NIL */
3387 } else if (nullp(name)) { /* NIL is OK */
3388 } else if (logical) {
3389 if (legal_logical_word(name)) { /* OK */
3390 } else if (logpathnamep(name)) { /* Pathname -> its Name */
3391 STACK_2 = TheLogpathname(name)->pathname_name;
3392 } else { /* None of the desired cases -> error: */
3393 pushSTACK(STACK_2); pushSTACK(S(Kname)); goto error_arg;
3394 }
3395 }
3396 #ifdef PATHNAME_NOEXT
3397 else if (eq(name,S(Kwild))) { /* :WILD is OK */
3398 }
3399 #endif
3400 else if (legal_name(name)) { /* admissible Name is OK */
3401 STACK_2 = name = coerce_normal_ss(name);
3402 } else if (xpathnamep(name)) { /* Pathname -> its Name */
3403 COERCE_PATHNAME_SLOT(name,name,STACK_2);
3404 } else { /* None of the desired cases -> error: */
3405 pushSTACK(STACK_2); pushSTACK(S(Kname)); goto error_arg;
3406 }
3407 }
3408 { /* 5. check type: */
3409 DOUT("make-pathname:[type]",STACK_1);
3410 var object type = STACK_1;
3411 if (stringp(type))
3412 STACK_1 = type = coerce_normal_ss(type);
3413 if (convert)
3414 STACK_1 = type = common_case(type);
3415 if (!boundp(type)) {
3416 if (!boundp(STACK_7)) /* no Defaults ? */
3417 STACK_1 = NIL; /* -> use NIL */
3418 } else if (nullp(type)) { /* NIL is OK */
3419 } else if (logical) {
3420 if (legal_logical_word(type)) { /* OK */
3421 } else if (logpathnamep(type)) { /* Pathname -> its Type */
3422 STACK_1 = TheLogpathname(type)->pathname_type;
3423 } else { /* None of the desired cases -> error: */
3424 pushSTACK(STACK_1); pushSTACK(S(Ktype)); goto error_arg;
3425 }
3426 }
3427 #ifdef PATHNAME_NOEXT
3428 else if (eq(type,S(Kwild))) { /* :WILD is OK */
3429 }
3430 #endif
3431 else if (legal_type(type)) {
3432 } else if (xpathnamep(type)) { /* Pathname -> its Type */
3433 COERCE_PATHNAME_SLOT(type,type,STACK_1);
3434 } else { /* None of the desired cases -> error: */
3435 pushSTACK(STACK_1); pushSTACK(S(Ktype)); goto error_arg;
3436 }
3437 }
3438 /* 6. check version: */
3439 { STACK_0 = test_optional_version(!boundp(STACK_7) ? NIL : unbound); }
3440 DOUT("make-pathname:[ver]",STACK_0);
3441 DOUT("make-pathname:[ver]",STACK_7);
3442 { /* 7. build Pathname: */
3443 var object pathname;
3444 if (logical) {
3445 pathname = allocate_logpathname(); /* new Logical Pathname */
3446 TheLogpathname(pathname)->pathname_version = popSTACK();
3447 TheLogpathname(pathname)->pathname_type = popSTACK();
3448 TheLogpathname(pathname)->pathname_name = popSTACK();
3449 TheLogpathname(pathname)->pathname_directory = popSTACK();
3450 skipSTACK(1);
3451 TheLogpathname(pathname)->pathname_host = popSTACK();
3452 } else {
3453 pathname = allocate_pathname(); /* new Pathname */
3454 ThePathname(pathname)->pathname_version = popSTACK();
3455 ThePathname(pathname)->pathname_type = popSTACK();
3456 ThePathname(pathname)->pathname_name = popSTACK();
3457 ThePathname(pathname)->pathname_directory = popSTACK();
3458 #if HAS_DEVICE
3459 ThePathname(pathname)->pathname_device = popSTACK();
3460 #else
3461 skipSTACK(1);
3462 #endif
3463 #if HAS_HOST
3464 ThePathname(pathname)->pathname_host = popSTACK();
3465 #else
3466 skipSTACK(1);
3467 #endif
3468 }
3469 STACK_0 = pathname; /* forget case */
3470 DOUT("make-pathname:[pathname]",STACK_0);
3471 DOUT("make-pathname:[defaults]",STACK_1);
3472 pathname = popSTACK();
3473 /* 8. poss. merge in Defaults: */
3474 var object defaults = popSTACK();
3475 if (!boundp(defaults)) { /* no defaults given -> pathname is the value */
3476 value1 = pathname;
3477 } else {
3478 /* (MERGE-PATHNAMES pathname defaults [nil] :wild #'make-pathname) */
3479 pushSTACK(pathname); pushSTACK(defaults);
3480 pushSTACK(unbound); pushSTACK(S(Kwild)); pushSTACK(L(make_pathname));
3481 funcall(L(merge_pathnames),5);
3482 }
3483 mv_count=1;
3484 DOUT("make-pathname:[ret]",value1);
3485 return;
3486 }
3487 error_arg: /* error-message: */
3488 pushSTACK(TheSubr(subr_self)->name);
3489 error(error_condition,GETTEXT("~S: Illegal ~S argument ~S"));
3490 }
3491 #undef COERCE_PATHNAME_SLOT
3492
3493 /* (MAKE-LOGICAL-PATHNAME [:host] [:device] [:directory] [:name]
3494 [:type] [:version] [:defaults] [:case]),
3495 like MAKE-PATHNAME, except that a Logical Pathname is built. */
3496 LISPFUN(make_logical_pathname,seclass_read,0,0,norest,key,8,
3497 (kw(defaults),kw(case),kw(host),kw(device),
3498 kw(directory),kw(name),kw(type),kw(version)) )
3499 { /* A logical pathname as :HOST-Argument for MAKE-PATHNAME
3500 enforces a logical pathname as result. */
3501 if (boundp(STACK_5)) STACK_5 = string_upcase(STACK_5); /* host */
3502 else STACK_5 = NIL;
3503 { var object obj = allocate_logpathname();
3504 TheLogpathname(obj)->pathname_host = STACK_5;
3505 STACK_5 = obj; }
3506 /* PATHNAME-DEVICE for LOGICAL-PATHNAME returns :UNSPECIFIC, so
3507 MAKE-LOGICAL-PATHNAME must accept :DEVICE :UNSPECIFIC */
3508 if (eq(STACK_4,S(Kunspecific))) STACK_4 = NIL; /* device */
3509 /* continue at MAKE-PATHNAME. */
3510 C_make_pathname();
3511 }
3512
3513 /* (USER-HOMEDIR-PATHNAME [host]), CLTL p. 418 */
3514 LISPFUN(user_homedir_pathname,seclass_default,0,1,norest,nokey,0,NIL) {
3515 DOUT("user-homedir-pathname:[host]",STACK_0);
3516 #if HAS_HOST
3517 STACK_0 = test_optional_host(STACK_0,false); /* check Host */
3518 if (!nullp(STACK_0)) {
3519 #if defined(PATHNAME_WIN32)
3520 { /* This is very primitive.
3521 Does Windows have the notion of homedirs on remote hosts?? */
3522 var object pathname = allocate_pathname(); /* new Pathname */
3523 ThePathname(pathname)->pathname_host = popSTACK();
3524 #if HAS_DEVICE
3525 ThePathname(pathname)->pathname_device = NIL;
3526 #endif
3527 ThePathname(pathname)->pathname_directory = O(directory_absolute);
3528 ThePathname(pathname)->pathname_name = NIL;
3529 ThePathname(pathname)->pathname_type = NIL;
3530 ThePathname(pathname)->pathname_version = NIL;
3531 VALUES1(pathname);
3532 }
3533 #else
3534 #error user-homedir-pathname: HAS_HOST & !WIN32
3535 #endif
3536 } else { /* no host given */
3537 skipSTACK(1);
3538 VALUES1(O(user_homedir)); /* User-Homedir-Pathname */
3539 }
3540 #else /* HAS_HOST */
3541 test_optional_host(popSTACK()); /* check Host and ignore */
3542 VALUES1(O(user_homedir)); /* User-Homedir-Pathname */
3543 #endif
3544 DOUT("user-homedir-pathname:[ret]",value1);
3545 }
3546
3547 /* UP: copies a pathname.
3548 copy_pathname(pathname)
3549 > pathname: non-logical pathname
3550 < result: copy of the pathname, with the same components
3551 can trigger GC */
copy_pathname(object pathname)3552 local maygc object copy_pathname (object pathname) {
3553 pushSTACK(pathname);
3554 var object newp = allocate_pathname();
3555 pathname = popSTACK();
3556 #if HAS_HOST
3557 ThePathname(newp)->pathname_host
3558 = ThePathname(pathname)->pathname_host;
3559 #endif
3560 #if HAS_DEVICE
3561 ThePathname(newp)->pathname_device
3562 = ThePathname(pathname)->pathname_device;
3563 #endif
3564 ThePathname(newp)->pathname_directory
3565 = ThePathname(pathname)->pathname_directory;
3566 ThePathname(newp)->pathname_name
3567 = ThePathname(pathname)->pathname_name;
3568 ThePathname(newp)->pathname_type
3569 = ThePathname(pathname)->pathname_type;
3570 ThePathname(newp)->pathname_version
3571 = ThePathname(pathname)->pathname_version;
3572 return newp;
3573 }
3574
3575 /*
3576 * Wildcards
3577 * =========
3578 */
3579
3580 /* UP: check whether the object is wild
3581 name(object)
3582 > object: normal simple-string or symbol
3583 < return: true when the object is wild */
3584 #define DEF_WILD_CHECKER(name,char_wild_p) \
3585 local bool name (object obj, bool dirp) { \
3586 if (simple_string_p(obj)) { \
3587 var uintL len = Sstring_length(obj); \
3588 if (len > 0) { \
3589 SstringDispatch(obj,X, { \
3590 const cintX* charptr = &((SstringX)TheVarobject(obj))->data[0]; \
3591 dotimespL(len,len, { \
3592 chart ch = as_chart(*charptr++); \
3593 if (char_wild_p(ch)) \
3594 return true; \
3595 }); \
3596 }); \
3597 } \
3598 return false; \
3599 } else \
3600 return eq(obj,S(Kwild)) || (dirp && eq(obj,S(Kwild_inferiors))); \
3601 }
3602
3603 #ifdef PATHNAME_NOEXT
3604 DEF_WILD_CHECKER(wild_p,wild_char_p)
3605 #endif
3606
3607 DEF_WILD_CHECKER(word_wild_p,multiwild_char_p)
3608
3609 /* UP: checks, if the host-component of a pathname contains wildcards.
3610 has_host_wildcards(pathname)
3611 > pathname: pathname
3612 < result: true if (PATHNAME-HOST pathname) contains wildcards. */
3613 local bool has_host_wildcards (object pathname);
3614 /* host can not contain wildcards. */
3615 #define has_host_wildcards(pathname) (unused (pathname), false)
3616
3617 /* UP: checks, if the device-component of a pathname contains wildcards.
3618 has_device_wildcards(pathname)
3619 > pathname: pathname
3620 < result: true if (PATHNAME-DEVICE pathname) contains wildcards. */
has_device_wildcards(object pathname)3621 local bool has_device_wildcards (object pathname) {
3622 #ifdef PATHNAME_WIN32
3623 if (logpathnamep(pathname))
3624 return false;
3625 /* check device: = :WILD ? */
3626 return eq(ThePathname(pathname)->pathname_device,S(Kwild));
3627 #else
3628 unused(pathname);
3629 return false;
3630 #endif
3631 }
3632
3633 /* UP: checks, if the directory-component of a pathname contains wildcards.
3634 has_directory_wildcards(pathname)
3635 > pathname: pathname
3636 < result: true if (PATHNAME-DIRECTORY pathname) contains wildcards. */
has_directory_wildcards(object pathname)3637 local bool has_directory_wildcards (object pathname) {
3638 /* check directory: */
3639 if (logpathnamep(pathname)) {
3640 var object directory = TheLogpathname(pathname)->pathname_directory;
3641 for (;consp(directory); directory = Cdr(directory))
3642 if (word_wild_p(Car(directory),true))
3643 return true;
3644 return false;
3645 }
3646 var object directory = ThePathname(pathname)->pathname_directory;
3647 for (;consp(directory); directory = Cdr(directory))
3648 if (wild_p(Car(directory),true))
3649 return true;
3650 return false;
3651 }
3652
3653 /* UP: checks, if the name-component of a pathname contains wildcards.
3654 has_name_wildcards(pathname)
3655 > pathname: pathname
3656 < result: true if (PATHNAME-NAME pathname) contains wildcards. */
has_name_wildcards(object pathname)3657 local bool has_name_wildcards (object pathname) {
3658 /* check name: */
3659 if (logpathnamep(pathname))
3660 return word_wild_p(TheLogpathname(pathname)->pathname_name,false);
3661 #ifdef PATHNAME_NOEXT
3662 return wild_p(ThePathname(pathname)->pathname_name,false);
3663 #endif
3664 return false;
3665 }
3666
3667 /* UP: checks, if the type-component of a pathname contains wildcards.
3668 has_type_wildcards(pathname)
3669 > pathname: pathname
3670 < result: true if (PATHNAME-TYPE pathname) contains wildcards. */
has_type_wildcards(object pathname)3671 local bool has_type_wildcards (object pathname) {
3672 /* check type: */
3673 if (logpathnamep(pathname))
3674 return word_wild_p(TheLogpathname(pathname)->pathname_type,false);
3675 #ifdef PATHNAME_NOEXT
3676 return wild_p(ThePathname(pathname)->pathname_type,false);
3677 #endif
3678 return false;
3679 }
3680
3681 /* UP: checks, if the version-component of a pathname contains wildcards.
3682 has_version_wildcards(pathname)
3683 > pathname: pathname
3684 < result: true if (PATHNAME-VERSION pathname) contains wildcards. */
has_version_wildcards(object pathname)3685 local bool has_version_wildcards (object pathname) {
3686 /* check version: */
3687 return eq(S(Kwild),xpathname_version(logpathnamep(pathname),pathname));
3688 }
3689
3690 /* UP: checks, if any component of a pathname contains wildcards.
3691 has_some_wildcards(pathname)
3692 > pathname: pathname
3693 < result: true if pathname contains wildcards. */
has_some_wildcards(object pathname)3694 local bool has_some_wildcards (object pathname) {
3695 if (has_host_wildcards(pathname)) return true;
3696 if (has_device_wildcards(pathname)) return true;
3697 if (has_directory_wildcards(pathname)) return true;
3698 if (has_name_wildcards(pathname)) return true;
3699 if (has_type_wildcards(pathname)) return true;
3700 if (has_version_wildcards(pathname)) return true;
3701 return false;
3702 }
3703
3704 /* UP: checks, if a pathname contains no wildcards.
3705 check_no_wildcards(pathname);
3706 > pathname: pathname */
check_no_wildcards(object pathname)3707 local void check_no_wildcards (object pathname) {
3708 if (!has_some_wildcards(pathname)) /* no wildcards found. */
3709 return;
3710 /* error-message, if the pathname contains wildcards: */
3711 pushSTACK(pathname); /* FILE-ERROR slot PATHNAME */
3712 pushSTACK(pathname); pushSTACK(TheSubr(subr_self)->name);
3713 error(file_error,GETTEXT("~S: wildcards are not allowed here: ~S"));
3714 }
3715
3716 LISPFUN(wild_pathname_p,seclass_rd_sig,1,1,norest,nokey,0,NIL)
3717 { /* (WILD-PATHNAME-P pathname [field-key]), CLtL2 p. 623 */
3718 var object pathname = coerce_xpathname(STACK_1);
3719 var object key = STACK_0;
3720 var bool erg;
3721 if (missingp(key)) {
3722 erg = has_some_wildcards(pathname);
3723 } else if (eq(key,S(Khost))) {
3724 erg = has_host_wildcards(pathname);
3725 } else if (eq(key,S(Kdevice))) {
3726 erg = has_device_wildcards(pathname);
3727 } else if (eq(key,S(Kdirectory))) {
3728 erg = has_directory_wildcards(pathname);
3729 } else if (eq(key,S(Kname))) {
3730 erg = has_name_wildcards(pathname);
3731 } else if (eq(key,S(Ktype))) {
3732 erg = has_type_wildcards(pathname);
3733 } else if (eq(key,S(Kversion))) {
3734 erg = has_version_wildcards(pathname);
3735 } else {
3736 pushSTACK(key); /* TYPE-ERROR slot DATUM */
3737 pushSTACK(O(type_pathname_field_key)); /* TYPE-ERROR slot EXPECTED-TYPE */
3738 pushSTACK(NIL);
3739 pushSTACK(S(Kversion));
3740 pushSTACK(S(Ktype));
3741 pushSTACK(S(Kname));
3742 pushSTACK(S(Kdirectory));
3743 pushSTACK(S(Kdevice));
3744 pushSTACK(S(Khost));
3745 pushSTACK(key);
3746 pushSTACK(TheSubr(subr_self)->name);
3747 error(type_error,
3748 GETTEXT("~S: argument ~S should be ~S, ~S, ~S, ~S, ~S, ~S or ~S"));
3749 }
3750 VALUES_IF(erg); /* boolean value */
3751 skipSTACK(2);
3752 }
3753
3754 /* Wildcard Matching
3755 ================= */
3756
3757 /* For the purposes of wildcard matching, according to CLHS, non-present
3758 components (i.e. NIL or a directory = (:RELATIVE)) are treated as wild. */
3759
3760 /* UP: Matches a Wildcard-String ("Pattern") with a "Sample".
3761 > pattern : Normal-Simple-String, with wildcards
3762 '?' for exactly 1 character
3763 '*' for arbitrary many characters
3764 > sample : Normal-Simple-String, that has to be matched
3765 recursive implementation because of backtracking: */
3766 local bool wildcard_match_ab (uintL m_count, const chart* m_ptr,
3767 uintL b_count, const chart* b_ptr);
wildcard_match(object pattern,object sample)3768 local bool wildcard_match (object pattern, object sample) {
3769 if (eq(pattern,S(Kwild)) || eq(pattern,S(Kwild_inferiors)))
3770 return true;
3771 if (eq(pattern,S(Kup)) || eq(pattern,S(Kback)))
3772 return false;
3773 ASSERT(sstring_normal_p(pattern));
3774 ASSERT(sstring_normal_p(sample));
3775 return wildcard_match_ab(
3776 /* m_count = */ Sstring_length(pattern),
3777 /* m_ptr = */ &TheSnstring(pattern)->data[0],
3778 /* b_count = */ Sstring_length(sample),
3779 /* b_ptr = */ &TheSnstring(sample)->data[0]
3780 );
3781 }
wildcard_match_ab(uintL m_count,const chart * m_ptr,uintL b_count,const chart * b_ptr)3782 local bool wildcard_match_ab (uintL m_count, const chart* m_ptr,
3783 uintL b_count, const chart* b_ptr) {
3784 var chart c;
3785 while (1) {
3786 if (m_count==0)
3787 return (b_count==0); /* "" matches only "" */
3788 m_count--;
3789 c = *m_ptr++; /* next match-character */
3790 if (singlewild_char_p(c)) { /* wildcard '?' */
3791 if (b_count==0) return false; /* at least one character still has to come */
3792 b_count--; b_ptr++; /* it will be ignored */
3793 } else if (multiwild_char_p(c))
3794 break; /* wildcard '*' later */
3795 else { /* everything else must match exactly: */
3796 if (b_count==0) return false;
3797 b_count--; if (!equal_pathchar(*b_ptr++,c)) return false;
3798 }
3799 }
3800 /* Wildcard '*': Search next non-wildcard-character and also count the '?'
3801 (because a sequence '*??*???***?' matches everything, that is as least as
3802 long as the sequence of question marks). The '?' can also be utilized
3803 immediately, because '*??*???***?' is equivalent to '??????*' . */
3804 while (1) {
3805 if (m_count==0) return true; /* wildcard at the end matches the rest. */
3806 m_count--;
3807 c = *m_ptr++; /* next match-character */
3808 if (singlewild_char_p(c)) {
3809 /* question mark: move forward, process instantly */
3810 if (b_count==0) return false;
3811 b_count--; b_ptr++;
3812 } else if (!multiwild_char_p(c))
3813 break;
3814 }
3815 /* c = next non-wildcard-character. Search it. */
3816 while (1) {
3817 if (b_count==0) return false; /* c not found */
3818 b_count--;
3819 if (equal_pathchar(*b_ptr++,c)) {
3820 if (wildcard_match_ab(m_count,m_ptr,b_count,b_ptr))
3821 return true;
3822 }
3823 }
3824 }
3825
3826 /* UPs: matches a pathname-component ("Sample") and
3827 a pathname-component ("Pattern") at a time. */
3828 local bool host_match (object pattern, object sample, bool logical);
3829 local bool device_match (object pattern, object sample, bool logical);
3830 local bool directory_match (object pattern, object sample, bool logical);
3831 local bool nametype_match (object pattern, object sample, bool logical);
3832 local bool version_match (object pattern, object sample, bool logical);
host_match(object pattern,object sample,bool logical)3833 local bool host_match (object pattern, object sample, bool logical)
3834 { unused(logical);
3835 if (nullp(pattern)) return true;
3836 return equal(pattern,sample);
3837 }
device_match(object pattern,object sample,bool logical)3838 local bool device_match (object pattern, object sample, bool logical) {
3839 #if HAS_DEVICE
3840 if (logical) {
3841 return true;
3842 }
3843 if (nullp(pattern)) return true;
3844 #ifdef PATHNAME_WIN32
3845 if (eq(pattern,S(Kwild))) return true;
3846 if (eq(sample,S(Kwild))) return false;
3847 #endif
3848 #ifdef PATHNAME_WIN32
3849 return equalp(pattern,sample);
3850 #else
3851 return equal(pattern,sample);
3852 #endif
3853 #else
3854 unused(pattern); unused(sample); unused(logical);
3855 return true;
3856 #endif
3857 }
nametype_match_aux(object pattern,object sample,bool logical)3858 local bool nametype_match_aux (object pattern, object sample, bool logical)
3859 { unused(logical);
3860 if (eq(pattern,S(Kwild))) return true;
3861 if (eq(sample,S(Kwild))) return false;
3862 if (nullp(pattern)) {
3863 if (nullp(sample))
3864 return true;
3865 else
3866 return false;
3867 }
3868 if (nullp(sample))
3869 return false;
3870 return wildcard_match(pattern,sample);
3871 }
subdir_match(object pattern,object sample,bool logical)3872 local bool subdir_match (object pattern, object sample, bool logical)
3873 { unused(logical);
3874 if (eq(pattern,sample)) return true;
3875 if (eq(pattern,S(Kwild))) return true;
3876 if (!simple_string_p(pattern) || !simple_string_p(sample)) return false;
3877 return wildcard_match(pattern,sample);
3878 }
3879 /* recursive implementation because of backtracking: */
3880 local bool directory_match_ab (object m_list, object b_list, bool logical);
directory_match_ab(object m_list,object b_list,bool logical)3881 local bool directory_match_ab (object m_list, object b_list, bool logical) {
3882 /* Algorithm analogous to wildcard_match_ab. */
3883 var object item;
3884 while (1) {
3885 if (atomp(m_list)) { return atomp(b_list); }
3886 item = Car(m_list); m_list = Cdr(m_list);
3887 if (eq(item,S(Kwild_inferiors))) break;
3888 if (atomp(b_list)) return false;
3889 if (!subdir_match(item,Car(b_list),logical)) return false;
3890 b_list = Cdr(b_list);
3891 }
3892 while (1) {
3893 if (atomp(m_list)) return true;
3894 item = Car(m_list); m_list = Cdr(m_list);
3895 if (!eq(item,S(Kwild_inferiors))) break;
3896 }
3897 while (1) {
3898 if (atomp(b_list)) return false;
3899 if (subdir_match(item,Car(b_list),logical)) {
3900 b_list = Cdr(b_list);
3901 if (directory_match_ab(m_list,b_list,logical)) return true;
3902 } else {
3903 b_list = Cdr(b_list);
3904 }
3905 }
3906 }
directory_trivial_p(object dir)3907 local inline bool directory_trivial_p (object dir) {
3908 return nullp(dir)
3909 || (consp(dir) ? (eq(Car(dir),S(Krelative)) && nullp(Cdr(dir))) : false);
3910 }
directory_match(object pattern,object sample,bool logical)3911 local bool directory_match (object pattern, object sample, bool logical) {
3912 if (nullp(pattern)) /* compare pattern with directory_default */
3913 return true;
3914 if (missingp(sample)) return true;
3915 /* match startpoint: */
3916 if (!eq(Car(pattern),Car(sample)))
3917 return false;
3918 pattern = Cdr(pattern); sample = Cdr(sample);
3919 /* match subdirs: */
3920 return directory_match_ab(pattern,sample,logical);
3921 }
nametype_match(object pattern,object sample,bool logical)3922 local bool nametype_match (object pattern, object sample, bool logical) {
3923 if (missingp(pattern)) return true;
3924 return nametype_match_aux(pattern,sample,logical);
3925 }
version_match(object pattern,object sample,bool logical)3926 local bool version_match (object pattern, object sample, bool logical)
3927 { unused(logical);
3928 SDOUT("version_match:",pattern);
3929 SDOUT("version_match:",sample);
3930 if (!boundp(sample)) return true;
3931 if (nullp(pattern) || eq(pattern,S(Kwild))) return true;
3932 if (eq(sample,S(Kwild))) return false;
3933 return eql(pattern,sample);
3934 }
3935
3936 LISPFUNNS(pathname_match_p,2)
3937 { /* (PATHNAME-MATCH-P pathname wildname), CLtL2 p. 623 */
3938 /* stack layout: pathname, wildname. */
3939 var bool logical = false;
3940 STACK_1 = coerce_xpathname(STACK_1);
3941 STACK_0 = coerce_xpathname(STACK_0);
3942 if (logpathnamep(STACK_1) && logpathnamep(STACK_0)) {
3943 logical = true;
3944 } else {
3945 /* not both logical pathnames -> first convert into normal pathnames: */
3946 STACK_1 = coerce_pathname(STACK_1);
3947 STACK_0 = coerce_pathname(STACK_0);
3948 }
3949 DOUT("pathname-match-p:[s0]",STACK_0);
3950 DOUT("pathname-match-p:[s1]",STACK_1);
3951 var object wildname = popSTACK();
3952 var object pathname = popSTACK();
3953 if (!host_match(xpathname_host(logical,wildname),
3954 xpathname_host(logical,pathname),
3955 logical))
3956 goto no;
3957 if (!device_match(xpathname_device(logical,wildname),
3958 xpathname_device(logical,pathname),
3959 logical))
3960 goto no;
3961 if (!directory_match(xpathname_directory(logical,wildname),
3962 xpathname_directory(logical,pathname),
3963 logical))
3964 goto no;
3965 if (!nametype_match(xpathname_name(logical,wildname),
3966 xpathname_name(logical,pathname),
3967 logical))
3968 goto no;
3969 if (!nametype_match(xpathname_type(logical,wildname),
3970 xpathname_type(logical,pathname),
3971 logical))
3972 goto no;
3973 if (!version_match(xpathname_version(logical,wildname),
3974 xpathname_version(logical,pathname),
3975 logical))
3976 goto no;
3977 yes:
3978 VALUES1(T); return;
3979 no:
3980 VALUES1(NIL); return;
3981 }
3982
3983 /* (TRANSLATE-PATHNAME sample pattern1 pattern2) implemented as follows:
3984 1. (PATHNAME-MATCH-P sample pattern1) while checking, extract
3985 text items from the substitution pattern (:WILD -> "*").
3986 2. Put the text items into pattern2 until pattern2 is full or all the
3987 text items are used up
3988 3. finally, (MERGE-PATHNAMES modified_pattern2 sample). */
3989
3990 /* UP: Compare a wildcard string ("Pattern") with "Sample".
3991 wildcard_diff(pattern,sample,previous,solutions);
3992 > pattern: normal simple string, with substitution characters
3993 '?' for exactly 1 character
3994 '*' for as many characters as desired
3995 > sample: normal simple string, to compare with
3996 > previous: the already known result of comparison
3997 (reversed list of normal simple strings, NILs and lists)
3998 > solutions: address of a list in the STACK, onto which the results of
3999 the comparisons (reversed list of normal simple strings
4000 and lists) have to be consed
4001 can trigger GC */
4002
4003 /* Here you need not Lisp or C, but PROLOG!
4004 (PUSH previous solutions) */
4005 #define push_solution() do { \
4006 var object new_cons = allocate_cons(); \
4007 Car(new_cons) = *previous; \
4008 Cdr(new_cons) = *solutions; \
4009 *solutions = new_cons; \
4010 } while(0)
4011 /* (PUSH (CONS new_piece previous) solutions) */
4012 #define push_solution_with(new_piece) do { \
4013 pushSTACK(new_piece); \
4014 {var object new_cons = allocate_cons(); \
4015 Car(new_cons) = STACK_0; Cdr(new_cons) = *previous; \
4016 STACK_0 = new_cons; \
4017 new_cons = allocate_cons(); \
4018 Car(new_cons) = popSTACK(); Cdr(new_cons) = *solutions; \
4019 *solutions = new_cons; \
4020 }} while(0)
4021
4022 /* recursive implementation because of backtracking: */
wildcard_diff_ab(object pattern,object sample,uintL m_index,uintL b_index,const gcv_object_t * previous,gcv_object_t * solutions)4023 local maygc void wildcard_diff_ab (object pattern, object sample,
4024 uintL m_index, uintL b_index,
4025 const gcv_object_t* previous,
4026 gcv_object_t* solutions) {
4027 var chart cc;
4028 while (1) {
4029 if (m_index == Sstring_length(pattern)) {
4030 if (b_index == Sstring_length(sample))
4031 push_solution();
4032 return;
4033 }
4034 cc = schar(pattern,m_index++);
4035 if (multiwild_char_p(cc))
4036 break;
4037 if (b_index == Sstring_length(sample))
4038 return;
4039 if (singlewild_char_p(cc)) {
4040 /* recursive call to wildcard_diff_ab(), with extended previous: */
4041 cc = schar(sample,b_index++);
4042 pushSTACK(pattern); pushSTACK(sample);
4043 {
4044 var object new_string = allocate_string(1);
4045 TheS32string(new_string)->data[0] = as_cint(cc);
4046 pushSTACK(new_string);
4047 }
4048 {
4049 var object new_cons = allocate_cons();
4050 Car(new_cons) = STACK_0; Cdr(new_cons) = *previous;
4051 STACK_0 = new_cons; /* (CONS ... previous) */
4052 }
4053 wildcard_diff_ab(STACK_2,STACK_1,m_index,b_index,&STACK_0,solutions);
4054 skipSTACK(3);
4055 return;
4056 } else {
4057 if (!equal_pathchar(schar(sample,b_index++),cc))
4058 return;
4059 }
4060 }
4061 var uintL b_start_index = b_index;
4062 while (1) {
4063 /* to reduce consing, intercept cases when wildcard_diff_ab()
4064 does nothing */
4065 if (m_index == Sstring_length(pattern)
4066 ? b_index == Sstring_length(sample)
4067 : (cc = schar(pattern,m_index),
4068 wild_char_p(cc)
4069 || (b_index < Sstring_length(sample)
4070 && equal_pathchar(schar(sample,b_index),cc)))) {
4071 /* wildcard_diff_ab() recursive call, with extended previous: */
4072 pushSTACK(pattern); pushSTACK(sample);
4073 /* (SUBSTRING sample b_start_index b_index) */
4074 pushSTACK(subsstring(sample,b_start_index,b_index));
4075 var object new_cons = allocate_cons();
4076 Car(new_cons) = STACK_0; Cdr(new_cons) = *previous;
4077 STACK_0 = new_cons; /* (CONS ... previous) */
4078 wildcard_diff_ab(STACK_2,STACK_1,m_index,b_index,&STACK_0,solutions);
4079 skipSTACK(1);
4080 sample = popSTACK(); pattern = popSTACK();
4081 }
4082 if (b_index == Sstring_length(sample))
4083 break;
4084 b_index++;
4085 }
4086 }
4087
wildcard_diff(object pattern,object sample,const gcv_object_t * previous,gcv_object_t * solutions)4088 local maygc void wildcard_diff (object pattern, object sample,
4089 const gcv_object_t* previous,
4090 gcv_object_t* solutions) {
4091 ASSERT(sstring_normal_p(pattern));
4092 ASSERT(sstring_normal_p(sample));
4093 wildcard_diff_ab(pattern,sample,0,0,previous,solutions);
4094 }
4095
4096 #if DEBUG_TRANSLATE_PATHNAME>1
4097 /* all arguments to *_diff are on stack - this should be safe */
4098 #define DEBUG_DIFF(f) \
4099 printf("\n* " #f " [logical: %d]\n",logical); \
4100 DOUT("",pattern); DOUT("",sample); DOUT("",*previous); DOUT("",*solutions)
4101 #else
4102 #define DEBUG_DIFF(f)
4103 #endif
4104 /* UPs: compares a pathname-component ("Sample") and
4105 a pathname-component ("Pattern") at a time.
4106 can trigger GC */
4107 local maygc void host_diff (object pattern, object sample, bool logical,
4108 const gcv_object_t* previous,
4109 gcv_object_t* solutions);
4110 local maygc void device_diff (object pattern, object sample, bool logical,
4111 const gcv_object_t* previous,
4112 gcv_object_t* solutions);
4113 local maygc void directory_diff (object pattern, object sample, bool logical,
4114 const gcv_object_t* previous,
4115 gcv_object_t* solutions);
4116 local maygc void nametype_diff (object pattern, object sample, bool logical,
4117 const gcv_object_t* previous,
4118 gcv_object_t* solutions);
4119 local maygc void version_diff (object pattern, object sample, bool logical,
4120 const gcv_object_t* previous,
4121 gcv_object_t* solutions);
host_diff(object pattern,object sample,bool logical,const gcv_object_t * previous,gcv_object_t * solutions)4122 local maygc void host_diff (object pattern, object sample, bool logical,
4123 const gcv_object_t* previous, gcv_object_t* solutions) {
4124 DEBUG_DIFF(host_diff);
4125 if (logical) {
4126 if (nullp(pattern)) {
4127 push_solution_with(sample); return;
4128 }
4129 if (!equal(pattern,sample)) return;
4130 } else {
4131 #if HAS_HOST
4132 if (nullp(pattern)) {
4133 push_solution_with(sample); return;
4134 }
4135 if (!equal(pattern,sample)) return;
4136 #endif
4137 }
4138 #if HAS_HOST
4139 push_solution_with(S(Khost));
4140 #else
4141 push_solution();
4142 #endif
4143 }
device_diff(object pattern,object sample,bool logical,const gcv_object_t * previous,gcv_object_t * solutions)4144 local maygc void device_diff (object pattern, object sample, bool logical,
4145 const gcv_object_t* previous, gcv_object_t* solutions) {
4146 DEBUG_DIFF(device_diff);
4147 if (logical) {
4148 #if HAS_DEVICE
4149 push_solution_with(S(Kdevice));
4150 #else
4151 push_solution();
4152 #endif
4153 return;
4154 }
4155 #if HAS_DEVICE
4156 #ifdef PATHNAME_WIN32
4157 if (nullp(pattern) || eq(pattern,S(Kwild))) {
4158 var object string = wild2string(sample);
4159 push_solution_with(string);
4160 return;
4161 }
4162 if (eq(sample,S(Kwild))) return;
4163 if (nullp(pattern)) {
4164 var object string = wild2string(sample);
4165 push_solution_with(string);
4166 return;
4167 }
4168 if (!equalp(pattern,sample)) return;
4169 #else
4170 if (!equal(pattern,sample)) return;
4171 #endif
4172 push_solution_with(S(Kdevice));
4173 #else /* HAS_DEVICE */
4174 unused(pattern); unused(sample);
4175 push_solution();
4176 #endif
4177 }
nametype_diff_aux(object pattern,object sample,bool logical,const gcv_object_t * previous,gcv_object_t * solutions)4178 local maygc void nametype_diff_aux (object pattern, object sample, bool logical,
4179 const gcv_object_t* previous,
4180 gcv_object_t* solutions) {
4181 unused(logical);
4182 if (eq(pattern,S(Kwild))) {
4183 var object string = wild2string(sample);
4184 push_solution_with(string);
4185 return;
4186 }
4187 if (eq(sample,S(Kwild))) return;
4188 if (nullp(pattern)) {
4189 if (nullp(sample))
4190 push_solution();
4191 return;
4192 }
4193 if (nullp(sample))
4194 return;
4195 wildcard_diff(pattern,sample,previous,solutions);
4196 }
subdir_diff(object pattern,object sample,bool logical,const gcv_object_t * previous,gcv_object_t * solutions)4197 local maygc void subdir_diff (object pattern, object sample, bool logical,
4198 const gcv_object_t* previous, gcv_object_t* solutions)
4199 {
4200 DEBUG_DIFF(subdir_diff);
4201 if (eq(pattern,sample)) {
4202 if (eq(sample,S(Kwild)))
4203 push_solution_with(O(wild_string));
4204 else
4205 push_solution();
4206 return;
4207 }
4208 unused(logical);
4209 if (eq(pattern,S(Kwild))) {
4210 var object string = wild2string(sample);
4211 push_solution_with(string);
4212 return;
4213 }
4214 if (eq(sample,S(Kwild))) return;
4215 if (!simple_string_p(pattern) || !simple_string_p(sample)) return;
4216 wildcard_diff(pattern,sample,previous,solutions);
4217 }
4218 /* recursive implementation because of backtracking: */
directory_diff_ab(object m_list,object b_list,bool logical,const gcv_object_t * previous,gcv_object_t * solutions)4219 local maygc void directory_diff_ab (object m_list, object b_list, bool logical,
4220 const gcv_object_t* previous,
4221 gcv_object_t* solutions) {
4222 /* algorithm analogous to wildcard_diff_ab. */
4223 var object item;
4224 if (atomp(m_list)) {
4225 if (atomp(b_list))
4226 push_solution();
4227 return;
4228 }
4229 item = Car(m_list); m_list = Cdr(m_list);
4230 if (!eq(item,S(Kwild_inferiors))) {
4231 if (atomp(b_list)) return;
4232 pushSTACK(NIL); pushSTACK(m_list); pushSTACK(Cdr(b_list));
4233 subdir_diff(item,Car(b_list),logical,previous,&STACK_2);
4234 /* call directory_diff_ab() recursively, with extended previous: */
4235 while (mconsp(STACK_2)) {
4236 pushSTACK(Car(STACK_2));
4237 directory_diff_ab(STACK_(1+1),STACK_(0+1),logical,&STACK_0,solutions);
4238 skipSTACK(1);
4239 STACK_2 = Cdr(STACK_2);
4240 }
4241 skipSTACK(3);
4242 } else {
4243 pushSTACK(b_list); /* b_start_list := b_list */
4244 while (1) {
4245 /* to reduce consing, intercept cases when directory_diff_ab()
4246 does nothing: */
4247 if (atomp(m_list)
4248 ? atomp(b_list)
4249 : (eq(Car(m_list),S(Kwild_inferiors)) || !atomp(b_list))) {
4250 /* call directory_diff_ab() recursively, with extended previous: */
4251 pushSTACK(m_list); pushSTACK(b_list);
4252 pushSTACK(STACK_2); pushSTACK(b_list);
4253 funcall(L(ldiff),2); /* (LDIFF b_start_list b_list) */
4254 pushSTACK(value1);
4255 { /* (:DIRECTORY subdir1 ... subdirn) */
4256 var object new_piece = allocate_cons();
4257 Car(new_piece) = S(Kdirectory); Cdr(new_piece) = STACK_0;
4258 STACK_0 = new_piece;
4259 }
4260 {
4261 var object new_cons = allocate_cons();
4262 Car(new_cons) = STACK_0; Cdr(new_cons) = *previous;
4263 STACK_0 = new_cons; /* (CONS ... previous) */
4264 directory_diff_ab(STACK_2,STACK_1,logical,&STACK_0,solutions);
4265 skipSTACK(1);
4266 b_list = popSTACK(); m_list = popSTACK();
4267 }
4268 }
4269 if (atomp(b_list))
4270 break;
4271 b_list = Cdr(b_list);
4272 }
4273 skipSTACK(1);
4274 }
4275 }
directory_diff(object pattern,object sample,bool logical,const gcv_object_t * previous,gcv_object_t * solutions)4276 local maygc void directory_diff (object pattern, object sample, bool logical,
4277 const gcv_object_t* previous,
4278 gcv_object_t* solutions) {
4279 DEBUG_DIFF(directory_diff);
4280 if (missingp(sample)) { push_solution_with(pattern); return; }
4281 if (directory_trivial_p(pattern)) { /* compare with directory_default */
4282 /* Augment the solution with the sample list - starting
4283 with :ABSOLUTE or :RELATIVE, it will not fit for "**". */
4284 push_solution_with(sample);
4285 return;
4286 }
4287 /* compare startpoint: */
4288 if (!eq(Car(pattern),Car(sample)))
4289 return;
4290 pattern = Cdr(pattern); sample = Cdr(sample);
4291 /* compare subdirs: */
4292 directory_diff_ab(pattern,sample,logical,previous,solutions);
4293 }
nametype_diff(object pattern,object sample,bool logical,const gcv_object_t * previous,gcv_object_t * solutions)4294 local maygc void nametype_diff (object pattern, object sample, bool logical,
4295 const gcv_object_t* previous,
4296 gcv_object_t* solutions) {
4297 DEBUG_DIFF(nametype_diff);
4298 if (!boundp(sample)) { push_solution_with(pattern); return; }
4299 if (nullp(pattern)) {
4300 var object string = wild2string(sample);
4301 push_solution_with(string);
4302 return;
4303 }
4304 nametype_diff_aux(pattern,sample,logical,previous,solutions);
4305 }
version_diff(object pattern,object sample,bool logical,const gcv_object_t * previous,gcv_object_t * solutions)4306 local maygc void version_diff (object pattern, object sample, bool logical,
4307 const gcv_object_t* previous, gcv_object_t* solutions)
4308 { unused(logical);
4309 DEBUG_DIFF(version_diff);
4310 if (!boundp(sample)) { push_solution_with(pattern); return; }
4311 if (nullp(pattern) || eq(pattern,S(Kwild))) {
4312 push_solution_with(sample);
4313 return;
4314 }
4315 if (eq(sample,S(Kwild))) return;
4316 if (!eql(pattern,sample)) return;
4317 push_solution();
4318 }
4319
4320 #undef push_solution_with
4321 #undef push_solution
4322 #undef DEBUG_DIFF
4323
4324 /* Each substitution is a list of Normal-Simple-Strings or Lists.
4325 (The Lists come into being with :WILD-INFERIORS in directory_diff().)
4326 A Normal-Simple-String fits only with '?' or '*' or :WILD,
4327 A List fits only with :WILD-INFERIORS. */
4328
4329 /* On insertion of pieces of normal pathnames in logical pathnames:
4330 Conversion to capital letters.
4331 logical_case(string)
4332 > string: Normal-Simple-String or Symbol/Number
4333 < result: converted Normal-Simple-String or the same Symbol/Number
4334 can trigger GC */
logical_case(object string)4335 local maygc object logical_case (object string) {
4336 if (!simple_string_p(string))
4337 return string;
4338 return string_upcase(string);
4339 }
4340 /* The same, recursive like with SUBST: */
subst_logical_case(object obj)4341 local maygc object subst_logical_case (object obj) {
4342 SUBST_RECURSE(logical_case(obj),subst_logical_case);
4343 }
4344
4345 /* On insertion of pieces of logical pathnames in normal pathnames:
4346 Conversion to capital letters.
4347 customary_case(string)
4348 > string: Normal-Simple-String or Symbol/Number
4349 < result: converted Normal-Simple-String or the same Symbol/Number
4350 can trigger GC */
customary_case(object string)4351 local maygc object customary_case (object string) {
4352 if (!simple_string_p(string))
4353 return string;
4354 #if defined(PATHNAME_UNIX) || defined(PATHNAME_WIN32)
4355 /* operating system with preference for lowercase letters */
4356 return string_downcase(string);
4357 #endif
4358 }
4359 /* The same, recursive like with SUBST: */
subst_customary_case(object obj)4360 local maygc object subst_customary_case (object obj) {
4361 SUBST_RECURSE(customary_case(obj),subst_customary_case);
4362 }
4363
4364 #undef SUBST_RECURSE
4365
4366 /* Apply substitution SUBST to the PATTERN.
4367 translate_pathname(&subst,pattern) */
4368 local object translate_pathname (object* subst, object pattern);
4369 /* Pop the CAR of *subst and return it. */
4370 #define RET_POP(subst) \
4371 { var object ret = Car(*subst); *subst = Cdr(*subst); return ret; }
4372 /* is the value trivial enough to ensure a trivial action? */
4373 #define TRIVIAL_P(val) (simple_string_p(val)||nullp(val))
4374 /* is the value simple enough to ensure a simple action? */
4375 #define SIMPLE_P(val) (TRIVIAL_P(val)||eq(val,S(Kwild)))
4376 /* translate_host(&subst,pattern,logical) etc.
4377 returns the appropriate replacement for host etc.; shortens subst;
4378 returns nullobj on failure
4379 can trigger GC */
4380 local maygc object translate_host (gcv_object_t* subst, object pattern,
4381 bool logical);
4382 local maygc object translate_device (gcv_object_t* subst, object pattern,
4383 bool logical);
4384 local maygc object translate_subdir (gcv_object_t* subst, object pattern,
4385 bool logical);
4386 local maygc object translate_directory (gcv_object_t* subst, object pattern,
4387 bool logical);
4388 local maygc object translate_nametype (gcv_object_t* subst, object pattern,
4389 bool logical);
4390 local maygc object translate_version (gcv_object_t* subst, object pattern,
4391 bool logical);
4392 #if DEBUG_TRANSLATE_PATHNAME
4393 /* all arguments to translate_* should be on stack - this should be safe */
4394 #define DEBUG_TRAN(f) \
4395 printf("\n* " #f " [logical: %d]\n",logical); \
4396 DOUT("",*subst); DOUT("",pattern)
4397 #else
4398 #define DEBUG_TRAN(f)
4399 #endif
translate_host(gcv_object_t * subst,object pattern,bool logical)4400 local maygc object translate_host (gcv_object_t* subst, object pattern,
4401 bool logical) {
4402 DEBUG_TRAN(translate_host);
4403 #define TRAN_HOST(subst,pattern) \
4404 if (nullp(pattern) && mconsp(*subst)) { \
4405 if (TRIVIAL_P(Car(*subst))) { \
4406 RET_POP(subst); \
4407 } else if (eq(Car(*subst),S(Khost))) { \
4408 *subst = Cdr(*subst); \
4409 return pattern; \
4410 } else \
4411 return nullobj; \
4412 }
4413 if (logical) {
4414 TRAN_HOST(subst,pattern);
4415 } else {
4416 #if HAS_HOST
4417 TRAN_HOST(subst,pattern);
4418 #endif
4419 }
4420 #if HAS_HOST
4421 if (eq(Car(*subst),S(Khost)))
4422 *subst = Cdr(*subst);
4423 #endif
4424 return pattern;
4425 #undef TRAN_HOST
4426 }
translate_device(gcv_object_t * subst,object pattern,bool logical)4427 local maygc object translate_device (gcv_object_t* subst, object pattern,
4428 bool logical) {
4429 DEBUG_TRAN(translate_device);
4430 #if HAS_DEVICE
4431 if (logical) {
4432 if (eq(Car(*subst),S(Kdevice)))
4433 { *subst = Cdr(*subst); }
4434 return pattern;
4435 }
4436 #ifdef PATHNAME_WIN32
4437 if (nullp(pattern) && mconsp(*subst))
4438 #else
4439 if ((nullp(pattern) || eq(pattern,S(Kwild))) && mconsp(*subst))
4440 #endif
4441 {
4442 if (TRIVIAL_P(Car(*subst))) {
4443 RET_POP(subst);
4444 } else if (eq(Car(*subst),S(Kdevice))) {
4445 *subst = Cdr(*subst);
4446 return pattern;
4447 } else
4448 return nullobj;
4449 }
4450 if (eq(Car(*subst),S(Kdevice)))
4451 *subst = Cdr(*subst);
4452 #else /* HAS_DEVICE */
4453 unused(subst); unused(logical);
4454 #endif
4455 return pattern;
4456 }
translate_nametype_aux(gcv_object_t * subst,object pattern,bool logical)4457 local maygc object translate_nametype_aux (gcv_object_t* subst, object pattern,
4458 bool logical) {
4459 DEBUG_TRAN(translate_nametype_aux);
4460 if (eq(pattern,S(Kwild)) && mconsp(*subst)) {
4461 if (TRIVIAL_P(Car(*subst))) {
4462 var object erg = Car(*subst); *subst = Cdr(*subst);
4463 return erg;
4464 } else
4465 return nullobj;
4466 }
4467 if (simple_string_p(pattern)) {
4468 pushSTACK(pattern); /* save pattern */
4469 var gcv_object_t* pattern_ = &STACK_0;
4470 var uintL len = Sstring_length(pattern);
4471 var uintL index = 0;
4472 var uintL stringcount = 0; /* number of strings on the stack */
4473 while (1) {
4474 var uintL last_index = index;
4475 var chart cc;
4476 /* search next wildcard-character: */
4477 pattern = *pattern_;
4478 while (index != len) {
4479 cc = schar(pattern,index);
4480 if ((multiwild_char_p(cc) /* wildcard for arbitrary many characters */
4481 || (!logical && singlewild_char_p(cc))) /* wildcard for exactly one character */
4482 && mconsp(*subst))
4483 break;
4484 index++;
4485 }
4486 /* Next (SUBSTRING pattern last_index index) on the stack: */
4487 pushSTACK(subsstring(pattern,last_index,index));
4488 stringcount++;
4489 /* finished? */
4490 if (index == len)
4491 break;
4492 /* replace wildcard: */
4493 if (TRIVIAL_P(Car(*subst))) {
4494 var object s = Car(*subst);
4495 pushSTACK(nullp(s) ? (object)O(empty_string) : s);
4496 *subst = Cdr(*subst); stringcount++;
4497 } else {
4498 skipSTACK(stringcount+1); return nullobj;
4499 }
4500 index++;
4501 }
4502 value1 = string_concat(stringcount);
4503 skipSTACK(1); /* skip pattern */
4504 return value1;
4505 }
4506 return pattern;
4507 }
translate_subdir(gcv_object_t * subst,object pattern,bool logical)4508 local maygc object translate_subdir (gcv_object_t* subst, object pattern,
4509 bool logical) {
4510 DEBUG_TRAN(translate_subdir);
4511 return translate_nametype_aux(subst,pattern,logical);
4512 }
translate_directory(gcv_object_t * subst,object pattern,bool logical)4513 local maygc object translate_directory (gcv_object_t* subst, object pattern,
4514 bool logical) {
4515 DEBUG_TRAN(translate_directory);
4516 /* compare pattern with directory_default: */
4517 if (nullp(pattern) && mconsp(*subst)) {
4518 var object list = Car(*subst); *subst = Cdr(*subst);
4519 return listp(list) ? copy_list(list) : nullobj;
4520 }
4521 /* if subst is :relative while pattern is :absolute,
4522 nothing is to be done */
4523 if (eq(Car(pattern),S(Kabsolute)) && mconsp(*subst)
4524 && directory_trivial_p(Car(*subst))) {
4525 *subst = Cdr(*subst);
4526 return copy_list(pattern);
4527 }
4528 var uintL itemcount = 0; /* number of items on the stack */
4529 /* Startpoint: */
4530 pushSTACK(Car(pattern)); pattern = Cdr(pattern); itemcount++;
4531 /* subdirs: */
4532 while (consp(pattern)) {
4533 var object item = Car(pattern);
4534 pattern = Cdr(pattern);
4535 if (eq(item,S(Kwild_inferiors))) {
4536 if (mconsp(*subst)) {
4537 if (consp(Car(*subst)) && eq(Car(Car(*subst)),S(Kdirectory))) {
4538 var object list = Cdr(Car(*subst)); *subst = Cdr(*subst);
4539 while (consp(list)) {
4540 pushSTACK(Car(list)); list = Cdr(list); itemcount++;
4541 }
4542 } else {
4543 skipSTACK(itemcount); return nullobj;
4544 }
4545 } else {
4546 pushSTACK(item); itemcount++;
4547 }
4548 } else {
4549 pushSTACK(pattern); /* save pattern */
4550 item = translate_subdir(subst,item,logical);
4551 if (eq(item,nullobj)) { skipSTACK(itemcount+1); return nullobj; }
4552 pattern = STACK_0; STACK_0 = item; itemcount++;
4553 }
4554 }
4555 return listof(itemcount);
4556 }
translate_nametype(gcv_object_t * subst,object pattern,bool logical)4557 local maygc object translate_nametype (gcv_object_t* subst, object pattern,
4558 bool logical) {
4559 DEBUG_TRAN(translate_nametype);
4560 if (nullp(pattern) && mconsp(*subst)) {
4561 if (SIMPLE_P(Car(*subst))) {
4562 RET_POP(subst);
4563 } else
4564 return nullobj;
4565 }
4566 return translate_nametype_aux(subst,pattern,logical);
4567 }
translate_version(gcv_object_t * subst,object pattern,bool logical)4568 local object translate_version (gcv_object_t* subst, object pattern,
4569 bool logical)
4570 { unused(logical);
4571 DEBUG_TRAN(translate_version);
4572 if ((nullp(pattern) || eq(pattern,S(Kwild))) && mconsp(*subst)) {
4573 var object erg = Car(*subst);
4574 if (nullp(erg) || integerp(erg)
4575 || eq(erg,S(Kwild)) || eq(erg,S(Knewest))) {
4576 *subst = Cdr(*subst);
4577 return erg;
4578 } else
4579 return nullobj;
4580 }
4581 return pattern;
4582 }
4583 #undef SIMPLE_P
4584 #undef TRIVIAL_P
4585 #undef RET_POP
4586 #undef DEBUG_TRAN
translate_pathname(gcv_object_t * subst,object pattern)4587 local maygc object translate_pathname (gcv_object_t* subst, object pattern) {
4588 var bool logical = false;
4589 var object item;
4590 pushSTACK(*subst); /* save subst for the error message */
4591 pushSTACK(pattern);
4592 if (logpathnamep(pattern))
4593 logical = true;
4594 #define GET_ITEM(what,xwhat,where,skip) do { \
4595 item = translate_##what(subst,xpathname_##xwhat(logical,where),logical); \
4596 if (eq(item,nullobj)) { skipSTACK(skip); goto subst_error; } \
4597 DOUT(#what " > ",item); pushSTACK(S(K##xwhat)); pushSTACK(item); \
4598 } while(0)
4599 #define GET_ITEM_S(y,x,w) GET_ITEM(y,x,STACK_(w),w)
4600 /* build together arguments for MAKE-PATHNAME: */
4601 GET_ITEM(host,host,pattern,0);
4602 #if HAS_DEVICE
4603 GET_ITEM_S(device,device,2);
4604 #endif
4605 GET_ITEM_S(directory,directory,2+2*HAS_DEVICE);
4606 GET_ITEM_S(nametype,name,2+2*HAS_DEVICE+2);
4607 GET_ITEM_S(nametype,type,2+2*HAS_DEVICE+4);
4608 GET_ITEM_S(version,version,2+2*HAS_DEVICE+6);
4609 /* All replacement pieces must be consumed! */
4610 if (mconsp(*subst)) { skipSTACK(2+2*HAS_DEVICE+8); goto subst_error; }
4611 /* call (MAKE-PATHNAME ...) resp. (SYS::MAKE-LOGICAL-PATHNAME ...) : */
4612 if (logical)
4613 funcall(L(make_logical_pathname),2+2*HAS_DEVICE+8);
4614 else funcall(L(make_pathname),2+2*HAS_DEVICE+8);
4615 skipSTACK(2);
4616 return value1;
4617 subst_error: /* Error because of nullobj. */
4618 /* stack layout: subst, pattern. */
4619 pushSTACK(STACK_1);
4620 pushSTACK(S(translate_pathname));
4621 error(error_condition,GETTEXT("~S: replacement pieces ~S do not fit into ~S"));
4622 }
4623 #undef GET_ITEM
4624 #undef GET_ITEM_S
4625
4626 /* (TRANSLATE-PATHNAME sample pattern1 pattern2 [:all] [:merge] [:absolute]),
4627 CLtL2 p. 624
4628 :absolute = T --> convert the resulting pathnames to absolute
4629 :all = T --> return a list of all fitting pathnames
4630 :all = NIL --> Error, if more than one pathname fits
4631 :merge = NIL --> skip last MERGE-PATHNAMES step */
4632 LISPFUN(translate_pathname,seclass_default,3,0,norest,key,3,
4633 (kw(all),kw(merge),kw(absolute)))
4634 { /* stack layout: sample, pattern1, pattern2, all, merge, absolute. */
4635 var bool absolute_p = !missingp(STACK_0);
4636 var bool logical = false; /* sample and pattern are logical pathnames */
4637 var bool logical2 = false; /* pattern2 is a logical pathname */
4638 skipSTACK(1); /* drop absolute */
4639 STACK_4 = coerce_xpathname(STACK_4);
4640 STACK_3 = coerce_xpathname(STACK_3);
4641 STACK_2 = coerce_xpathname(STACK_2);
4642 if (logpathnamep(STACK_4) && logpathnamep(STACK_3)) {
4643 logical = true;
4644 } else {
4645 /* not both logical pathnames -> first convert into normal pathnames: */
4646 STACK_4 = coerce_pathname(STACK_4);
4647 STACK_3 = coerce_pathname(STACK_3);
4648 }
4649 if (logpathnamep(STACK_2))
4650 logical2 = true;
4651 /* 1. step: construct list of all fitting substitutions. */
4652 pushSTACK(NIL); pushSTACK(NIL);
4653 host_diff(xpathname_host(logical,STACK_(3+2)),
4654 xpathname_host(logical,STACK_(4+2)),
4655 logical,&STACK_1,&STACK_0);
4656 while (mconsp(STACK_0)) {
4657 pushSTACK(Car(STACK_0)); pushSTACK(NIL);
4658 device_diff(xpathname_device(logical,STACK_(3+4)),
4659 xpathname_device(logical,STACK_(4+4)),
4660 logical,&STACK_1,&STACK_0);
4661 while (mconsp(STACK_0)) {
4662 pushSTACK(Car(STACK_0)); pushSTACK(NIL);
4663 directory_diff(xpathname_directory(logical,STACK_(3+6)),
4664 xpathname_directory(logical,STACK_(4+6)),
4665 logical,&STACK_1,&STACK_0);
4666 while (mconsp(STACK_0)) {
4667 pushSTACK(Car(STACK_0)); pushSTACK(NIL);
4668 nametype_diff(xpathname_name(logical,STACK_(3+8)),
4669 xpathname_name(logical,STACK_(4+8)),
4670 logical,&STACK_1,&STACK_0);
4671 while (mconsp(STACK_0)) {
4672 pushSTACK(Car(STACK_0)); pushSTACK(NIL);
4673 nametype_diff(xpathname_type(logical,STACK_(3+10)),
4674 xpathname_type(logical,STACK_(4+10)),
4675 logical,&STACK_1,&STACK_0);
4676 while (mconsp(STACK_0)) {
4677 pushSTACK(Car(STACK_0));
4678 version_diff(xpathname_version(logical,STACK_(3+11)),
4679 xpathname_version(logical,STACK_(4+11)),
4680 logical,&STACK_0,&STACK_10);
4681 skipSTACK(1);
4682 STACK_0 = Cdr(STACK_0);
4683 }
4684 skipSTACK(2);
4685 STACK_0 = Cdr(STACK_0);
4686 }
4687 skipSTACK(2);
4688 STACK_0 = Cdr(STACK_0);
4689 }
4690 skipSTACK(2);
4691 STACK_0 = Cdr(STACK_0);
4692 }
4693 skipSTACK(2);
4694 STACK_0 = Cdr(STACK_0);
4695 }
4696 skipSTACK(1);
4697 /* stack layout: ..., solutions. */
4698 if (matomp(STACK_0)) {
4699 pushSTACK(STACK_(3+1));
4700 pushSTACK(STACK_(4+1+1));
4701 pushSTACK(S(translate_pathname));
4702 error(error_condition,GETTEXT("~S: ~S is not a specialization of ~S"));
4703 }
4704 /* 2.,3. step: */
4705 pushSTACK(NIL); /* pathnames := '() */
4706 while (mconsp(STACK_1)) { /* traverse solutions */
4707 var object solutions = STACK_1;
4708 STACK_1 = Cdr(solutions);
4709 { /* reverse list solution */
4710 var object solution = reverse(Car(solutions));
4711 /* 2. step: insert substitution in pattern2. */
4712 /* convert capital-/small letters suitably: */
4713 if (!logical) {
4714 if (logical2)
4715 solution = subst_logical_case(solution);
4716 } else {
4717 if (!logical2)
4718 solution = subst_customary_case(solution);
4719 }
4720 pushSTACK(solution);
4721 STACK_0 = translate_pathname(&STACK_0,STACK_(2+1+2));
4722 }
4723 /* 3. step: (MERGE-PATHNAMES modified_pattern2 sample :WILD T) */
4724 if (!nullp(STACK_(0+1+2)) /* query :MERGE-Argument */
4725 && has_some_wildcards(STACK_0)) {/*MERGE-PATHNAMES may be unnecessary*/
4726 pushSTACK(STACK_(4+1+2)); pushSTACK(unbound);
4727 pushSTACK(S(Kwild)); pushSTACK(T);
4728 funcall(L(merge_pathnames),5);
4729 pushSTACK(value1);
4730 }
4731 /* step 4: merge in default pathname */
4732 #if defined(PATHNAME_UNIX) || defined(PATHNAME_WIN32)
4733 if (absolute_p) {
4734 STACK_0 = use_default_dir(STACK_0); /* insert default-directory */
4735 /* (because Unix does not know the default-directory of LISP
4736 and Win32 is multitasking) */
4737 }
4738 #endif
4739 { /* (PUSH pathname pathnames) */
4740 var object new_cons = allocate_cons();
4741 Car(new_cons) = popSTACK(); Cdr(new_cons) = STACK_0;
4742 STACK_0 = new_cons;
4743 }
4744 }
4745 /* 4. step: (DELETE-DUPLICATES pathnames :TEST #'EQUAL) */
4746 pushSTACK(S(Ktest)); pushSTACK(L(equal));
4747 funcall(L(delete_duplicates),3);
4748 /* stack layout: ..., nil. */
4749 if (missingp(STACK_(1+1))) { /* query :ALL-Argument */
4750 if (mconsp(Cdr(value1))) {
4751 pushSTACK(value1);
4752 pushSTACK(STACK_(2+2));
4753 pushSTACK(STACK_(3+3));
4754 pushSTACK(STACK_(4+4));
4755 pushSTACK(S(translate_pathname));
4756 error(error_condition,GETTEXT("(~S ~S ~S ~S) is ambiguous: ~S"));
4757 }
4758 value1 = Car(value1);
4759 }
4760 mv_count=1;
4761 skipSTACK(5+1);
4762 }
4763
4764 /* (ABSOLUTE-PATHNAME pathname) converts pathname to a physical pathname,
4765 if necessary, and makes it absolute
4766 (using clisp's notion of default directory). */
4767 LISPFUNN(absolute_pathname,1)
4768 { VALUES1(use_default_dir(coerce_pathname(popSTACK()))); }
4769
4770 /* Converts an object into an absolute physical pathname and returns its
4771 namestring (merge in default-directory).
4772 physical_namestring(thing)
4773 > thing: an object
4774 < result: the namestring of the pathname denoted by thing
4775 can trigger GC */
physical_namestring(object thing)4776 modexp maygc object physical_namestring (object thing)
4777 { return whole_namestring(use_default_dir(coerce_pathname(thing))); }
4778
4779 /* UP: tests, if the name of a pathname is =NIL.
4780 namenullp(pathname)
4781 > pathname: non-logical pathname
4782 local bool namenullp (object pathname);
4783 local bool namenullp(pathname)
4784 { return nullp(ThePathname(pathname)->pathname_name); } */
4785 #define namenullp(path) (nullp(ThePathname(path)->pathname_name))
4786
4787 /* error, if directory does not exist
4788 > obj: pathname or (better) erroneous component */
error_dir_not_exists(object obj)4789 local _Noreturn void error_dir_not_exists (object obj) {
4790 pushSTACK(obj); /* FILE-ERROR slot PATHNAME */
4791 pushSTACK(obj); pushSTACK(TheSubr(subr_self)->name);
4792 error(file_error,GETTEXT("~S: Directory ~S does not exist"));
4793 }
4794
4795 /* error, if a file already exits
4796 > STACK_0: pathname */
error_file_exists(void)4797 local _Noreturn void error_file_exists (void) {
4798 /* STACK_0 = FILE-ERROR slot PATHNAME */
4799 pushSTACK(STACK_0); /* pathname */
4800 pushSTACK(TheSubr(subr_self)->name);
4801 error(file_error,GETTEXT("~S: File ~S already exists"));
4802 }
4803
4804 /* error, if the pathname is a directory */
error_directory(object pathname)4805 local _Noreturn void error_directory (object pathname) {
4806 pushSTACK(pathname); /* FILE-ERROR slot PATHNAME */
4807 pushSTACK(whole_namestring(pathname));
4808 pushSTACK(TheSubr(subr_self)->name);
4809 error(file_error,GETTEXT("~S: ~S names a directory, not a file"));
4810 }
4811
4812 #ifdef PATHNAME_WIN32
4813
4814 /* An "absolute pathname" is a pathname, whose device is a checked
4815 String and directory does not contain :RELATIVE, :CURRENT, :PARENT. */
4816
4817 /* UP: returns a namestring of a pathname for the operating system.
4818 OSnamestring(dir_namestring)
4819 > STACK_0: non-logical pathname
4820 > dir_namestring: directory-namestring (for DOS)
4821 < result: namestring (for DOS)
4822 can trigger GC */
OSnamestring(object dir_namestring)4823 local maygc object OSnamestring (object dir_namestring) {
4824 var uintC stringcount;
4825 pushSTACK(dir_namestring); /* Directory-Namestring as the first String */
4826 stringcount = file_namestring_parts(STACK_(0+1)); /* filename Strings */
4827 return string_concat(1+stringcount); /* concatenate */
4828 }
4829
4830 /* UP: tests, if a drive exists.
4831 > uintB drive: drive-(capital-)letter
4832 < bool result: if this drive exists and is responsive */
4833 local maygc bool good_drive (uintB drive);
4834 #ifdef WIN32_NATIVE
good_drive(uintB drive)4835 local maygc bool good_drive (uintB drive) {
4836 var char rootpath[4];
4837 var DWORD result;
4838 rootpath[0] = drive;
4839 rootpath[1] = ':';
4840 rootpath[2] = '\\';
4841 rootpath[3] = '\0';
4842 begin_blocking_system_call();
4843 result = GetDriveType(rootpath);
4844 switch (result) {
4845 case DRIVE_UNKNOWN:
4846 end_blocking_system_call();
4847 return false;
4848 case DRIVE_NO_ROOT_DIR:
4849 /* Distinguish NFS mounts from nonassigned drive letters: */
4850 result = GetFileAttributes(rootpath);
4851 end_blocking_system_call();
4852 return !(result==0xFFFFFFFF);
4853 default:
4854 end_blocking_system_call();
4855 return true;
4856 }
4857 }
4858 #if 0
4859 /* The following fails to recognize some (but not all) NFS mounts on WinNT. */
4860 local maygc bool good_drive_notsogood (uintB drive) {
4861 var DWORD drives_bitmask;
4862 begin_blocking_system_call();
4863 drives_bitmask = GetLogicalDrives();
4864 end_blocking_system_call();
4865 return ((drives_bitmask & ((DWORD)1 << (drive-'A'))) != 0);
4866 }
4867 #endif
4868 #endif /* WIN32_NATIVE */
4869
4870 /* UP: returns the current drive.
4871 < char drive: drive-(capital-)letter */
default_drive(void)4872 local maygc char default_drive (void) {
4873 #ifdef WIN32_NATIVE
4874 var DWORD path_buflen = _MAX_PATH;
4875 var char* path_buffer = (char*)alloca(path_buflen);
4876 var DWORD result;
4877 GC_SAFE_SYSTEM_CALL(result = GetCurrentDirectory(path_buflen,path_buffer));
4878 if (!result) { OS_error(); }
4879 if (result >= path_buflen) {
4880 path_buflen = result; path_buffer = (char*)alloca(path_buflen);
4881 GC_SAFE_SYSTEM_CALL(result = GetCurrentDirectory(path_buflen,path_buffer));
4882 if (!result) { OS_error(); }
4883 }
4884 if (path_buffer[1]==':') { /* local device */
4885 ASSERT(path_buffer[2]=='\\');
4886 return as_cint(up_case(as_chart(path_buffer[0])));
4887 } else if (path_buffer[0]=='\\') { /* network host */
4888 ASSERT(path_buffer[1]=='\\');
4889 return 0;
4890 } else NOTREACHED;
4891 #endif
4892 }
4893
4894 /* UP: returns the current directory on the given drive.
4895 > uintB drive: drive-(capital-)letter
4896 > object pathname: pathname (for error-reporting purposes)
4897 < result: current directory (as pathname)
4898 can trigger GC */
default_directory_of(uintB drive,object pathname)4899 local maygc object default_directory_of (uintB drive, object pathname) {
4900 /* working directory (of DOS) is the current directory: */
4901 #if defined(WIN32_NATIVE)
4902 var char currpath[4];
4903 var DWORD path_buflen = _MAX_PATH;
4904 var char* path_buffer = (char*)alloca(path_buflen+1);
4905 var char* dummy;
4906 var DWORD result;
4907 if (drive) { /* local disk */
4908 currpath[0] = drive;
4909 currpath[1] = ':';
4910 currpath[2] = '.'; /* this dot is actually not needed */
4911 currpath[3] = '\0';
4912 GC_SAFE_SYSTEM_CALL(result = GetFullPathName(currpath,path_buflen,path_buffer,&dummy));
4913 if (!result) { OS_file_error(pathname); }
4914 if (result >= path_buflen) {
4915 path_buflen = result; path_buffer = (char*)alloca(path_buflen+1);
4916 GC_SAFE_SYSTEM_CALL(result = GetFullPathName(currpath,path_buflen,path_buffer,&dummy));
4917 if (!result) { OS_file_error(pathname); }
4918 }
4919 } else { /* network path */
4920 GC_SAFE_SYSTEM_CALL(result = GetCurrentDirectory(path_buflen,path_buffer));
4921 if (!result) { OS_file_error(pathname); }
4922 if (result >= path_buflen) {
4923 path_buflen = result; path_buffer = (char*)alloca(path_buflen);
4924 GC_SAFE_SYSTEM_CALL(result = GetCurrentDirectory(path_buflen,path_buffer));
4925 if (!result) { OS_file_error(pathname); }
4926 }
4927 }
4928 { /* poss. add a '\' at the end: */
4929 var char* path_end = &path_buffer[asciz_length(path_buffer)];
4930 if (!(path_end[-1]=='\\')) { path_end[0] = '\\'; path_end[1] = '\0'; }
4931 }
4932 #else
4933 var char path_buffer[3+MAXPATHLEN]; /* cf. GETWD(3) */
4934 path_buffer[0] = drive; path_buffer[1] = ':';
4935 /* file working directory in path_buffer: */
4936 begin_blocking_system_call();
4937 getwd_of(&path_buffer[2],drive);
4938 end_blocking_system_call();
4939 #endif
4940 /* Hack by DJ (see GO32/EXPHDLR.C) and EM (see LIB/MISC/_GETCWD1.C):
4941 converts all '\' to '/' and all captial- to small letters (only cosmetics,
4942 because DOS and our PARSE-NAMESTRING also understand filenames with '/'
4943 instead of '\').
4944 convert to pathname: */
4945 return asciz_dir_to_pathname(&path_buffer[0],O(pathname_encoding));
4946 }
4947
4948 /* UP: Fills default-drive and default-directory into a pathname.
4949 use_default_dir(pathname)
4950 > pathname: non-logical pathname with Device /= :WILD
4951 < result: new absolute pathname
4952 can trigger GC */
use_default_dir(object pathname)4953 local maygc object use_default_dir (object pathname) {
4954 /* first copy the pathname: */
4955 pathname = copy_pathname(pathname);
4956 pushSTACK(pathname);
4957 /* stack layout: pathname.
4958 default for the device: */
4959 #if HAS_HOST /* PATHNAME_WIN32 */
4960 if (nullp(ThePathname(pathname)->pathname_host))
4961 #endif
4962 if (nullp(ThePathname(pathname)->pathname_device)) {
4963 /* no device specified? --- take the default-drive instead: */
4964 ThePathname(pathname)->pathname_device = O(default_drive);
4965 }
4966 { /* Default for the directory: */
4967 var object subdirs = ThePathname(pathname)->pathname_directory;
4968 /* Does pathname-directory start with :RELATIVE ? */
4969 if (nullp(subdirs) || eq(Car(subdirs),S(Krelative))) {
4970 /* yes -> replace :RELATIVE with the default-directory: */
4971 pushSTACK(consp(subdirs) ? (object)Cdr(subdirs) : NIL);
4972 #if HAS_HOST /* PATHNAME_WIN32 */
4973 if (!nullp(ThePathname(pathname)->pathname_host)) {
4974 /* We do not have the concept of a current directory on a
4975 remote host. Simply use :ABSOLUTE instead of :RELATIVE. */
4976 subdirs = allocate_cons();
4977 Car(subdirs) = S(Kabsolute);
4978 Cdr(subdirs) = popSTACK();
4979 } else
4980 #endif
4981 { /* drive does not have to be present if we start on a network path */
4982 var object drive = ThePathname(pathname)->pathname_device;
4983 if (eq(drive,S(Kwild))) check_no_wildcards(pathname); /* error */
4984 var uintB dr = nullp(drive) ? 0 : as_cint(TheSnstring(drive)->data[0]);
4985 var object default_dir = default_directory_of(dr,pathname);
4986 #if HAS_HOST /* PATHNAME_WIN32 */
4987 ThePathname(STACK_1)->pathname_host = /* replace NIL in pathname ... */
4988 ThePathname(default_dir)->pathname_host; /* ... with default */
4989 #endif
4990 /* default_dir (a Pathname) is finished.
4991 Replace :RELATIVE with default-subdirs, i.e.
4992 form (append default-subdirs (cdr subdirs))
4993 = (nreconc (reverse default-subdirs) (cdr subdirs)) */
4994 var object temp = ThePathname(default_dir)->pathname_directory;
4995 temp = reverse(temp);
4996 subdirs = nreconc(temp,popSTACK());
4997 }
4998 }
4999 /* traverse list and freshly cons up, thereby process '.\' and '..\'
5000 and '...\' (do not leave it to DOS): */
5001 pushSTACK(subdirs);
5002 pushSTACK(NIL);
5003 /* stack layout: pathname, subdir-oldlist, subdir-newlist. */
5004 while (mconsp(STACK_1)) { /* until oldlist is finished: */
5005 var object subdir = Car(STACK_1); /* next subdir */
5006 if (equal(subdir,O(dot_string))) {
5007 /* = :CURRENT -> leave newlist unchanged */
5008 } else if (equal(subdir,O(dotdot_string))) {
5009 /* = :PARENT -> shorten newlist by one: */
5010 if (matomp(Cdr(STACK_0))) { /* newlist (except for :ABSOLUTE) empty ? */
5011 /* :PARENT from "\" returns Error */
5012 pushSTACK(STACK_2); /* FILE-ERROR slot PATHNAME */
5013 pushSTACK(O(backslash_string)); /* "\\" */
5014 pushSTACK(directory_namestring(STACK_(2+2))); /* directory of pathname */
5015 error(file_error,GETTEXT("no directory ~S above ~S"));
5016 }
5017 if (eq(Car(STACK_0),S(Kwild_inferiors))) { /* newlist starts with '...\' ? */
5018 /* :PARENT from "...\" returns Error */
5019 pushSTACK(STACK_2); /* FILE-ERROR slot PATHNAME */
5020 pushSTACK(directory_namestring(STACK_(2+1))); /* directory of pathname */
5021 error(file_error, /* '"..\\" after "...\\" is inadmissible: ~' */
5022 GETTEXT("\"..\\\\\" after \"...\\\\\" is invalid: ~S"));
5023 }
5024 STACK_0 = Cdr(STACK_0);
5025 } else { /* (also if :ABSOLUTE !) */
5026 /* lengthen newlist by one: */
5027 pushSTACK(subdir);
5028 var object new_cons = allocate_cons();
5029 Car(new_cons) = popSTACK();
5030 Cdr(new_cons) = STACK_0;
5031 STACK_0 = new_cons;
5032 }
5033 STACK_1 = Cdr(STACK_1);
5034 }
5035 subdirs = nreverse(popSTACK()); /* newlist, reverse again */
5036 skipSTACK(1);
5037 /* stack layout: pathname. */
5038 ThePathname(STACK_0)->pathname_directory =
5039 simplify_directory(subdirs); /* enter into the pathname */
5040 pathname = popSTACK();
5041 }
5042 return pathname;
5043 }
5044
5045 #ifdef WIN32_NATIVE
5046
5047 /* UP: translates short name to full name
5048 > shortname: old DOS 8.3 pathname
5049 wildcards aren't allowed. "." and ".." can be used.
5050 < fullname: buffer should be not less than MAX_PATH
5051 < result: true on success */
FullName(LPCSTR shortname,LPSTR fullname)5052 static BOOL FullName (LPCSTR shortname, LPSTR fullname) {
5053 var char current[_MAX_PATH];
5054 var char * rent = current;/* current+end-device-pos, rest after X: */
5055 var int state = 1;
5056 /* states for automata reading 'rent' pathname backward:
5057 0 - end
5058 1 - beginning
5059 2 - name component
5060 3 - slash component
5061 9,11,13... slash component after dots ("..").
5062 components to be skipped = (state - 9)/2
5063 10,12,14... name components after dots.
5064 components to be skipped = (state - 10)/2; */
5065 var enum {fn_eof, fn_name, fn_dots, fn_dot, fn_slash} symbol;
5066 /* symbol at the end of 'rent':
5067 1 - generic name
5068 2 - ".."
5069 3 - "."
5070 4 - slash
5071 0 - EOF i.e. beginning of 'rent' */
5072 var int pos;
5073 var int ops = 0;/* output position */
5074 strcpy(current,shortname);
5075 /* determine the end of device part */
5076 if (((current[0] >= 'a' && current[0] <= 'z')
5077 || (current[0] >= 'A' && current[0] <= 'Z'))
5078 && current[1] == ':') {
5079 rent = current+2;
5080 } else if (current[0]=='\\' && current[1]=='\\') {
5081 int i;rent = current;
5082 /* host */
5083 rent+=2;
5084 for (i=0;i<2;i++) {/* skip host and sharename */
5085 while (*rent && !cpslashp(*rent))
5086 rent++;
5087 if (*rent) rent++; else
5088 return false;/*host and sharename don't end with slash*/
5089 }
5090 }
5091 pos = strlen(rent);
5092 do {
5093 rent[pos] = '\0';
5094 if (pos == 0) symbol = fn_eof; else
5095 if (cpslashp(rent[pos-1])) { pos--; symbol = fn_slash; } else
5096 { var int dotcount = 0;/* < 0 -> not only dots */
5097 var int wild = 0;
5098 while (pos > 0 && !cpslashp(rent[pos-1])) {
5099 if (rent[pos-1] == '.') dotcount++; else dotcount = -pos;
5100 if (rent[pos-1] == '*' || rent[pos-1] == '?') wild = 1;
5101 pos--;
5102 }
5103 if (wild) return false;
5104 if (dotcount <= 0) symbol = fn_name; else
5105 if (dotcount == 1) symbol = fn_dot; else
5106 if (dotcount == 2) symbol = fn_dots; else
5107 return false; /* too many dots */
5108 }
5109 if (state == 1 /* beginning */
5110 || state == 2 /* name component */) {
5111 switch(symbol) {
5112 case fn_dot: state = 3; break; /* slash */
5113 case fn_dots: state = 11; break; /* dots-slash */
5114 case fn_name: {
5115 var WIN32_FIND_DATA wfd;
5116 var HANDLE h = NULL;
5117 h = FindFirstFile(current,&wfd);
5118 if (h != INVALID_HANDLE_VALUE) {
5119 strrev(wfd.cFileName);
5120 if (ops > 0 || wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
5121 fullname[ops++] = '\\';
5122 strcpy(fullname+ops,wfd.cFileName);
5123 ops+=strlen(wfd.cFileName);
5124 FindClose(h);
5125 } else return false; /* file not found */
5126 state = 3;
5127 } break;
5128 case fn_slash:
5129 if (state == 1) state = 2;
5130 else return false; /* two slashes in a row */
5131 break;
5132 case fn_eof:
5133 if (state == 1 && current == rent) return false; /* D: */
5134 else state = 0;
5135 break;
5136 default:
5137 return false;/* program error */
5138 }
5139 } else if (state == 3) {/* slash */
5140 switch(symbol) {
5141 case fn_slash: state = 2;break;
5142 case fn_eof:
5143 if (current == rent) state = 0; else return false; /*D:FOO*/
5144 break;
5145 default: return false; /* program error */
5146 }
5147 } else if (state % 2 == 1) {/* dots - slash 9, 11, 13 ... */
5148 switch(symbol) {
5149 case fn_slash:
5150 state += 1;
5151 if (state == 10) state = 2; /* zero depth */
5152 break; /* same depth */
5153 case fn_eof:
5154 return false; /* too many ".." */
5155 break;
5156 default: return false; /* program error */
5157 }
5158 } else {/* dots - name 10, 12, 14, ... */
5159 switch(symbol) {
5160 case fn_dot: state -= 1; break; /* same depth */
5161 case fn_dots: state += 1; break; /* increase depth */
5162 case fn_name: state -= 3; /* decrease depth */
5163 if (state < 9) return false; /* program error */
5164 break;
5165 case fn_slash: return false; /* two slashes */
5166 case fn_eof: return false; /* too many ".."s */
5167 }
5168 }
5169 } while (state != 0);
5170 if (rent > current) fullname[ops++] = '\\';
5171 /* add device */
5172 while(rent > current)
5173 fullname[ops++] = (rent--)[-1];
5174 fullname[ops] = '\0';
5175 strrev(fullname);
5176 return true;
5177 }
5178
5179 #endif
5180
5181 /* UP: guarantees that the Directory of the Pathname exists
5182 (signals an error if it does not)
5183 assure_dir_exists(file_status,links_resolved,tolerantp)
5184 > fs->fs_pathname: absolute pathname without wildcards in directory
5185 > links_resolved: Flag, whether all links in the directory
5186 of the pathname are already resolved
5187 > tolerantp: Flag, whether an error should be avoided
5188 < fs->fs_namestring:
5189 if Name=NIL: Directory-Namestring (for DOS)
5190 if Name/=NIL: Namestring (for DOS)
5191 if tolerantp, maybe: nullobj
5192 can trigger GC */
5193 #ifdef WIN32_NATIVE
5194 struct file_status {
5195 gcv_object_t *fs_pathname; /* pointer into STACK */
5196 object fs_namestring; /* usually returned by assure_dir_exists() */
5197 DWORD fs_fileattr;
5198 };
file_status_is_dir(struct file_status * fs)5199 local inline bool file_status_is_dir (struct file_status *fs)
5200 { return fs->fs_fileattr == 0xFFFFFFFF
5201 || !(fs->fs_fileattr & FILE_ATTRIBUTE_DIRECTORY); }
file_status_init(struct file_status * fs,gcv_object_t * path)5202 local inline void file_status_init(struct file_status *fs,gcv_object_t *path) {
5203 fs->fs_pathname = path;
5204 fs->fs_namestring = nullobj;
5205 fs->fs_fileattr = 0;
5206 }
assure_dir_exists(struct file_status * fs,bool links_resolved,bool tolerantp)5207 local maygc void assure_dir_exists (struct file_status *fs,
5208 bool links_resolved, bool tolerantp) {
5209 var bool nnullp = namenullp(*(fs->fs_pathname));
5210 if (nnullp && links_resolved) {
5211 fs->fs_namestring = directory_namestring(*(fs->fs_pathname));
5212 return;
5213 }
5214 with_sstring_0(whole_namestring(*(fs->fs_pathname)),O(pathname_encoding),
5215 path, {
5216 var char resolved[MAX_PATH];
5217 var bool substitute = false;
5218 var bool error = false;
5219 begin_system_call();
5220 if (links_resolved) { /* use light function */
5221 var shell_shortcut_target_t rresolve = resolve_shell_symlink(path,resolved);
5222 if (rresolve != shell_shortcut_notresolved) {
5223 if (rresolve == shell_shortcut_notexists)
5224 error = true;
5225 else
5226 substitute = true;
5227 }
5228 } else {
5229 if (real_path(path,resolved))
5230 substitute = true;
5231 else { /* A file doesn't exist. Maybe dir does ? */
5232 error = true; /* let's be pessimistic */
5233 if (!nnullp) {
5234 var uintL lastslashpos = strlen(path) - 1;
5235 while (lastslashpos > 0 && path[lastslashpos]!=slash) lastslashpos--;
5236 if (path[lastslashpos]==slash) {
5237 path[lastslashpos + 1] = '\0'; /* leave only path without name */
5238 if (real_path(path,resolved)) {
5239 /* substitute only directory part */
5240 fs->fs_fileattr = GetFileAttributes(resolved);
5241 /* resolved to a file ? Only directories allowed
5242 - nonmaskable error */
5243 if (fs->fs_fileattr == 0xFFFFFFFF
5244 || !(fs->fs_fileattr & FILE_ATTRIBUTE_DIRECTORY))
5245 error_directory(*(fs->fs_pathname));
5246 pushSTACK(asciz_to_string(resolved,O(pathname_encoding)));
5247 /* substitute immediately - w/o substitute flag
5248 turn it into a pathname and use it with old name: */
5249 pushSTACK(coerce_pathname(STACK_0));
5250 /* save old pathname name and type components */
5251 pushSTACK(ThePathname(STACK_2)->pathname_name);
5252 pushSTACK(ThePathname(STACK_3)->pathname_type);
5253 *(fs->fs_pathname) = STACK_2;
5254 ThePathname(*(fs->fs_pathname))->pathname_name = STACK_1;
5255 ThePathname(*(fs->fs_pathname))->pathname_type = STACK_0;
5256 skipSTACK(4);
5257 error = false;
5258 }
5259 }
5260 }
5261 }
5262 }
5263 end_system_call();
5264 if (error) {
5265 if (tolerantp) {
5266 fs->fs_namestring = nullobj;
5267 return;
5268 }
5269 pushSTACK(copy_pathname(*(fs->fs_pathname)));
5270 ThePathname(STACK_0)->pathname_name = NIL;
5271 ThePathname(STACK_0)->pathname_type = NIL;
5272 error_dir_not_exists(popSTACK());
5273 }
5274 if (substitute) {
5275 var object resolved_string =
5276 asciz_to_string(resolved,O(pathname_encoding));
5277 *(fs->fs_pathname) = coerce_pathname(resolved_string);
5278 nnullp = namenullp(*(fs->fs_pathname));
5279 }
5280 });
5281 /* merge in *DEFAULT-PATHNAME-DEFAULTS* & :VERSION :NEWEST:
5282 for cross-platform consistency, either all or no versions of
5283 assure_dir_exists() must call MERGE-PATHNAMES */
5284 funcall(L(merge_pathnames),1); pushSTACK(value1);
5285 { var object dns = directory_namestring(*(fs->fs_pathname));
5286 fs->fs_namestring = nnullp ? dns : OSnamestring(dns); }
5287 }
5288 #endif
5289
5290 #endif
5291
5292 #ifdef PATHNAME_UNIX
5293
5294 /* UP: Return the current Directory.
5295 < result: current Directory (as Pathname)
5296 can trigger GC */
default_directory(void)5297 local maygc object default_directory (void) {
5298 var char path_buffer[MAXPATHLEN]; /* cf. GETWD(3) */
5299 /* store Working Directory in path_buffer: */
5300 begin_blocking_system_call();
5301 if ( getwd(&path_buffer[0]) ==NULL) {
5302 end_blocking_system_call();
5303 pushSTACK(O(dot_string)); /* FILE-ERROR slot PATHNAME */
5304 pushSTACK(asciz_to_string(&path_buffer[0],O(pathname_encoding))); /* message */
5305 error(file_error,GETTEXT("UNIX error while GETWD: ~S"));
5306 }
5307 end_blocking_system_call();
5308 /* It must start with '/' : */
5309 if (!(path_buffer[0] == '/')) {
5310 pushSTACK(O(dot_string)); /* FILE-ERROR slot PATHNAME */
5311 pushSTACK(asciz_to_string(&path_buffer[0],O(pathname_encoding)));
5312 error(file_error,GETTEXT("UNIX GETWD returned ~S"));
5313 }
5314 /* convert to pathname: */
5315 return asciz_dir_to_pathname(&path_buffer[0],O(pathname_encoding));
5316 }
5317
5318 /* UP: Fills Default-Directory into a pathname.
5319 use_default_dir(pathname)
5320 > pathname: non-logical pathname
5321 < result: new pathname, whose directory contains no :RELATIVE .
5322 (short: "absolute pathname")
5323 can trigger GC */
use_default_dir(object pathname)5324 local maygc object use_default_dir (object pathname) {
5325 /* copy the pathname first: */
5326 pathname = copy_pathname(pathname);
5327 { /* then build the default-directory into the pathname: */
5328 var object subdirs = ThePathname(pathname)->pathname_directory;
5329 /* does pathname-directory start with :RELATIVE? */
5330 if (nullp(subdirs) || eq(Car(subdirs),S(Krelative))) {
5331 /* yes -> replace :RELATIVE with default-subdirs, i.e.
5332 form (append default-subdirs (cdr subdirs))
5333 = (nreconc (reverse default-subdirs) (cdr subdirs)) */
5334 pushSTACK(pathname);
5335 pushSTACK(consp(subdirs) ? (object)Cdr(subdirs) : NIL);
5336 var object temp = default_directory();
5337 temp = ThePathname(temp)->pathname_directory;
5338 temp = reverse(temp);
5339 subdirs = nreconc(temp,popSTACK());
5340 subdirs = simplify_directory(subdirs);
5341 pathname = popSTACK();
5342 /* enter into the pathname: */
5343 ThePathname(pathname)->pathname_directory = subdirs;
5344 }
5345 }
5346 return pathname;
5347 }
5348
5349 /* UP: Assures, that the directory of a pathname exists, and thereby resolves
5350 symbolic links.
5351 assure_dir_exists(file_status, links_resolved, tolerantp)
5352 > file_status->fs_pathname: non-logical pathname,
5353 whose directory does not contain :RELATIVE.
5354 > links_resolved: Flag, if all links in the directory of the pathname
5355 are already resolved and if it is known to exist
5356 > tolerantp: flag, if an error is to be avoided
5357 < file_status->fs_pathname: (poss. the same) pathname, whereas neither for
5358 the directory nor for the Filename a symbolic link is to be tracked.
5359 < file_status->fs_namestring:
5360 if Name=NIL: directory-namestring (for UNIX, with '/' at the end)
5361 if Name/=NIL: namestring (for UNIX)
5362 if tolerantp poss.: nullobj
5363 < file_status->fs_stat_validp: if Name/=NIL:
5364 false if the file does not exist,
5365 true if it exists, in which case file_status->fs_stat contains its stats
5366 can trigger GC */
5367 struct file_status {
5368 gcv_object_t *fs_pathname; /* pointer into STACK */
5369 object fs_namestring; /* usually returned by assure_dir_exists() */
5370 bool fs_stat_validp;
5371 struct stat fs_stat;
5372 };
file_status_is_dir(struct file_status * fs)5373 local inline bool file_status_is_dir (struct file_status *fs)
5374 { return S_ISDIR(fs->fs_stat.st_mode); }
file_status_init(struct file_status * fs,gcv_object_t * path)5375 local inline void file_status_init(struct file_status *fs,gcv_object_t *path) {
5376 fs->fs_pathname = path;
5377 fs->fs_namestring = nullobj;
5378 fs->fs_stat_validp = false;
5379 }
5380
realpath_obj(object namestring,char * path_buffer)5381 local maygc char* realpath_obj (object namestring, char *path_buffer) {
5382 char* ret;
5383 with_sstring_0(namestring,O(pathname_encoding),namestring_asciz, {
5384 begin_blocking_system_call();
5385 ret = realpath(namestring_asciz,path_buffer);
5386 if (!realpath_is_my_realpath && ret == NULL && errno == ENOENT) {
5387 /* Put the nonexistent component into path_buffer. */
5388 ret = my_realpath(namestring_asciz,path_buffer);
5389 }
5390 end_blocking_system_call();
5391 });
5392 return ret;
5393 }
5394 /* return true if assure_dir_exists is done */
get_path_info(struct file_status * fs,char * namestring_asciz,uintC * allowed_links,bool tolerantp)5395 local maygc bool get_path_info (struct file_status *fs, char *namestring_asciz,
5396 uintC *allowed_links, bool tolerantp) {
5397 begin_blocking_system_call();
5398 if (!( lstat(namestring_asciz,&(fs->fs_stat)) ==0)) {
5399 if (!tolerantp && (errno!=ENOENT))
5400 { end_blocking_system_call(); OS_file_error(*(fs->fs_pathname)); }
5401 /* file does not exist. */
5402 end_blocking_system_call();
5403 fs->fs_stat_validp = false; return true;
5404 }
5405 end_blocking_system_call();
5406 /* file exists. */
5407 if (S_ISDIR(fs->fs_stat.st_mode))
5408 error_directory(*(fs->fs_pathname));
5409 else if (S_ISLNK(fs->fs_stat.st_mode)) {
5410 /* is it a symbolic link? yes -> continue resolving: */
5411 if (*allowed_links == 0) { /* no more links allowed? */
5412 /* yes -> simulate UNIX-Error ELOOP */
5413 begin_system_call();
5414 errno = ELOOP;
5415 end_system_call();
5416 OS_file_error(*(fs->fs_pathname));
5417 }
5418 --*allowed_links; /* after that, one link less is allowed */
5419 var uintL linklen = fs->fs_stat.st_size; /* presumed length of the link-content */
5420 /* Use a minimum linklen, in order to speed up things when linklen == 0. */
5421 if (linklen < 64) { linklen = 64; }
5422 retry_readlink: {
5423 var DYNAMIC_ARRAY(linkbuf,char,linklen+1); /* buffer for the Link-content */
5424 /* read link-content: */
5425 { var int result;
5426 GC_SAFE_SYSTEM_CALL(result = readlink(namestring_asciz,linkbuf,linklen));
5427 if (result<0)
5428 OS_file_error(*(fs->fs_pathname));
5429 if (result >= (int)linklen) { /* linkbuf too small - probably a link from /proc */
5430 FREE_DYNAMIC_ARRAY(linkbuf); linklen = 2*result+1; goto retry_readlink;
5431 }
5432 linklen = result;
5433 }
5434 /* when piping, /dev/fd/1 -> /proc/<pid>/fd/1 -> pipe:[<inode>]
5435 on a terminal, /dev/fd/1 -> /proc/<pid>/fd/1 -> /dev/pts/<terminal> */
5436 if (asciz_startswith(namestring_asciz,"/proc/") && linkbuf[0] != '/') {
5437 /* ignore local links in /proc */
5438 FREE_DYNAMIC_ARRAY(linkbuf);
5439 return ((fs->fs_stat_validp = true)); /* done */
5440 }
5441 /* turn it into a pathname:
5442 (MERGE-PATHNAMES (PARSE-NAMESTRING linkbuf) pathname-without-name&type) */
5443 pushSTACK(n_char_to_string(linkbuf,linklen,O(pathname_encoding)));
5444 FREE_DYNAMIC_ARRAY(linkbuf);
5445 }
5446 funcall(L(parse_namestring),1);
5447 pushSTACK(value1);
5448 var object pathname = copy_pathname(*(fs->fs_pathname));
5449 ThePathname(pathname)->pathname_name = NIL;
5450 ThePathname(pathname)->pathname_type = NIL;
5451 pushSTACK(pathname);
5452 funcall(L(merge_pathnames),2);
5453 *(fs->fs_pathname) = value1;
5454 } else /* normal file */
5455 return ((fs->fs_stat_validp = true));
5456 return false;
5457 }
assure_dir_exists(struct file_status * fs,bool links_resolved,bool tolerantp)5458 local maygc void assure_dir_exists (struct file_status *fs,
5459 bool links_resolved, bool tolerantp) {
5460 var uintC allowed_links = MAXSYMLINKS; /* number of allowed symbolic links */
5461 if (links_resolved)
5462 goto dir_exists;
5463 while (1) { /* loop over the symbolic links to be resolved */
5464 { /* determine Truepath of the directory: */
5465 var char path_buffer[MAXPATHLEN]; /* cf. REALPATH(3) */
5466 {
5467 var object pathname = *(fs->fs_pathname);
5468 var uintC stringcount = /* host and directory strings */
5469 directory_namestring_parts(pathname);
5470 pushSTACK(O(dot_string)); /* and "." */
5471 var object string = string_concat(stringcount+1); /* concatenate */
5472 /* resolve symbolic links therein: */
5473 if (realpath_obj(string,path_buffer) == NULL) {
5474 if (errno!=ENOENT) { OS_file_error(*(fs->fs_pathname)); }
5475 if (!tolerantp)
5476 error_dir_not_exists(asciz_dir_to_pathname(path_buffer,O(pathname_encoding))); /* erroneous component */
5477 fs->fs_namestring = nullobj; return;
5478 }
5479 }
5480 /* new Directory-Path must start with '/' : */
5481 if (!(path_buffer[0] == '/')) {
5482 pushSTACK(*(fs->fs_pathname)); /* FILE-ERROR slot PATHNAME */
5483 pushSTACK(asciz_to_string(&path_buffer[0],O(pathname_encoding)));
5484 error(file_error,GETTEXT("UNIX REALPATH returned ~S"));
5485 }
5486 /* possibly add a '/' at the end: */
5487 var char* pathptr = &path_buffer[0];
5488 var uintL len = 0; /* string-length */
5489 while (*pathptr != 0) { pathptr++; len++; } /* search ASCIZ-string-end */
5490 if (!((len>0) && (pathptr[-1]=='/'))) {
5491 *pathptr = '/'; len++; /* add a '/' */
5492 }
5493 /* and convert to a string: */
5494 var object new_string =
5495 n_char_to_string(&path_buffer[0],len,O(pathname_encoding));
5496 /* turn it into a pathname and use its Directory: */
5497 var object new_pathname = coerce_pathname(new_string);
5498 ThePathname(*(fs->fs_pathname))->pathname_directory
5499 = ThePathname(new_pathname)->pathname_directory;
5500 }
5501 dir_exists:
5502 pushSTACK(*(fs->fs_pathname));
5503 funcall(L(merge_pathnames),1);
5504 *(fs->fs_pathname) = value1;
5505 /* get information for the addressed file: */
5506 if (namenullp(*(fs->fs_pathname))) { /* no file addressed? */
5507 fs->fs_namestring = directory_namestring(*(fs->fs_pathname));
5508 return; /* yes -> finished */
5509 }
5510 fs->fs_namestring = whole_namestring(*(fs->fs_pathname)); /* concat */
5511 /* get information: */
5512 pushSTACK(fs->fs_namestring); /* save for get_path_info() */
5513 var bool done;
5514 with_sstring_0(fs->fs_namestring,O(pathname_encoding),namestring_asciz, {
5515 done = get_path_info(fs,namestring_asciz,&allowed_links,tolerantp);
5516 });
5517 fs->fs_namestring = popSTACK(); /* restore */
5518 if (done) return;
5519 }
5520 }
5521
5522 #endif
5523
5524 #ifdef PATHNAME_WIN32
5525 #if 0 /* unused */
5526 /* UP: Turns a directory-namestring into one, that is suitably for DOS.
5527 OSdirnamestring(namestring)
5528 > namestring: newly created directory-namestring, with '\' at the end,
5529 a normal-simple-string
5530 < result: namestring for this directory, in DOS-Format: last '\'
5531 discarded, if superfluous, a normal-simple-string
5532 can trigger GC */
5533 local maygc object OSdirnamestring (object namestring) {
5534 var uintL len = Sstring_length(namestring);
5535 if (len==0) goto ok; /* empty string -> do not discard anything */
5536 var chart ch = TheSnstring(namestring)->data[len-1];
5537 if (!chareq(ch,ascii('\\'))) /* no '\' at the end -> do not discard */
5538 goto ok;
5539 if (len==1) goto ok; /* "\" means Root -> do not discard */
5540 ch = TheSnstring(namestring)->data[len-2];
5541 if (chareq(ch,ascii('\\')) || colonp(ch)) /* '\' or ':' before it */
5542 goto ok; /* -> means parent -> do not discard */
5543 /* discard '\' at the end: */
5544 namestring = subsstring(namestring,0,len-1);
5545 ok: /* do not discard anything */
5546 return namestring;
5547 }
5548 #endif
5549 /* UP: Changes the default-drive and its default-directory.
5550 change_default();
5551 > STACK_0: absolute pathname, whose device is a string and directory
5552 contains no :RELATIVE, :CURRENT, :PARENT, and name and type are =NIL.
5553 can trigger GC */
change_default(void)5554 local maygc void change_default (void) {
5555 { /* change default-directory for this drive: */
5556 var object pathname = STACK_0;
5557 var uintC stringcount = directory_namestring_parts(pathname);
5558 /* no redundant '\' at the end */
5559 if (mconsp(Cdr(ThePathname(pathname)->pathname_directory))) {
5560 skipSTACK(1); stringcount--;
5561 }
5562 var object string = string_concat(stringcount); /* concatenate */
5563 with_sstring_0(string,O(pathname_encoding),asciz, {
5564 /* change default-directory: */
5565 change_current_directory(asciz);
5566 });
5567 }
5568 /* change default-drive: */
5569 O(default_drive) = ThePathname(STACK_0)->pathname_device;
5570 /* set *DEFAULT-PATHNAME-DEFAULTS* : */
5571 recalc_defaults_pathname();
5572 }
5573 #endif
5574 #ifdef PATHNAME_UNIX
5575 /* UP: changes the default-directory.
5576 change_default();
5577 > STACK_0: absolute pathname, whose directory contains no :RELATIVE,
5578 :CURRENT, :PARENT , and name and Type are =NIL.
5579 can trigger GC */
change_default(void)5580 local maygc void change_default (void) {
5581 var object string = directory_namestring(STACK_0);
5582 with_sstring_0(string,O(pathname_encoding),asciz, {
5583 /* change default-directory: */
5584 begin_blocking_system_call();
5585 if (!( chdir(asciz) ==0)) {
5586 end_blocking_system_call();
5587 OS_file_error(STACK_0);
5588 }
5589 end_blocking_system_call();
5590 });
5591 }
5592 #endif
5593
5594 LISPFUNNR(namestring,1) { /* (NAMESTRING pathname), CLTL p. 417 */
5595 var object pathname = coerce_xpathname(popSTACK());
5596 VALUES1(whole_namestring(pathname));
5597 }
5598
5599 /* error-message because of missing file name
5600 error_noname(pathname);
5601 > pathname: pathname */
error_noname(object pathname)5602 local _Noreturn void error_noname (object pathname) {
5603 pushSTACK(pathname); /* FILE-ERROR slot PATHNAME */
5604 pushSTACK(pathname); pushSTACK(TheSubr(subr_self)->name);
5605 error(file_error,GETTEXT("~S: No file name given: ~S"));
5606 }
5607 #define check_noname(pathname) \
5608 do { if (namenullp(pathname)) { error_noname(pathname); } } while(0)
5609
5610 /* error-message because of illegal Name/Type-specification
5611 error_notdir(pathname);
5612 > pathname: pathname */
error_notdir(object pathname)5613 local _Noreturn void error_notdir (object pathname) {
5614 pushSTACK(pathname); /* FILE-ERROR slot PATHNAME */
5615 pushSTACK(pathname); pushSTACK(TheSubr(subr_self)->name);
5616 error(file_error,GETTEXT("~S: Not a directory: ~S"));
5617 }
5618 #define check_notdir(pathname) \
5619 do { if (!(nullp(ThePathname(pathname)->pathname_name) \
5620 && nullp(ThePathname(pathname)->pathname_type))) \
5621 error_notdir(pathname); } while(0)
5622
5623 /* test, if a file exists:
5624 file_exists(file_status)
5625 > only after: assure_dir_exists() */
5626 #if defined(WIN32_NATIVE)
access0(const char * path,struct file_status * fs)5627 local /* maygc */ inline int access0 (const char* path, struct file_status *fs) {
5628 GCTRIGGER1(fs->fs_namestring);
5629 GC_SAFE_SYSTEM_CALL(fs->fs_fileattr = GetFileAttributes(path));
5630 if (fs->fs_fileattr == 0xFFFFFFFF) {
5631 if (WIN32_ERROR_NOT_FOUND) {
5632 return -1;
5633 }
5634 OS_file_error(*(fs->fs_pathname));
5635 }
5636 return 0;
5637 }
file_exists(struct file_status * fs)5638 local /* maygc */ bool file_exists (struct file_status *fs) {
5639 GCTRIGGER1(fs->fs_namestring);
5640 var bool exists;
5641 with_sstring_0(fs->fs_namestring,O(pathname_encoding),namestring_asciz, {
5642 exists = (access0(namestring_asciz,fs)==0);
5643 });
5644 return exists;
5645 }
5646 #elif defined(UNIX)
5647 #define file_exists(fs) ((fs)->fs_stat_validp)
5648 #define FILE_EXISTS_TRIVIAL
5649 #else
5650 #error file_exists is not defined
5651 #endif
5652
5653 /* error-message because of non-existent file
5654 error_file_not_exists();
5655 > STACK_0: pathname */
error_file_not_exists(void)5656 local _Noreturn void error_file_not_exists (void) {
5657 /* STACK_0 = FILE-ERROR slot PATHNAME */
5658 pushSTACK(STACK_0); /* pathname */
5659 pushSTACK(TheSubr(subr_self)->name);
5660 error(file_error,GETTEXT("~S: File ~S does not exist"));
5661 }
5662
5663 /* TRUENAME for a pathname
5664 set fs->fs_pathname to the truename (filename for the operating system)
5665 or nullobj
5666 can trigger GC */
true_namestring(struct file_status * fs,bool noname_p,bool tolerantp)5667 local maygc void true_namestring (struct file_status *fs, bool noname_p,
5668 bool tolerantp) {
5669 check_no_wildcards(*fs->fs_pathname); /* with wildcards -> error */
5670 *(fs->fs_pathname) = use_default_dir(*(fs->fs_pathname)); /* insert default-directory */
5671 if (noname_p) check_noname(*(fs->fs_pathname));
5672 assure_dir_exists(fs,false,tolerantp);
5673 }
5674
5675 LISPFUNNS(truename,1)
5676 { /* (TRUENAME pathname), CLTL p. 413 */
5677 var object pathname = STACK_0; /* pathname-argument */
5678 if (builtin_stream_p(pathname)) { /* stream -> treat extra: */
5679 /* must be file-stream: */
5680 pathname = as_file_stream(pathname);
5681 /* Streamtype File-Stream */
5682 pathname = file_stream_truename(pathname);
5683 VALUES1(pathname);
5684 } else {
5685 var struct file_status fs; file_status_init(&fs,&STACK_0);
5686 *(fs.fs_pathname) = merge_defaults(coerce_pathname(pathname));
5687 true_namestring(&fs,false,false);
5688 if (namenullp(*(fs.fs_pathname))) { /* no name specified */
5689 if (!nullp(ThePathname(*(fs.fs_pathname))->pathname_type)) {
5690 pushSTACK(*(fs.fs_pathname)); /* FILE-ERROR slot PATHNAME */
5691 pushSTACK(STACK_0); /* pathname */
5692 pushSTACK(TheSubr(subr_self)->name);
5693 error(file_error,GETTEXT("~S: pathname with type but without name makes no sense: ~S"));
5694 }
5695 /* no name and no type specified -> pathname as result */
5696 } else {
5697 /* name specified.
5698 check, if the file exists: */
5699 if (!file_exists(&fs)) { error_file_not_exists(); }
5700 /* file exists -> pathname as value */
5701 }
5702 VALUES1(*(fs.fs_pathname));
5703 }
5704 skipSTACK(1);
5705 }
5706
5707 /* Probe filename referred to by the stream
5708 > stream : a built-in stream
5709 < stream : its strm_file_truename (or error is not a file stream)
5710 < true if the stream was open and thus no further checks are necessary */
probe_path_from_stream(gcv_object_t * stream)5711 local bool probe_path_from_stream (gcv_object_t *stream) {
5712 /* must be file-stream: */
5713 *stream = as_file_stream(*stream);
5714 /* streamtype file-stream -> take truename: */
5715 var uintB flags = TheStream(*stream)->strmflags;
5716 *stream = file_stream_truename(*stream);
5717 return flags & strmflags_open_B;
5718 }
5719
5720 LISPFUNNS(probe_file,1)
5721 { /* (PROBE-FILE filename), CLTL p. 424 */
5722 if (builtin_stream_p(STACK_0)) { /* stream -> treat extra: */
5723 if (probe_path_from_stream(&STACK_0))
5724 { VALUES1(popSTACK()); return; }
5725 } else /* turn into a pathname */
5726 STACK_0 = merge_defaults(coerce_pathname(STACK_0));
5727 /* STACK_0 is a pathname */
5728 var struct file_status fs; file_status_init(&fs,&STACK_0);
5729 true_namestring(&fs,true,true);
5730 if (eq(fs.fs_namestring,nullobj)) {
5731 /* path to the file does not exist -> NIL as value: */
5732 skipSTACK(1); VALUES1(NIL); return;
5733 }
5734 if (file_exists(&fs)) /* check, if the file exists: */
5735 VALUES1(*(fs.fs_pathname)); /* file exists -> pathname as value */
5736 else VALUES1(NIL); /* else NIL as value */
5737 skipSTACK(1);
5738 }
5739
5740 #if defined(WIN32_NATIVE)
5741 #define FIND_DATA_FWD(filedata) \
5742 ((filedata.ftLastWriteTime.dwLowDateTime == 0 \
5743 && filedata.ftLastWriteTime.dwHighDateTime == 0) \
5744 ? &(filedata.ftCreationTime) : &(filedata.ftLastWriteTime))
5745 #define FIND_DATA_FSIZE(filedata) \
5746 (((uint64)filedata.nFileSizeHigh<<32)|filedata.nFileSizeLow)
5747
5748 /* call FindFirstFile with all checks
5749 > namestring_asciz : asciz path
5750 < filedata : file data
5751 STACK_0 = FILE-ERROR slot PATHNAME */
find_first_file(const char * namestring_asciz,WIN32_FIND_DATA * filedata)5752 local void find_first_file (const char *namestring_asciz,
5753 WIN32_FIND_DATA *filedata) {
5754 var HANDLE search_handle;
5755 begin_blocking_system_call();
5756 search_handle = FindFirstFile(namestring_asciz,filedata);
5757 if (search_handle == INVALID_HANDLE_VALUE) {
5758 if (WIN32_ERROR_NOT_FOUND) {
5759 end_blocking_system_call(); error_file_not_exists();
5760 }
5761 end_blocking_system_call(); OS_file_error(STACK_0);
5762 } else if (!FindClose(search_handle)) {
5763 end_blocking_system_call(); OS_file_error(STACK_0);
5764 }
5765 end_blocking_system_call();
5766 }
5767 #endif
5768
5769 /* Check whether the file exists
5770 > namestring : path
5771 > STACK_0 = FILE-ERROR slot PATHNAME
5772 < resolved : truename (if return is success, i.e., FILE or DIR)
5773 < fwd: file write date (if return is success and address is supplied)
5774 < fsize: file size (if return is success and address is supplied)
5775 < returns: the file kind
5776 triggers GC if fwd or fsize are supplied */
classify_namestring(const char * namestring,char * resolved,gcv_object_t * fwd,gcv_object_t * fsize)5777 global /*maygc*/ file_kind_t classify_namestring
5778 (const char* namestring, char *resolved, gcv_object_t *fwd, gcv_object_t* fsize) {
5779 if (fwd || fsize) GCTRIGGER();
5780 #if defined(UNIX)
5781 var struct stat status;
5782 var int ret;
5783 GC_SAFE_SYSTEM_CALL(ret = stat(namestring,&status));
5784 if (ret) {
5785 if (errno != ENOENT && errno != ENOTDIR) return FILE_KIND_BAD;
5786 return FILE_KIND_NONE; /* does not exist */
5787 } else { /* file exists. */
5788 realpath(namestring,resolved); /* ==> success assured */
5789 if (fwd) *fwd = convert_time_to_universal(&(status.st_mtime));
5790 if (fsize) *fsize = off_to_I(status.st_size);
5791 if (S_ISDIR(status.st_mode)) return FILE_KIND_DIR;
5792 else return FILE_KIND_FILE;
5793 }
5794 #elif defined(WIN32_NATIVE)
5795 var bool ret;
5796 begin_blocking_system_call();
5797 if (real_path(namestring,resolved)) {
5798 var WIN32_FILE_ATTRIBUTE_DATA filedata;
5799 var BOOL success = GetFileAttributesEx(resolved, GetFileExInfoStandard, &filedata);
5800 end_blocking_system_call();
5801 if (success) { /* file exists. */
5802 if (fwd) *fwd = convert_time_to_universal(
5803 filedata.ftLastWriteTime.dwHighDateTime
5804 || filedata.ftLastWriteTime.dwLowDateTime
5805 ? &filedata.ftLastWriteTime : &filedata.ftCreationTime);
5806 if (fsize) *fsize = off_to_I(
5807 ((uint64)filedata.nFileSizeHigh<<32)|filedata.nFileSizeLow);
5808 if (filedata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
5809 return FILE_KIND_DIR;
5810 else return FILE_KIND_FILE;
5811 } else {
5812 /* you get ERROR_INVALID_NAME on GetFileAttributes("foo/")
5813 when file "foo" exists */
5814 if (!(WIN32_ERROR_NOT_FOUND || GetLastError() == ERROR_INVALID_NAME))
5815 return FILE_KIND_BAD;
5816 return FILE_KIND_NONE; /* does not exist */
5817 }
5818 } else { end_blocking_system_call(); return FILE_KIND_NONE; }
5819 #else
5820 #error classify_namestring is not defined
5821 #endif
5822 }
5823
5824 LISPFUN(probe_pathname,seclass_rd_sig,1,0,norest,key,1,(kw(error)))
5825 { /* (PROBE-PATHNAME pathname &key (error t))
5826 a safe way to distinguish between files and dirs:
5827 "dir", "dir/" ==> #p"dir/"
5828 "file", "file/" ==> #p"file"
5829 "none", "none/" ==> NIL
5830 the first value is the truename,
5831 the second is the "correct" absolute pathname */
5832 var bool errorp = !nullp(popSTACK());
5833 if (builtin_stream_p(STACK_0)) { /* stream -> path */
5834 probe_path_from_stream(&STACK_0); /* STACK_0 is now an absolute truename */
5835 } else { /* turn into a pathname */
5836 STACK_0 = merge_defaults(coerce_pathname(STACK_0));
5837 check_no_wildcards(STACK_0);
5838 STACK_0 = use_default_dir(STACK_0); /* absolute pathname */
5839 }
5840 /* STACK_0 is a non-wild non-logical absolute pathname */
5841 var file_kind_t classification;
5842 var char resolved[MAXPATHLEN];
5843 pushSTACK(NIL); pushSTACK(STACK_1); /* space for FWD & FSIZE */
5844 with_sstring_0(whole_namestring(STACK_0),O(pathname_encoding),
5845 namestring_asciz, {
5846 while (true) {
5847 classification = classify_namestring(namestring_asciz,resolved,
5848 &STACK_1/*fwd*/,&STACK_2/*fsize*/);
5849 if (classification == FILE_KIND_NONE
5850 && namestring_asciz_bytelen > 1 /* no need to classify "" */
5851 && cpslashp(namestring_asciz[namestring_asciz_bytelen-1]))
5852 namestring_asciz[--namestring_asciz_bytelen] = 0; /* strip last slash */
5853 else break;
5854 }
5855 });
5856 switch (classification) {
5857 case FILE_KIND_BAD:
5858 if (errorp)
5859 OS_file_error(STACK_0);
5860 /*FALLTHROUGH*/
5861 case FILE_KIND_NONE: /* does not exist */
5862 VALUES1(NIL); skipSTACK(3); return;
5863 case FILE_KIND_DIR: { /* directory */
5864 var int len = strlen(resolved);
5865 if (!cpslashp(resolved[len-1])) { /* append '/' to truename */
5866 resolved[len] = '/'; resolved[len+1]= 0;
5867 }
5868 if (!namenullp(STACK_0)) { /* make STACK_0 a directory pathname */
5869 STACK_0 = copy_pathname(STACK_0);
5870 var uintC count = file_namestring_parts(STACK_0);
5871 var object tmp = string_concat(count);
5872 pushSTACK(tmp);
5873 tmp = allocate_cons();
5874 Car(tmp) = STACK_0;
5875 STACK_0 = tmp;
5876 tmp = ThePathname(STACK_1)->pathname_directory;
5877 if (consp(tmp)) {
5878 /* do NOT modify the argument! */
5879 tmp = copy_list(tmp);
5880 ThePathname(STACK_1)->pathname_directory = tmp;
5881 while (!nullp(Cdr(tmp))) tmp = Cdr(tmp);
5882 Cdr(tmp) = popSTACK(); /* append name.type to directory */
5883 } else if (nullp(tmp)) {
5884 tmp = allocate_cons();
5885 ThePathname(STACK_1)->pathname_directory = tmp;
5886 Car(tmp) = S(Krelative);
5887 Cdr(tmp) = popSTACK(); /* :directory (:relative name.type) */
5888 } else NOTREACHED;
5889 ThePathname(STACK_0)->pathname_name = NIL; /* drop name... */
5890 ThePathname(STACK_0)->pathname_type = NIL; /* ...and type */
5891 }
5892 } break;
5893 case FILE_KIND_FILE: /* file */
5894 if (namenullp(STACK_0)) { /* make STACK_0 a regular file pathname */
5895 STACK_0 = copy_pathname(STACK_0);
5896 var object tmp = ThePathname(STACK_0)->pathname_directory;
5897 while (!nullp(Cdr(Cdr(tmp)))) tmp = Cdr(tmp);
5898 pushSTACK(Car(Cdr(tmp))); Cdr(tmp) = NIL; /* chop off last dir comp */
5899 split_name_type(1);
5900 ThePathname(STACK_2)->pathname_name = STACK_1;
5901 ThePathname(STACK_2)->pathname_type = STACK_0;
5902 skipSTACK(2);
5903 }
5904 break;
5905 }
5906 pushSTACK(asciz_to_string(resolved,O(pathname_encoding)));
5907 funcall(L(truename),1);
5908 value2 = popSTACK();
5909 value3 = popSTACK();
5910 value4 = popSTACK();
5911 mv_count = 4;
5912 }
5913
5914 #ifdef UNIX
5915 /* call stat(2) on the object and return its return value
5916 > namestring: string
5917 > status: pointer to a stat
5918 < status */
stat_obj(object namestring,struct stat * status)5919 local maygc int stat_obj (object namestring, struct stat *status) {
5920 var int ret;
5921 var int saved_errno;
5922 with_sstring_0(namestring,O(pathname_encoding),namestring_asciz, {
5923 GC_SAFE_SYSTEM_CALL((ret = stat(namestring_asciz,status), saved_errno = errno));
5924 });
5925 errno = saved_errno;
5926 return ret;
5927 }
5928 #endif
5929
5930 /* tests, if a directory exists.
5931 directory_exists(pathname)
5932 > pathname: an absolute pathname without wildcards, with Name=NIL and Type=NIL
5933 < result: true, if it denotes an existing directory
5934 can trigger GC */
directory_exists(object pathname)5935 local maygc bool directory_exists (object pathname) {
5936 pushSTACK(pathname); /* save pathname */
5937 var object dir_namestring = directory_namestring(pathname);
5938 /* existence test, see assure_dir_exists(): */
5939 var bool exists = true;
5940 #if defined(WIN32_NATIVE)
5941 with_sstring_0(dir_namestring,O(pathname_encoding),dir_namestring_asciz, {
5942 if (!nullp(Cdr(ThePathname(STACK_0)->pathname_directory))) {
5943 var uintL len = Sstring_length(dir_namestring);
5944 ASSERT((len > 0) && cpslashp(dir_namestring_asciz[len-1]));
5945 dir_namestring_asciz[len-1] = '\0'; /* replace '\' at the end with nullbyte */
5946 }
5947 var DWORD fileattr;
5948 GC_SAFE_SYSTEM_CALL(fileattr = GetFileAttributes(dir_namestring_asciz));
5949 if (fileattr == 0xFFFFFFFF) {
5950 if (!WIN32_ERROR_NOT_FOUND) {
5951 OS_file_error(STACK_0);
5952 }
5953 exists = false;
5954 } else {
5955 if (!(fileattr & FILE_ATTRIBUTE_DIRECTORY)) /* found file is no subdirectory ? */
5956 exists = false;
5957 }
5958 });
5959 #elif defined(PATHNAME_UNIX)
5960 pushSTACK(dir_namestring);
5961 pushSTACK(O(dot_string)); /* and "." */
5962 dir_namestring = string_concat(2); /* concatenate */
5963 var struct stat statbuf;
5964 if (stat_obj(dir_namestring,&statbuf) < 0) {
5965 if (errno != ENOENT) OS_file_error(STACK_0);
5966 exists = false;
5967 } else {
5968 if (!S_ISDIR(statbuf.st_mode)) /* found file is no subdirectory ? */
5969 exists = false;
5970 }
5971 #else
5972 #error directory_exists is not defined
5973 #endif
5974 skipSTACK(1);
5975 return exists;
5976 }
5977
5978 LISPFUNNS(probe_directory,1)
5979 { /* (PROBE-DIRECTORY filename) tests, if a directory exists. */
5980 var object pathname = popSTACK(); /* pathname-argument */
5981 pathname = merge_defaults(coerce_pathname(pathname)); /* --> pathname */
5982 check_no_wildcards(pathname); /* with wildcards -> error */
5983 pathname = use_default_dir(pathname); /* insert default-directory */
5984 check_notdir(pathname); /* ensure that Name=NIL and Type=NIL */
5985 VALUES_IF(directory_exists(pathname));
5986 }
5987
5988 /* Converts a directory pathname to an OS directory specification.
5989 > pathname: an object
5990 > use_default: whether to use the current default directory
5991 < result: a simple-bit-vector containing an ASCIZ string in OS format
5992 can trigger GC */
pathname_to_OSdir(object pathname,bool use_default)5993 modexp maygc object pathname_to_OSdir (object pathname, bool use_default) {
5994 pathname = coerce_pathname(pathname); /* convert to pathname */
5995 check_no_wildcards(pathname); /* if it has wildcards -> error */
5996 if (use_default)
5997 pathname = use_default_dir(pathname); /* insert default directory */
5998 check_notdir(pathname); /* ensure that Name=NIL and Type=NIL */
5999 pushSTACK(pathname); /* save pathname */
6000 var object dir_namestring = directory_namestring(pathname);
6001 var object dir_namestring_asciz =
6002 string_to_asciz(dir_namestring,O(pathname_encoding));
6003 var char* asciz = TheAsciz(dir_namestring_asciz);
6004 var uintL len = asciz_length(asciz);
6005 #if defined(WIN32_NATIVE) || defined(UNIX)
6006 if (!nullp(Cdr(ThePathname(STACK_0)->pathname_directory))) {
6007 ASSERT((len > 0) && cpslashp(asciz[len-1]));
6008 asciz[len-1] = '\0';
6009 }
6010 #endif
6011 skipSTACK(1); /* forget pathname */
6012 return dir_namestring_asciz;
6013 }
6014
6015 /* Converts an OS directory specification to a directory pathname.
6016 > path: a pathname referring to a directory
6017 < result: a pathname without name and type
6018 can trigger GC */
OSdir_to_pathname(const char * path)6019 modexp maygc object OSdir_to_pathname (const char* path) {
6020 return asciz_dir_to_pathname(path,O(pathname_encoding));
6021 }
6022
6023 /* UP: determines, if a file is opened.
6024 openp(pathname) */
6025 #ifdef PATHNAME_WIN32
6026 /* > pathname: absolute pathname, without wildcards. */
6027 #endif
6028 #ifdef PATHNAME_UNIX
6029 /* > pathname: absolute pathname, without wildcards, after resolution
6030 of symbolic links */
6031 #endif
6032 /* < result: true, if an opened file-stream exits for this file. */
openp(object pathname)6033 local maygc bool openp (object pathname) {
6034 pushSTACK(pathname);
6035 var bool found = false;
6036 var gcv_object_t *pathname_ = &STACK_0;
6037 WITH_OS_MUTEX_LOCK(0,&open_files_lock, {
6038 var object flist = O(open_files); /* traverse list of all open files */
6039 while (!found && consp(flist)) {
6040 var object f = Car(flist); /* next open stream */
6041 if (TheStream(f)->strmtype == strmtype_file) { /* file-stream ? */
6042 if (equal(TheStream(f)->strm_file_truename,*pathname_)) {
6043 found = true; /* exit */
6044 }
6045 }
6046 flist = Cdr(flist);
6047 }
6048 });
6049 skipSTACK(1);
6050 return found;
6051 }
6052
6053 /* error-message because of deletion attempt on opened file
6054 error_delete_open(pathname);
6055 > pathname: truename of the file */
error_delete_open(object pathname)6056 local _Noreturn void error_delete_open (object pathname) {
6057 pushSTACK(pathname); /* FILE-ERROR slot PATHNAME */
6058 pushSTACK(pathname); pushSTACK(TheSubr(subr_self)->name);
6059 error(file_error,GETTEXT("~S: Cannot delete file ~S since there is a file stream open to it"));
6060 }
6061 #define check_delete_open(pathname) \
6062 do { if (openp(pathname)) { error_delete_open(pathname); } } while(0)
6063
6064 /* (DELETE-FILE filename), CLTL p. 424 */
6065 LISPFUNN(delete_file,1) {
6066 var object pathname = popSTACK(); /* pathname-argument */
6067 if (builtin_stream_p(pathname)) { /* stream -> treat extra: */
6068 var object stream = as_file_stream(pathname); /* must be file-stream */
6069 test_file_stream_named(stream);
6070 /* Streamtype file-stream.
6071 if file is opened, close file first: */
6072 if (TheStream(stream)->strmflags & strmflags_open_B) { /* file opened ? */
6073 pushSTACK(stream); builtin_stream_close(&STACK_0,0); stream = popSTACK();
6074 }
6075 /* then take the truename as file to be deleted: */
6076 pathname = file_stream_truename(stream);
6077 } else /* turn into a pathname */
6078 pathname = merge_defaults(coerce_pathname(pathname));
6079 /* pathname is now a pathname. */
6080 check_no_wildcards(pathname); /* with wildcards -> error */
6081 pathname = use_default_dir(pathname); /* insert default-directory */
6082 check_noname(pathname);
6083 pushSTACK(pathname); pushSTACK(pathname);
6084 var struct file_status fs; file_status_init(&fs,&STACK_0);
6085 assure_dir_exists(&fs,false,true);
6086 if (!eq(fs.fs_namestring,nullobj)) /* path to the file exists */
6087 check_delete_open(*(fs.fs_pathname));
6088 /* delete the original filename - not the truename (which may be invalid!) */
6089 if (delete_file_if_exists_obj(whole_namestring(STACK_1)))
6090 /* file existed, was deleted -> pathname (/=NIL) as value */
6091 VALUES1(nullp(O(ansi)) ? (object)STACK_1 : T);
6092 else /* file does not exist -> value NIL */
6093 VALUES1(NIL);
6094 skipSTACK(2);
6095 }
6096
6097 /* error-message because of renaming attempt of an opened file
6098 error_rename_open(pathname);
6099 > pathname: truename of the file */
error_rename_open(object pathname)6100 local _Noreturn void error_rename_open (object pathname) {
6101 pushSTACK(pathname); /* FILE-ERROR slot PATHNAME */
6102 pushSTACK(pathname); pushSTACK(TheSubr(subr_self)->name);
6103 error(file_error,GETTEXT("~S: Cannot rename file ~S since there is a file stream open to it"));
6104 }
6105 #define check_rename_open(pathname) \
6106 do { if (openp(pathname)) { error_rename_open(pathname); } } while(0)
6107
6108 /* UP: Renames a file.
6109 rename_file();
6110 > stack layout: filename, newname, oldpathname.
6111 < stack layout: filename, newname, oldpathname, newpathname,
6112 oldtruename, oldnamestring, newtruename, newnamestring. */
rename_file(if_exists_t if_exists)6113 local void rename_file (if_exists_t if_exists) {
6114 { /* 1. newpathname := (MERGE-PATHNAMES newname oldpathname) */
6115 pushSTACK(STACK_1); /* newname as 1st argument */
6116 pushSTACK(STACK_(0+1)); /* oldpathname as 2nd argument */
6117 funcall(L(merge_pathnames),2);
6118 pushSTACK(value1);
6119 }
6120 /* stack layout: filename, newname, oldpathname, newpathname. */
6121 { /* 2. check oldpathname: */
6122 pushSTACK(STACK_1);
6123 var struct file_status fs; file_status_init(&fs,&STACK_0);
6124 true_namestring(&fs,true,false);
6125 pushSTACK(fs.fs_namestring);
6126 check_rename_open(*(fs.fs_pathname)); /* do not rename open files! */
6127 fs.fs_namestring = STACK_0;
6128 if (!file_exists(&fs))
6129 error_file_not_exists();
6130 fs.fs_namestring = popSTACK();
6131 pushSTACK(fs.fs_namestring);
6132 }
6133 /* stack layout: filename, newname, oldpathname, newpathname,
6134 oldtruename, oldnamestring. */
6135 { /* 3. check newpathname: */
6136 var object newpathname = coerce_pathname(STACK_2);
6137 pushSTACK(newpathname);
6138 var struct file_status fs; file_status_init(&fs,&STACK_0);
6139 true_namestring(&fs,true,false);
6140 /* stack layout: filename, newname, oldpathname, newpathname,
6141 oldtruename, oldnamestring, newtruename.
6142 4. rename file: */
6143 pushSTACK(fs.fs_namestring); /* since soon may be invalid */
6144 switch (if_exists) {
6145 case IF_EXISTS_UNBOUND: case IF_EXISTS_NIL: case IF_EXISTS_ERROR:
6146 if (file_exists(&fs)) {
6147 skipSTACK(1);
6148 /* file already exists -> do not delete without forewarn */
6149 error_file_exists();
6150 } break;
6151 default: break; /* atomically replace */
6152 }
6153 }
6154 /* stack layout: filename, newname, oldpathname, newpathname,
6155 oldtruename, oldnamestring, newtruename, newnamestring.
6156 now it can be renamed without risk: */
6157 rename_existing_path(STACK_2,STACK_0);
6158 }
6159
6160 /* (RENAME-FILE filename newname &if-exists), CLTL p. 423 */
6161 LISPFUN(rename_file,seclass_default,2,0,norest,key,1,(kw(if_exists))) {
6162 var if_exists_t if_exists = check_if_exists(popSTACK());
6163 if (!nullp(O(ansi)) && if_exists != IF_EXISTS_UNBOUND)
6164 error_too_many_args(unbound,S(rename_file),4,2);
6165 var object filename = STACK_1; /* filename-argument */
6166 if (builtin_stream_p(filename)) { /* stream -> treat extra: */
6167 /* must be file-stream: */
6168 filename = as_file_stream(filename);
6169 /* streamtype file-stream -> use truename: */
6170 filename = file_stream_truename(filename);
6171 pushSTACK(filename);
6172 /* rename: */
6173 rename_file(if_exists);
6174 /* update stream: */
6175 filename = STACK_7;
6176 TheStream(filename)->strm_file_name = STACK_4; /* newpathname as new name */
6177 TheStream(filename)->strm_file_truename = STACK_1; /* newtruename as new truename */
6178 /* leave handle etc. untouched */
6179 } else { /* turn into a pathname */
6180 filename = merge_defaults(coerce_pathname(filename));
6181 pushSTACK(filename);
6182 /* rename: */
6183 rename_file(if_exists);
6184 }
6185 VALUES3(STACK_4, /* newpathname as 1st value */
6186 STACK_3, /* oldtruename as 2nd value */
6187 STACK_1); /* newtruename as 3rd value */
6188 skipSTACK(8);
6189 }
6190
6191 /* Create a file.
6192 create_new_file(pathstring);
6193 It is known that the file does not already exist.
6194 > pathstring: file name, ASCIZ-String
6195 > STACK_0: pathname */
create_new_file(char * pathstring)6196 local maygc inline void create_new_file (char* pathstring) {
6197 #if defined(WIN32_NATIVE)
6198 var Handle handle;
6199 GC_SAFE_SYSTEM_CALL(handle = CreateFile(pathstring, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL));
6200 if (handle==INVALID_HANDLE_VALUE)
6201 { OS_file_error(STACK_0); }
6202 /* file was created, handle is the Handle.
6203 close file again: */
6204 var BOOL closed;
6205 GC_SAFE_SYSTEM_CALL(closed = CloseHandle(handle));
6206 if (!closed)
6207 { OS_file_error(STACK_0); }
6208 #elif defined(UNIX)
6209 var int result;
6210 GC_SAFE_SYSTEM_CALL(result = OPEN(pathstring, O_WRONLY | O_BINARY | O_CREAT | O_TRUNC, my_open_mask));
6211 if (result<0) { OS_file_error(STACK_0); } /* report error */
6212 /* file was created, result is the Handle.
6213 close file again: */
6214 var int closed;
6215 GC_SAFE_SYSTEM_CALL(closed = CLOSE(result));
6216 if (!(closed == 0))
6217 { OS_file_error(STACK_0); } /* report error */
6218 #else
6219 #error create_new_file is not defined
6220 #endif
6221 }
6222
6223 /* Open a file for input.
6224 open_input_file(file_status, pathstring,create_if_not_exists,&handle)
6225 > only after: assure_dir_exists()
6226 > file_status: structure, filled in by assure_dir_exists()
6227 > pathstring: file name, ASCIZ-String
6228 > create_if_not_exists: if true, the file must be created
6229 > STACK_0: pathname
6230 < handle: open file handle
6231 < result: whether the file could be opened (necessarily true if create_if_not_exists) */
open_input_file(struct file_status * fs,char * pathstring,bool create_if_not_exists,Handle * handle_)6232 local maygc inline bool open_input_file (struct file_status *fs, char* pathstring,
6233 bool create_if_not_exists, Handle* handle_) {
6234 #if defined(UNIX)
6235 var int result;
6236 #ifdef FILE_EXISTS_TRIVIAL
6237 var int oflags = O_RDONLY | O_BINARY;
6238 if (!file_exists(fs)) {
6239 /* file does not exist */
6240 if (!create_if_not_exists) return false;
6241 /* create file with open: */
6242 oflags |= O_CREAT;
6243 }
6244 GC_SAFE_SYSTEM_CALL(result = OPEN(pathstring,oflags,my_open_mask));
6245 if (result<0) { OS_file_error(STACK_0); }
6246 #else
6247 var int oflags = O_RDONLY | O_BINARY;
6248 if (create_if_not_exists) { oflags |= O_CREAT; }
6249 GC_SAFE_SYSTEM_CALL(result = OPEN(pathstring,oflags,my_open_mask));
6250 if (result<0) {
6251 if (errno == ENOENT) { /* not found? */
6252 /* file does not exist */
6253 if (!create_if_not_exists) { return false; }
6254 }
6255 OS_file_error(STACK_0); /* report error */
6256 }
6257 #endif
6258 *handle_ = result; return true;
6259 #elif defined(WIN32_NATIVE)
6260 var Handle handle;
6261 #ifdef FILE_EXISTS_TRIVIAL
6262 var DWORD flag = OPEN_EXISTING;
6263 if (!file_exists(_EMA_)) { /* file does not exist */
6264 if (!create_if_not_exists) return false;
6265 /* create file with CreateFile: */
6266 flag = OPEN_ALWAYS;
6267 }
6268 GC_SAFE_SYSTEM_CALL(handle =
6269 CreateFile(pathstring, GENERIC_READ,
6270 FILE_SHARE_READ | FILE_SHARE_WRITE,
6271 NULL, flag, FILE_ATTRIBUTE_NORMAL, NULL));
6272 if (handle==INVALID_HANDLE_VALUE) { OS_file_error(STACK_0); }
6273 #else
6274 var DWORD flag = OPEN_EXISTING;
6275 if (create_if_not_exists) { flag = OPEN_ALWAYS; }
6276 GC_SAFE_SYSTEM_CALL(handle =
6277 CreateFile(pathstring, GENERIC_READ,
6278 FILE_SHARE_READ | FILE_SHARE_WRITE,
6279 NULL, flag, FILE_ATTRIBUTE_NORMAL, NULL));
6280 if (handle==INVALID_HANDLE_VALUE) {
6281 if (WIN32_ERROR_NOT_FOUND) { /* not found? */
6282 /* file does not exist */
6283 if (!create_if_not_exists) { return false; }
6284 }
6285 OS_file_error(STACK_0); /* report Error */
6286 }
6287 #endif
6288 *handle_ = handle; return true;
6289 #else
6290 #error open_input_file is not defined
6291 #endif
6292 }
6293
6294 /* Open a file for output.
6295 open_output_file(pathstring,truncate_if_exists)
6296 > pathstring: file name, ASCIZ-String
6297 > truncate_if_exists: if true, the file is truncated to zero size
6298 > STACK_0: pathname
6299 < result: open file handle */
open_output_file(char * pathstring,bool wronly,bool truncate_if_exists)6300 local maygc inline Handle open_output_file (char* pathstring, bool wronly,
6301 bool truncate_if_exists) {
6302 #if defined(UNIX)
6303 begin_blocking_system_call();
6304 var int flags = O_BINARY | O_CREAT | (truncate_if_exists ? O_TRUNC : 0);
6305 /* regular file or !wronly => O_RDWR
6306 i.e., for the handle to be O_WRONLY, it must be opened :DIRECTION :OUTPUT
6307 AND the underlying file must be special (pipe &c)
6308 https://sourceforge.net/p/clisp/bugs/291/
6309 see Stevens, UNIX Network Programming, vol 2 (IPC), ch 4 (pipes & FIFOs)*/
6310 if (wronly) { /* regular (regular_handle_p) => ignore wronly for buffering */
6311 var struct stat statbuf;
6312 if (stat(pathstring,&statbuf) ||
6313 S_ISREG(statbuf.st_mode) || S_ISBLK(statbuf.st_mode))
6314 flags |= O_RDWR; /* not exists or regular => read-write */
6315 else flags |= O_WRONLY; /* special => write-only */
6316 } else flags |= O_RDWR;
6317 var int result = OPEN(pathstring,flags,my_open_mask);
6318 end_blocking_system_call();
6319 if (result<0) { OS_file_error(STACK_0); } /* report error */
6320 return result;
6321 #elif defined(WIN32_NATIVE)
6322 begin_blocking_system_call();
6323 var Handle handle = /* ignore wronly: no "special" files where it may hurt */
6324 CreateFile(pathstring, GENERIC_READ | GENERIC_WRITE,
6325 FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
6326 (truncate_if_exists ? CREATE_ALWAYS : OPEN_ALWAYS),
6327 FILE_ATTRIBUTE_NORMAL, NULL);
6328 end_blocking_system_call();
6329 if (handle==INVALID_HANDLE_VALUE) { OS_file_error(STACK_0); }
6330 return handle;
6331 #else
6332 #error open_output_file is nore defined
6333 #endif
6334 }
open_output_file_obj(object namestring,bool wronly,bool truncate_if_exists)6335 local maygc inline Handle open_output_file_obj (object namestring, bool wronly,
6336 bool truncate_if_exists) {
6337 Handle ret;
6338 with_sstring_0(namestring,O(pathname_encoding),namestring_asciz, {
6339 ret = open_output_file(namestring_asciz,wronly,truncate_if_exists);
6340 });
6341 return ret;
6342 }
6343
6344 /* Create a backup file before opening a file for output.
6345 create_backup_file(pathstring,delete_backup_file);
6346 > only after: assure_dir_exists()
6347 > pathstring: file name, ASCIZ-String
6348 > delete_backup_file: if true, delete the backup file
6349 > STACK_0: pathname
6350 Can trigger GC */
create_backup_file(char * pathstring,bool delete_backup_file)6351 local inline maygc void create_backup_file (char* pathstring,
6352 bool delete_backup_file) {
6353 check_rename_open(STACK_0); /* do not rename open files! */
6354 var object filename = STACK_0;
6355 var object new_namestring;
6356 /* extend truename with "%" resp. ".bak" resp. "~" :
6357 filename := (parse-namestring (concatenate 'string (namestring filename) "%")) : */
6358 filename = whole_namestring(filename); /* as String */
6359 pushSTACK(filename); pushSTACK(O(backupextend_string)); /* "%" */
6360 filename = string_concat(2); /* concatenate */
6361 pushSTACK(filename); /* save */
6362 pushSTACK(filename); /* save */
6363 filename = coerce_pathname(filename); /* again as filename */
6364 pushSTACK(filename);
6365 check_delete_open(filename); /* maygc */
6366 filename = popSTACK();
6367 STACK_1 = filename;
6368 /* directory already exists. Do not resolve further links here. */
6369 new_namestring = popSTACK(); /* filename for the operating system */
6370 with_sstring_0(new_namestring,O(pathname_encoding),new_namestring_asciz, {
6371 /* delete file (or link) with this name, if existing: */
6372 delete_file_before_rename(new_namestring_asciz);
6373 /* rename file from the old name to this name: */
6374 rename_existing_file(pathstring,new_namestring_asciz);
6375 if (delete_backup_file) { delete_existing_file(new_namestring_asciz); }
6376 });
6377 skipSTACK(1);
6378 }
create_backup_file_obj(object namestring,bool delete_backup_file)6379 local inline maygc void create_backup_file_obj
6380 (object namestring, bool delete_backup_file) {
6381 with_sstring_0(namestring,O(pathname_encoding),namestring_asciz,
6382 { create_backup_file(namestring_asciz,delete_backup_file); });
6383 }
6384
6385 /* check the :DIRECTION argument */
check_direction(object dir)6386 modexp direction_t check_direction (object dir) {
6387 if (!boundp(dir) || eq(dir,S(Kinput)))
6388 return DIRECTION_INPUT;
6389 else if (eq(dir,S(Kinput_immutable)))
6390 return DIRECTION_INPUT_IMMUTABLE;
6391 else if (eq(dir,S(Koutput)))
6392 return DIRECTION_OUTPUT;
6393 else if (eq(dir,S(Kio)))
6394 return DIRECTION_IO;
6395 else if (eq(dir,S(Kprobe)))
6396 return DIRECTION_PROBE;
6397 else error_illegal_arg(dir,O(type_direction),S(Kdirection));
6398 }
6399
direction_symbol(direction_t direction)6400 local object direction_symbol (direction_t direction) {
6401 switch (direction) {
6402 case DIRECTION_INPUT: { return S(Kinput); }
6403 case DIRECTION_INPUT_IMMUTABLE: { return S(Kinput_immutable); }
6404 case DIRECTION_OUTPUT: { return S(Koutput); }
6405 case DIRECTION_IO: { return S(Kio); }
6406 case DIRECTION_PROBE: { return S(Kprobe); }
6407 default: NOTREACHED;
6408 }
6409 }
6410
6411 /* check the :IF-DOES-NOT-EXIST argument
6412 check_if_does_not_exist(argument) */
check_if_does_not_exist(object if_not_exist)6413 modexp if_does_not_exist_t check_if_does_not_exist (object if_not_exist) {
6414 if (!boundp(if_not_exist))
6415 return IF_DOES_NOT_EXIST_UNBOUND;
6416 else if (eq(if_not_exist,S(Kerror)))
6417 return IF_DOES_NOT_EXIST_ERROR;
6418 else if (nullp(if_not_exist))
6419 return IF_DOES_NOT_EXIST_NIL;
6420 else if (eq(if_not_exist,S(Kcreate)))
6421 return IF_DOES_NOT_EXIST_CREATE;
6422 else error_illegal_arg(if_not_exist,O(type_if_does_not_exist),
6423 S(Kif_does_not_exist));
6424 }
6425
6426 /* Converts a :IF-DOES-NOT-EXIST enum item to a symbol.
6427 if_does_not_exist_symbol(item)*/
if_does_not_exist_symbol(if_does_not_exist_t if_not_exist)6428 modexp object if_does_not_exist_symbol (if_does_not_exist_t if_not_exist) {
6429 switch (if_not_exist) {
6430 case IF_DOES_NOT_EXIST_UNBOUND: { return unbound; }
6431 case IF_DOES_NOT_EXIST_ERROR: { return S(Kerror); }
6432 case IF_DOES_NOT_EXIST_NIL: { return NIL; }
6433 case IF_DOES_NOT_EXIST_CREATE: { return S(Kcreate); }
6434 }
6435 NOTREACHED;
6436 }
6437
6438 /* check the :IF-EXISTS argument
6439 check_if_exists(argument) */
check_if_exists(object if_exists)6440 modexp if_exists_t check_if_exists (object if_exists) {
6441 if (!boundp(if_exists))
6442 return IF_EXISTS_UNBOUND;
6443 else if (eq(if_exists,S(Kerror)))
6444 return IF_EXISTS_ERROR;
6445 else if (nullp(if_exists))
6446 return IF_EXISTS_NIL;
6447 else if (eq(if_exists,S(Krename)))
6448 return IF_EXISTS_RENAME;
6449 else if (eq(if_exists,S(Krename_and_delete)))
6450 return IF_EXISTS_RENAME_AND_DELETE;
6451 else if (eq(if_exists,S(Knew_version)) || eq(if_exists,S(Ksupersede)))
6452 return IF_EXISTS_SUPERSEDE;
6453 else if (eq(if_exists,S(Kappend)))
6454 return IF_EXISTS_APPEND;
6455 else if (eq(if_exists,S(Koverwrite)))
6456 return IF_EXISTS_OVERWRITE;
6457 else error_illegal_arg(if_exists,O(type_if_exists),S(Kif_exists));
6458 }
6459
6460 /* Converts a :IF-EXISTS enum item to a symbol.
6461 if_exists_symbol(item) */
if_exists_symbol(if_exists_t if_exists)6462 modexp object if_exists_symbol (if_exists_t if_exists) {
6463 switch (if_exists) { /* :IF-EXISTS */
6464 case IF_EXISTS_UNBOUND: { return unbound; }
6465 case IF_EXISTS_ERROR: { return S(Kerror); }
6466 case IF_EXISTS_NIL: { return NIL; }
6467 case IF_EXISTS_RENAME: { return S(Krename); }
6468 case IF_EXISTS_RENAME_AND_DELETE: { return S(Krename_and_delete); }
6469 case IF_EXISTS_SUPERSEDE: { return S(Ksupersede); }
6470 case IF_EXISTS_APPEND: { return S(Kappend); }
6471 case IF_EXISTS_OVERWRITE: { return S(Koverwrite); }
6472 }
6473 NOTREACHED;
6474 }
6475
6476 /* UP: check that the file we are about to open has not been opened yet
6477 > object truename - the name of the file that is being opened
6478 > direction_t direction - the direction of the pending OPEN
6479 can trigger GC - if CERROR or WARNING is signaled */
6480 extern maygc void* find_open_file (struct file_id *fid, uintB flags);
check_file_reopen(object truename,direction_t direction)6481 local maygc void check_file_reopen (object truename, direction_t direction) {
6482 var uintB flags;
6483 switch (direction) {
6484 case DIRECTION_INPUT_IMMUTABLE: case DIRECTION_INPUT:
6485 flags = strmflags_wr_B;
6486 break;
6487 case DIRECTION_IO: case DIRECTION_OUTPUT:
6488 flags = (strmflags_rd_B | strmflags_wr_B);
6489 break;
6490 default: return; /* PROBE: nothing to check */
6491 }
6492 check_file_reopen_restart_search:
6493 var object bad_stream = nullobj;
6494 var struct file_id fi;
6495 var os_error_code_t status;
6496 pushSTACK(truename); /* save for find_open_file & namestring_file_id. */
6497 with_string_0(truename,O(pathname_encoding),namez, {
6498 GC_SAFE_SYSTEM_CALL(status = namestring_file_id(namez,&fi));
6499 });
6500 if (status == 0 /* file exists - see if it is already open */
6501 && find_open_file(&fi,flags))
6502 bad_stream = popSTACK();
6503 truename = popSTACK();
6504 if (!eq(bad_stream,nullobj)) { /* found an existing open stream */
6505 #define error_format_string CLSTEXT("~S: ~S already points to file ~S, opening the file again for ~S may produce unexpected results")
6506 if (eq(Symbol_value(S(reopen_open_file)),S(error))) {
6507 check_file_reopen_error:
6508 pushSTACK(NIL); /* 8: continue-format-string */
6509 pushSTACK(S(file_error)); /* 7: error type */
6510 pushSTACK(S(Kpathname)); /* 6: :PATHNAME */
6511 pushSTACK(truename); /* 5: the offending pathname */
6512 pushSTACK(NIL); /* 4: error-format-string */
6513 pushSTACK(TheSubr(subr_self)->name); /* 3: caller */
6514 pushSTACK(bad_stream); /* 2: bad stream */
6515 pushSTACK(truename); /* 1: truename */
6516 pushSTACK(direction_symbol(direction)); /* 0: direction */
6517 STACK_8 = CLSTEXT("Open the file anyway"); /* continue-format-string */
6518 STACK_4 = error_format_string;
6519 funcall(L(cerror_of_type),9);
6520 } else if (eq(Symbol_value(S(reopen_open_file)),S(warn))) {
6521 pushSTACK(NIL); /* 4: error_format_string */
6522 pushSTACK(TheSubr(subr_self)->name); /* 3: caller */
6523 pushSTACK(bad_stream); /* 2: bad stream */
6524 pushSTACK(truename); /* 1: truename */
6525 pushSTACK(direction_symbol(direction)); /* 0: direction */
6526 STACK_4 = error_format_string;
6527 funcall(S(warn),5);
6528 } else if (eq(Symbol_value(S(reopen_open_file)),S(closeL))) {
6529 pushSTACK(truename); /* save */
6530 pushSTACK(bad_stream); builtin_stream_close(&STACK_0,1); skipSTACK(1);
6531 truename = popSTACK(); /* restore */
6532 goto check_file_reopen_restart_search;
6533 } else {
6534 pushSTACK(bad_stream); pushSTACK(truename); /* save */
6535 pushSTACK(CLSTEXT("~S: The value of ~S should be one of ~S, ~S, ~S, or ~S, not ~S. It has been changed to ~S."));
6536 pushSTACK(TheSubr(subr_self)->name);
6537 pushSTACK(S(reopen_open_file));
6538 pushSTACK(S(error)); pushSTACK(S(warn));
6539 pushSTACK(S(closeL)); pushSTACK(NIL);
6540 pushSTACK(Symbol_value(S(reopen_open_file))); pushSTACK(S(error));
6541 funcall(S(warn),9);
6542 Symbol_value(S(reopen_open_file)) = S(error);
6543 truename = popSTACK(); bad_stream = popSTACK(); /* restore */
6544 goto check_file_reopen_error;
6545 }
6546 #undef error_format_string
6547 }
6548 }
6549
6550 /* UP: create a file-stream
6551 open_file(filename,direction,if_exists,if_not_exists)
6552 > STACK_3: original filename (may be logical)
6553 > STACK_2: :BUFFERED argument
6554 > STACK_1: :EXTERNAL-FORMAT argument
6555 > STACK_0: :ELEMENT-TYPE argument
6556 > filename: filename, a pathname
6557 > direction: direction_t (see lispbibl.d)
6558 > if_exists: :IF-EXISTS argument if_exists_t (see lispbibl.d)
6559 > if_not_exists: :IF-DOES-NOT-EXIST argument (see lispbibl.d)
6560 < result: Stream or NIL
6561 < STACK: cleaned up
6562 can trigger GC */
open_file(object filename,direction_t direction,if_exists_t if_exists,if_does_not_exist_t if_not_exists)6563 local maygc object open_file (object filename, direction_t direction,
6564 if_exists_t if_exists,
6565 if_does_not_exist_t if_not_exists) {
6566 pushSTACK(NIL); /* reserve space on STACK for namestring ... */
6567 var gcv_object_t *namestring_ = &STACK_0; /* ... and remember it */
6568 pushSTACK(STACK_(3+1)); /* save filename */
6569 /* Directory must exist: */
6570 pushSTACK(filename);
6571 var struct file_status fs; file_status_init(&fs,&STACK_0);
6572 /* tolerant only if :PROBE and if_not_exists = UNBOUND or NIL */
6573 true_namestring(&fs,true,
6574 ((direction == DIRECTION_PROBE)
6575 && (if_not_exists == IF_DOES_NOT_EXIST_UNBOUND))
6576 || (if_not_exists == IF_DOES_NOT_EXIST_NIL));
6577 if (eq(fs.fs_namestring,nullobj))
6578 /* path to the file does not exist,
6579 and :IF-DOES-NOT-EXIST = unbound or NIL */
6580 goto result_NIL;
6581 *namestring_ = fs.fs_namestring;
6582 /* stack layout: Namestring, Pathname, Truename
6583 check filename and get the handle: */
6584 if (!nullpSv(reopen_open_file)) {
6585 check_file_reopen(*namestring_,direction);
6586 fs.fs_namestring = *namestring_;
6587 }
6588 var object handle;
6589 {var bool append_flag = false;
6590 var bool wronly_flag = false;
6591 switch (direction) {
6592 case DIRECTION_PROBE:
6593 if (!file_exists(&fs)) { /* file does not exist */
6594 /* :IF-DOES-NOT-EXIST decides: */
6595 if (if_not_exists==IF_DOES_NOT_EXIST_ERROR)
6596 goto error_notfound;
6597 if (if_not_exists==IF_DOES_NOT_EXIST_UNBOUND
6598 || if_not_exists==IF_DOES_NOT_EXIST_NIL)
6599 goto result_NIL;
6600 /* :CREATE -> create the file using open and close: */
6601 with_sstring_0(*namestring_,O(pathname_encoding),namestring_asciz, {
6602 create_new_file(namestring_asciz);
6603 });
6604 }
6605 { handle = NIL; } /* Handle := NIL */
6606 break;
6607 case DIRECTION_INPUT: case DIRECTION_INPUT_IMMUTABLE: { /* == :INPUT */
6608 var Handle handl;
6609 var bool result;
6610 with_sstring_0(*namestring_,O(pathname_encoding),namestring_asciz, {
6611 result = open_input_file(&fs,namestring_asciz,
6612 if_not_exists==IF_DOES_NOT_EXIST_CREATE,
6613 &handl);
6614 });
6615 if (!result) {
6616 /* :IF-DOES-NOT-EXIST decides: */
6617 if (if_not_exists==IF_DOES_NOT_EXIST_NIL)
6618 goto result_NIL;
6619 else /* UNBOUND or :ERROR -> Error */
6620 goto error_notfound;
6621 }
6622 handle = allocate_handle(handl);
6623 } break;
6624 case DIRECTION_OUTPUT: wronly_flag = true; /*FALLTHROUGH*/
6625 case DIRECTION_IO:
6626 /* default for if_not_exists depends on if_exists: */
6627 if (if_not_exists==IF_DOES_NOT_EXIST_UNBOUND) {
6628 if (if_exists!=IF_EXISTS_APPEND && if_exists!=IF_EXISTS_OVERWRITE)
6629 /* (if_exists<IF_EXISTS_APPEND)
6630 if_exists = :APPEND or :OVERWRITE -> if_not_exists unchanged,
6631 otherwise :CREATE is the default */
6632 if_not_exists = IF_DOES_NOT_EXIST_CREATE;
6633 }
6634 /* default for if_exists is :SUPERSEDE (= :NEW-VERSION) : */
6635 if (if_exists==IF_EXISTS_UNBOUND)
6636 if_exists = IF_EXISTS_SUPERSEDE;
6637 if (file_exists(&fs)) {
6638 /* file exists => :IF-EXISTS decides: */
6639 switch (if_exists) {
6640 case IF_EXISTS_ERROR:
6641 goto error_exists;
6642 case IF_EXISTS_NIL:
6643 goto result_NIL;
6644 case IF_EXISTS_RENAME: case IF_EXISTS_RENAME_AND_DELETE:
6645 create_backup_file_obj(*namestring_,
6646 if_exists==IF_EXISTS_RENAME_AND_DELETE);
6647 break;
6648 case IF_EXISTS_APPEND:
6649 append_flag = true; /* position at the end */
6650 default: ;
6651 /* :OVERWRITE -> use the existing file
6652 :NEW-VERSION, :SUPERSEDE -> truncate the file at 0 length */
6653 }
6654 } else { /* file does not exist => :IF-DOES-NOT-EXIST decides: */
6655 if (if_not_exists==IF_DOES_NOT_EXIST_UNBOUND
6656 || if_not_exists==IF_DOES_NOT_EXIST_ERROR)
6657 goto error_notfound;
6658 if (if_not_exists==IF_DOES_NOT_EXIST_NIL)
6659 goto result_NIL;
6660 /* :CREATE */
6661 }
6662 /* open file:
6663 if-exists: if if_exists<IF_EXISTS_APPEND delete contents;
6664 othersise (with :APPEND, :OVERWRITE) preserve contents.
6665 if-not-exists: create new file. */
6666 { handle = allocate_handle(open_output_file_obj
6667 (*namestring_,wronly_flag,
6668 (if_exists!=IF_EXISTS_APPEND
6669 && if_exists!=IF_EXISTS_OVERWRITE))); }
6670 break;
6671 default: NOTREACHED;
6672 /* STACK_0 = Truename, FILE-ERROR slot PATHNAME */
6673 error_notfound: /* error: file not found */
6674 error_file_not_exists();
6675 error_exists: /* error: file already exists */
6676 error_file_exists();
6677 }
6678 handle_ok:
6679 /* handle and append_flag are done with.
6680 make the Stream: */
6681 pushSTACK(STACK_5); /* :BUFFERED argument */
6682 pushSTACK(STACK_5); /* :EXTERNAL-FORMAT argument */
6683 pushSTACK(STACK_5); /* :ELEMENT-TYPE argument */
6684 pushSTACK(handle);
6685 {var object stream = make_file_stream(direction,append_flag,true);
6686 skipSTACK(5);
6687 return stream;
6688 }}
6689 result_NIL: /* return NIL */
6690 skipSTACK(7); /* forget both Pathnames and three arguments */
6691 return NIL;
6692 }
6693
6694 /* (OPEN filename :direction :element-type :if-exists :if-does-not-exist
6695 :external-format :buffered) */
6696 LISPFUN(open,seclass_default,1,0,norest,key,6,
6697 (kw(direction),kw(element_type),kw(if_exists),
6698 kw(if_does_not_exist),kw(external_format),kw(buffered)) ) {
6699 var object filename = STACK_6; /* filename */
6700 if (builtin_stream_p(filename)) {
6701 /* must be file-stream: */
6702 filename = as_file_stream(filename);
6703 test_file_stream_named(filename);
6704 /* streamtype file-stream -> use pathname, not truename */
6705 filename = TheStream(filename)->strm_file_name;
6706 pushSTACK(filename);
6707 } else {
6708 filename = coerce_xpathname(filename); /* turn into a pathname */
6709 pushSTACK(filename);
6710 /* Convert from logical to physical pathname: */
6711 if (logpathnamep(filename))
6712 filename = coerce_pathname(filename);
6713 filename = merge_defaults(filename);
6714 }
6715 /* Stack layout: filename-arg, direction, element-type, if-exists,
6716 if-does-not-exist, external-format, buffered, origpathname.
6717 filename is now a pathname. */
6718 var direction_t direction = check_direction(STACK_(5+1));
6719 var if_exists_t if_exists = check_if_exists(STACK_(3+1));
6720 var if_does_not_exist_t if_not_exists=check_if_does_not_exist(STACK_(2+1));
6721 /* :element-type is checked later.
6722 :external-format is checked later.
6723 :buffered is checked later.
6724 open file: */
6725 STACK_4 = STACK_5; STACK_5 = STACK_2; STACK_6 = STACK_1; STACK_7 = STACK_0;
6726 skipSTACK(4);
6727 VALUES1(open_file(filename,direction,if_exists,if_not_exists));
6728 }
6729
6730 /* UP: Returns a list of all matching pathnames.
6731 directory_search(pathname,dir_search_param)
6732 > pathname: pathname with device /= :WILD
6733 > dir_search_param: :if-does-not-exist, :circle flag, :full flag
6734 < result:
6735 if name=NIL and type=NIL: list of all matching directories,
6736 else (name=NIL -> name=:WILD): list of all matching files.
6737 as absolute pathname without wildcards at a time,
6738 resp. for files and Full-Flag /=NIL as list
6739 (Pathname Write-Date Length)
6740 with Pathname without :WILD/:WILD-INFERIORS-components,
6741 Write-Date = Date of file creation (ss mm hh dd mm yy),
6742 as Decoded-Time suitable for ENCODE-UNIVERSAL-TIME,
6743 Length = Length of the file (in Bytes).
6744 Method: Breadth-first-search (=> only one search operation runs at a time)
6745 can trigger GC */
6746 typedef enum {
6747 DIR_IF_NONE_DISCARD, DIR_IF_NONE_ERROR, DIR_IF_NONE_KEEP, DIR_IF_NONE_IGNORE
6748 } dir_search_if_none_t;
6749 typedef struct {
6750 dir_search_if_none_t if_none;
6751 bool full_p;
6752 bool circle_p;
6753 } dir_search_param_t;
6754 local maygc object directory_search (object pathname, dir_search_param_t *dsp);
6755
6756 /* UP: extends a pathname by the file-information.
6757 > STACK_1: absolute pathname
6758 > STACK_0: absolute pathname, links resolved
6759 > timepoint: decoded mtime
6760 > entry_size: file size
6761 < replace STACK_0 with :FULL info:
6762 (Pathname Truename Write-Date Length [Comment])
6763 can trigger GC */
pack_full_info(decoded_time_t * timepoint,off_t * entry_size)6764 local maygc void pack_full_info (decoded_time_t *timepoint, off_t *entry_size) {
6765 var object newlist;
6766 /* Pathname already in STACK_1, as 1st list element
6767 Truename already in STACK_0, as 2nd list element */
6768 pushSTACK(timepoint->seconds);
6769 pushSTACK(timepoint->minutes);
6770 pushSTACK(timepoint->hours);
6771 pushSTACK(timepoint->day);
6772 pushSTACK(timepoint->month);
6773 pushSTACK(timepoint->year);
6774 newlist = listof(6); /* build 6-element list */
6775 pushSTACK(newlist); /* as 3rd list element */
6776 pushSTACK(off_to_I(*entry_size)); /* length as 4th list element */
6777 newlist = listof(4); /* build 4-element list */
6778 pushSTACK(Car(newlist)); /* pathname again in the STACK */
6779 pushSTACK(newlist); /* list in the STACK */
6780 }
6781
6782 #ifdef WIN32_NATIVE
6783 /* Set of macros for directory search. */
6784 #define READDIR_wildnametype_suffix O(wild_string) /* "*" */
6785 #define READDIR_var_declarations \
6786 var WIN32_FIND_DATA filedata; \
6787 var HANDLE search_handle;
6788 #define READDIR_end_declarations
6789 #define READDIR_findfirst(pathstring,error_statement,done_statement) \
6790 if ((search_handle = FindFirstFile(pathstring,&filedata)) \
6791 == INVALID_HANDLE_VALUE) { \
6792 if (!WIN32_ERROR_NOT_FOUND) { error_statement } \
6793 else { done_statement } \
6794 }
6795 #define READDIR_findnext(error_statement,done_statement) \
6796 if (!FindNextFile(search_handle,&filedata)) { \
6797 if (!(GetLastError()==ERROR_NO_MORE_FILES) \
6798 || !FindClose(search_handle)) \
6799 { error_statement } \
6800 else { done_statement } \
6801 }
6802 #define READDIR_entry_name() (&filedata.cFileName[0])
6803 #define READDIR_entry_ISDIR() (filedata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
6804 #define READDIR_entry_timedate(timepointp) \
6805 convert_time(FIND_DATA_FWD(filedata),timepointp)
6806 #define READDIR_entry_size() FIND_DATA_FSIZE(filedata)
6807
6808 /* UP: get mtime and size from filesystem
6809 > pathname: absolute pathname, links resolved
6810 < timepoint: decoded time
6811 < entry_size: file size
6812 can trigger GC */
get_time_size(gcv_object_t * pathname,decoded_time_t * timepoint,off_t * entry_size)6813 local maygc void get_time_size (gcv_object_t *pathname,
6814 decoded_time_t *timepoint, off_t *entry_size) {
6815 READDIR_var_declarations;
6816 with_sstring_0(whole_namestring(*pathname),O(pathname_encoding),
6817 resolved_asciz,{
6818 var bool notfound = false;
6819 /* strip trailing slash,
6820 see http://msdn2.microsoft.com/en-us/library/aa364418.aspx */
6821 if (resolved_asciz[resolved_asciz_bytelen - 1] == '\\')
6822 resolved_asciz[resolved_asciz_bytelen - 1] = 0;
6823 begin_blocking_system_call();
6824 READDIR_findfirst(resolved_asciz, notfound = true; , notfound = true; );
6825 end_blocking_system_call();
6826 if (notfound) /* just to be paranoid */
6827 OS_file_error(*pathname);
6828 begin_blocking_system_call();
6829 FindClose(search_handle);
6830 end_blocking_system_call();
6831 READDIR_entry_timedate(timepoint);
6832 *entry_size = READDIR_entry_size();
6833 });
6834 READDIR_end_declarations;
6835 }
with_stat_info_computed(struct file_status * fs)6836 local maygc void with_stat_info_computed (struct file_status *fs) {
6837 decoded_time_t timepoint;
6838 off_t entry_size;
6839 get_time_size(fs->fs_pathname,&timepoint,&entry_size);
6840 pack_full_info(&timepoint,&entry_size);
6841 }
6842 #endif
6843
6844 #ifdef UNIX
6845 /* Just like stat(), except that directories or files which would lead
6846 to problems are silently hidden. */
stat_for_search(char * pathstring,struct stat * statbuf)6847 local maygc inline int stat_for_search (char* pathstring, struct stat * statbuf) {
6848 var int result;
6849 GC_SAFE_SYSTEM_CALL(result = stat(pathstring,statbuf));
6850 #ifdef UNIX_CYGWIN
6851 if ((result < 0) && (errno == EACCES)) { errno = ENOENT; }
6852 #endif
6853 return result;
6854 }
6855 #endif
6856
6857 #ifdef PATHNAME_NOEXT
6858 /* UP: Extends the directory of a pathname by one component.
6859 > pathname: a pathname
6860 > subdir: new Subdir-component, a Simple-String
6861 < result: new pathname with directory lengthened by subdir
6862 can trigger GC */
pathname_add_subdir(object pathname,object subdir)6863 local maygc object pathname_add_subdir (object pathname, object subdir) {
6864 pushSTACK(pathname); pushSTACK(subdir);
6865 /* copy pathname and lengthen its directory according to
6866 (append x (list y)) = (nreverse (cons y (reverse x))) : */
6867 pathname = copy_pathname(STACK_1);
6868 STACK_1 = pathname;
6869 pushSTACK(reverse(ThePathname(pathname)->pathname_directory));
6870 var object new_cons = allocate_cons();
6871 Cdr(new_cons) = popSTACK();
6872 Car(new_cons) = popSTACK();
6873 new_cons = nreverse(new_cons);
6874 pathname = popSTACK();
6875 ThePathname(pathname)->pathname_directory = new_cons;
6876 return pathname;
6877 }
6878
6879 #ifdef UNIX
6880 /* UP: extends a pathname by the file-information.
6881 > STACK_1: absolute pathname
6882 > STACK_0: absolute pathname, links resolved
6883 > *filestatus: its stat-info
6884 < STACK_0: list (Pathname Truename Write-Date Length [Comment])
6885 in :FULL-Format */
with_stat_info(struct stat * filestatus)6886 local maygc void with_stat_info (struct stat *filestatus) {
6887 var decoded_time_t timepoint; /* Write-Date in decoded form */
6888 convert_time(&(filestatus->st_mtime),&timepoint);
6889 pack_full_info(&timepoint,&(filestatus->st_size));
6890 }
with_stat_info_computed(struct file_status * fs)6891 local maygc void with_stat_info_computed (struct file_status *fs) {
6892 if (!fs->fs_stat_validp) {
6893 if (stat_obj(whole_namestring(*(fs->fs_pathname)),&(fs->fs_stat)) < 0)
6894 OS_file_error(*(fs->fs_pathname));
6895 fs->fs_stat_validp = true;
6896 }
6897 with_stat_info(&(fs->fs_stat));
6898 }
6899 #endif
6900
6901 /* push object in front of a list
6902 can trigger GC */
push(gcv_object_t * head,gcv_object_t * tail)6903 local inline maygc void push (gcv_object_t *head, gcv_object_t *tail) {
6904 var object new_cons = allocate_cons();
6905 Car(new_cons) = *head;
6906 Cdr(new_cons) = *tail;
6907 *tail = new_cons;
6908 }
6909 #define PUSH_ON_STACK(h,t) push(&STACK_(h),&STACK_(t))
6910
6911 /* Search for a subdirectory with a given name.
6912 directory_search_1subdir(subdir,namestring);
6913 > STACK_0 = pathname
6914 > STACK_(3+1) = new-pathname-list
6915 > subdirtail: the tail of the directory list, Car(subdirtail) is the new
6916 directory component to add to the pathname, if it exists
6917 > namestring: the namestring (for the OS)
6918 < STACK_0: replaced
6919 < STACK_(3+1): augmented
6920 can trigger GC */
copy_pathname_and_add_subdir(object subdir)6921 local maygc void copy_pathname_and_add_subdir (object subdir)
6922 { /* copy pathname(STACK_0) and lengthen its directory by subdir: */
6923 STACK_0 = pathname_add_subdir(STACK_0,subdir);
6924 /* push this new pathname in front of new-pathname-list: */
6925 PUSH_ON_STACK(0,3+1);
6926 }
6927
6928 /* Check whether a directory exists and call copy_pathname_and_add_subdir()
6929 on it; if the directory does not exist or is a file, do nothing */
directory_search_1subdir(gcv_object_t * subdirtail,object namestring)6930 local maygc void directory_search_1subdir (gcv_object_t *subdirtail,
6931 object namestring) {
6932 with_sstring_0(namestring,O(pathname_encoding),namestring_asciz, {
6933 char resolved[MAXPATHLEN];
6934 if (classify_namestring(namestring_asciz,resolved,NULL,NULL)
6935 == FILE_KIND_DIR) /* namestring is a directory */
6936 copy_pathname_and_add_subdir(Car(*subdirtail));
6937 });
6938 }
6939
6940 /* Returns a truename dependent hash code for a directory.
6941 directory_search_hashcode()
6942 STACK_0 = dir_namestring
6943 STACK_1 = pathname
6944 < result: a hash code, or nullobj if the directory does not exist
6945 can trigger GC */
6946
6947 #if defined(UNIX)
6948 /* return (cons drive inode) */
directory_search_hashcode(void)6949 local maygc object directory_search_hashcode (void) {
6950 pushSTACK(STACK_0); /* Directory-Name */
6951 pushSTACK(O(dot_string)); /* and "." */
6952 var object namestring = string_concat(2); /* concatenate */
6953 var struct stat status;
6954 if (stat_obj(namestring,&status) != 0) return nullobj;
6955 /* entry exists (oh miracle...) */
6956 pushSTACK(UL_to_I(status.st_dev)); /* Device-Number and */
6957 #if SIZEOF_INO_T > 4
6958 pushSTACK(UQ_to_I(status.st_ino)); /* Inode-Number */
6959 #else
6960 pushSTACK(UL_to_I(status.st_ino)); /* Inode-Number */
6961 #endif
6962 var object new_cons = allocate_cons(); /* cons them together */
6963 Cdr(new_cons) = popSTACK(); Car(new_cons) = popSTACK();
6964 return new_cons;
6965 }
6966 #elif defined(WIN32_NATIVE)
6967 /* win32 - there is stat but no inodes
6968 using directory truenames as hashcodes */
directory_search_hashcode(void)6969 local maygc object directory_search_hashcode (void) {
6970 return STACK_0;
6971 }
6972 #else
6973 #error directory_search_hashcode is not defined
6974 #endif
6975
6976 #ifdef UNIX
6977 /* Tests whether a directory entry actually exists and is accessible.
6978 (It could be a link pointing to nowhere, or a directory without permissions)
6979 directory_search_direntry_ok(namestring,&statbuf)
6980 STACK_2 = pathname
6981 < result: true and statbuf filled, or false. */
directory_search_direntry_ok(object namestring,struct stat * statbuf)6982 local maygc bool directory_search_direntry_ok (object namestring,
6983 struct stat * statbuf) {
6984 var bool exists = true;
6985 with_sstring_0(namestring,O(pathname_encoding),namestring_asciz, {
6986 if (stat_for_search(namestring_asciz,statbuf)) {
6987 exists = false;
6988 }
6989 });
6990 return exists;
6991 }
6992 #endif
6993 /* the version of files returned by DIRECTORY
6994 Since all pathnames returned by DIRECTORY must be truenames,
6995 this must be :NEWEST [but then they will not be printable readably!] */
6996 #define DEFAULT_VERSION S(Knewest)
6997
6998 /* UP: Match name&type against a direntry
6999 Split direntry using split_name_type and match name & type separately
7000 > pattern: Pathname (only name and type are used)
7001 > original: the original pathname onto which the direntry is split
7002 > direntry: string
7003 < true if match
7004 < direntry: pathname with the original string split into name & type
7005 can trigger GC */
split_nametype_match(gcv_object_t * pattern,gcv_object_t * original,gcv_object_t * direntry)7006 local maygc bool split_nametype_match(gcv_object_t *pattern,
7007 gcv_object_t *original,
7008 gcv_object_t *direntry) {
7009 pushSTACK(*direntry);
7010 split_name_type(1); /* split into Name and Type, add to STACK */
7011 *direntry = copy_pathname(*original); /* overwrite direntry */
7012 ThePathname(*direntry)->pathname_type = popSTACK(); /* insert type */
7013 ThePathname(*direntry)->pathname_name = popSTACK(); /* insert name */
7014 ThePathname(*direntry)->pathname_version = DEFAULT_VERSION;
7015 if (pattern == NULL)
7016 return true;
7017 bool n = nametype_match(ThePathname(*pattern)->pathname_name,
7018 ThePathname(*direntry)->pathname_name,false);
7019 bool t = nametype_match(ThePathname(*pattern)->pathname_type,
7020 ThePathname(*direntry)->pathname_type,false);
7021 return n && t;
7022 }
7023
7024 /* Convert a directory entry to a string
7025 direntry_to_string (char* string, int len)
7026 > string : asciz
7027 > len : its length (if it is -1, asciz_length is used)
7028 < value1 : lisp string or NIL if string is "." or ".."
7029 OR if the conversion failed and the CONTINUE restart was selected
7030 can trigger GC */
7031 #if !defined(MULTITHREAD)
7032 /* should be per thread - moved to clisp_thread_t in MT */
7033 local bool running_handle_directory_encoding_error = false;
7034 #endif
handle_directory_encoding_error(void * sp,gcv_object_t * frame,object label,object condition)7035 local void handle_directory_encoding_error /* cf. enter_frame_at_STACK */
7036 (void *sp, gcv_object_t* frame, object label, object condition) {
7037 unused(sp); unused(label);
7038 /* avoid nested handle_directory_encoding_error calls */
7039 if (running_handle_directory_encoding_error) return;
7040 else running_handle_directory_encoding_error = true;
7041 value1 = condition;
7042 unwind_upto(frame);
7043 }
direntry_to_string(char * string,volatile int len)7044 local maygc object direntry_to_string (char* string, volatile int len) {
7045 if (asciz_equal(string,".") || asciz_equal(string,"..")) return NIL;
7046 if (len == -1) len = asciz_length(string);
7047 #ifdef ENABLE_UNICODE
7048 var gcv_object_t *stack_save = STACK;
7049 var object encoding = O(pathname_encoding);
7050 restart_direntry_to_string:
7051 running_handle_directory_encoding_error = false;
7052 /* build UNWIND-PROTECT-frame: */
7053 var sp_jmp_buf returner; /* return point */
7054 make_C_HANDLER_entry_frame(O(handler_for_charset_type_error),
7055 handle_directory_encoding_error,returner,
7056 goto signal_encoding_error; );
7057 value1 = n_char_to_string(string,len,encoding);
7058 unwind_C_HANDLER_frame();
7059 direntry_to_string_done:
7060 running_handle_directory_encoding_error = false;
7061 if (stack_save != STACK) abort();
7062 return value1;
7063 signal_encoding_error: /* value1 = condition */
7064 unwind_C_HANDLER_frame();
7065 pushSTACK(S(pathname_encoding)); /* PLACE */
7066 pushSTACK(value1); /* condition - for CHECK-VALUE */
7067 /* set condition $DATUM slot to string (as a byte vector) */
7068 pushSTACK(value1/*condition*/); pushSTACK(S(datum));
7069 pushSTACK(allocate_bit_vector(Atype_8Bit,len)); /* slot DATUM */
7070 var int pos; /* fill DATUM: string as a byte vector */
7071 for (pos = 0; pos < len; pos++)
7072 TheSbvector(STACK_0)->data[pos] = string[pos];
7073 funcall(L(set_slot_value),3);
7074 funcall(S(check_value),2);
7075 if (nullp(value1)) goto direntry_to_string_done; /* CONTINUE restart */
7076 encoding = check_encoding(value1,&O(pathname_encoding),false);
7077 if (eq(T,value2)) O(pathname_encoding) = encoding; /* STORE-VALUE restart */
7078 goto restart_direntry_to_string;
7079 #else
7080 return n_char_to_string(string,len,O(pathname_encoding));
7081 #endif
7082 }
7083
7084 #if defined(MULTITHREAD) && defined(HAVE_READDIR_R)
7085 /* UP: Calculate the required buffer size (in bytes) for directory
7086 entries read from the given directory handle. Return -1 if this
7087 this cannot be done.
7088 http://womble.decadentplace.org.uk/readdir_r-advisory.html
7089 > dirp: open directory handle
7090 < returns the size in bytes needed for struct dirent buffer
7091
7092 This code does not trust values of NAME_MAX that are less than
7093 255, since some systems (including at least HP-UX) incorrectly
7094 define it to be a smaller value.
7095 If you use autoconf, include fpathconf and dirfd in your
7096 AC_CHECK_FUNCS list. Otherwise use some other method to detect
7097 and use them where available. */
dirent_buf_size(DIR * dirp)7098 local size_t dirent_buf_size(DIR * dirp)
7099 {
7100 var long name_max;
7101 var size_t name_end;
7102 #if defined(HAVE_FPATHCONF) && defined(HAVE_DIRFD) && defined(_PC_NAME_MAX)
7103 name_max = fpathconf(dirfd(dirp), _PC_NAME_MAX);
7104 if (name_max == -1)
7105 #if defined(NAME_MAX)
7106 name_max = (NAME_MAX > 255) ? NAME_MAX : 255;
7107 #else
7108 return (size_t)(-1);
7109 #endif
7110 #else
7111 #if defined(NAME_MAX)
7112 name_max = (NAME_MAX > 255) ? NAME_MAX : 255;
7113 #else
7114 #error "buffer size for readdir_r cannot be determined"
7115 #endif
7116 #endif
7117 name_end = (size_t)offsetof(struct dirent, d_name) + name_max + 1;
7118 return (name_end > sizeof(struct dirent)
7119 ? name_end : sizeof(struct dirent));
7120 }
7121 #endif
7122
7123 /* Scans an entire directory.
7124 directory_search_scandir(recursively,next_task);
7125 stack layout: result-list, pathname, name&type, subdir-list, pathname-list,
7126 new-pathname-list, ht, pathname-list-rest, pathnames-to-insert,
7127 pathname, dir_namestring. */
7128 typedef enum {
7129 TASK_DONE = 0, /* nothing, finished */
7130 TASK_FILE = 1, /* look for a file of given name/type */
7131 TASK_DIR = -1, /* look for a subdirectory of given name */
7132 TASK_ALL_FILES = 2, /* look for all files matching the given name/type */
7133 TASK_ALL_DIRS = -2 /* look for all subdirectories matching the given name */
7134 } task_t;
7135 #define TASK_FILE_P(task) (task>0)
7136 #define TASK_DIR_P(task) (task<0)
directory_search_scandir(bool recursively,task_t next_task,dir_search_param_t * dsp)7137 local maygc void directory_search_scandir (bool recursively, task_t next_task,
7138 dir_search_param_t *dsp) {
7139 #if defined(UNIX)
7140 {
7141 var object namestring;
7142 pushSTACK(STACK_0); /* directory-name */
7143 pushSTACK(O(dot_string)); /* and "." */
7144 namestring = string_concat(2); /* concatenate */
7145 /* scan directory: */
7146 var DIR* dirp;
7147 set_break_sem_4();
7148 with_sstring_0(namestring,O(pathname_encoding),namestring_asciz, {
7149 /* open directory */
7150 GC_SAFE_SYSTEM_CALL(dirp = opendir(namestring_asciz));
7151 });
7152 if (dirp == (DIR*)NULL) {
7153 switch (dsp->if_none) {
7154 case DIR_IF_NONE_IGNORE: return;
7155 case DIR_IF_NONE_DISCARD: /* ansi cl: unaccessible directory => error */
7156 case DIR_IF_NONE_ERROR: OS_file_error(STACK_1);
7157 case DIR_IF_NONE_KEEP: return;
7158 default: NOTREACHED;
7159 }
7160 }
7161 while (1) {
7162 #if defined(MULTITHREAD) && defined(HAVE_READDIR_R)
7163 /* we should use readdir_r(). it is thread safe (but optional in POSIX)
7164 TODO: some config scripts for it ? Is there thread safe, portable
7165 implementation in GNULIB? */
7166 var DYNAMIC_ARRAY(dp_buf,char,dirent_buf_size(dirp));
7167 var struct dirent * dp;
7168 var int rdr; /* readdir_r() return*/
7169 /* fetch next directory-entry */
7170 GC_SAFE_SYSTEM_CALL(rdr = readdir_r(dirp,(struct dirent *)dp_buf,&dp));
7171 if (dp == (struct dirent *)NULL) { FREE_DYNAMIC_ARRAY(dp_buf); }
7172 errno = rdr;
7173 #else
7174 var struct dirent * dp;
7175 errno = 0;
7176 /* fetch next directory-entry */
7177 GC_SAFE_SYSTEM_CALL(dp = readdir(dirp));
7178 #endif
7179 if (dp == (struct dirent *)NULL) { /* error or directory finished */
7180 if (!(errno==0)) { OS_file_error(STACK_1); }
7181 break;
7182 }
7183 /* convert directory-entry into string: */
7184 var object direntry;
7185 {
7186 var uintL direntry_len;
7187 #if defined(UNIX_CYGWIN)
7188 /* Neither d_reclen nor d_namlen present in DIR structure. */
7189 direntry_len = asciz_length(dp->d_name);
7190 #elif !defined(HAVE_STRUCT_DIRENT_D_NAMLEN) || defined(__USE_GNU)
7191 { /* On UNIX_LINUX direntry_len := dp->d_reclen was sufficient, but in
7192 general direntry_len := min(dp->d_reclen,asciz_length(dp->d_name))
7193 is necessary. The GNU libc is buggy: it does
7194 "#define d_namlen d_reclen", just as the Linux libc-5.0.9. */
7195 var const uintB* ptr = (const uintB*)(&dp->d_name[0]);
7196 var uintL count = dp->d_reclen;
7197 direntry_len = 0;
7198 while (count-- && *ptr++) direntry_len++;
7199 }
7200 #else
7201 direntry_len = dp->d_namlen;
7202 #endif
7203 direntry = direntry_to_string(dp->d_name,direntry_len);
7204 }
7205 #ifdef MULTITHREAD
7206 FREE_DYNAMIC_ARRAY(dp_buf);
7207 #endif
7208 if (!nullp(direntry)) {
7209 pushSTACK(direntry);
7210 /* stack layout: ..., pathname, dir_namestring, direntry.
7211 determine, if it is a directory or a file: */
7212 pushSTACK(STACK_1); /* Directory-Namestring */
7213 SUBDIR_PUSHSTACK(direntry); /* direntry */
7214 var object namestring = string_concat(2); /* concatenate */
7215 /* get information: */
7216 var struct stat status;
7217 #if 1 /* just an optimization */
7218 if (!recursively) {
7219 /* Try to avoid calling directory_search_direntry_ok(),
7220 since it is an expensive operation (it calls stat()). */
7221 if (TASK_DIR_P(next_task)) {
7222 /* match (car subdir-list) with direntry: */
7223 if (wildcard_match(Car(STACK_(1+4+3)),STACK_0)) {
7224 if (directory_search_direntry_ok(namestring,&status)) {
7225 if (S_ISDIR(status.st_mode))
7226 goto push_matching_subdir;
7227 } else
7228 switch (dsp->if_none) {
7229 case DIR_IF_NONE_IGNORE: break;
7230 case DIR_IF_NONE_DISCARD: case DIR_IF_NONE_ERROR:
7231 OS_file_error(namestring);
7232 case DIR_IF_NONE_KEEP:
7233 split_nametype_match(NULL,&STACK_2,&STACK_0);
7234 goto push_matching_file;
7235 default: NOTREACHED;
7236 }
7237 }
7238 } else if (TASK_FILE_P(next_task)) { /* match name&type with direntry: */
7239 pushSTACK(namestring);
7240 var bool name_type_match = split_nametype_match(
7241 &STACK_(2+4+3+1),&STACK_(2+1),&STACK_(0+1));
7242 namestring = popSTACK();
7243 if (name_type_match) {
7244 if (directory_search_direntry_ok(namestring,&status)) {
7245 if (!S_ISDIR(status.st_mode))
7246 goto push_matching_file;
7247 } else
7248 switch (dsp->if_none) {
7249 case DIR_IF_NONE_IGNORE: case DIR_IF_NONE_DISCARD: break;
7250 case DIR_IF_NONE_ERROR:
7251 OS_file_error(namestring);
7252 case DIR_IF_NONE_KEEP:
7253 goto push_matching_file;
7254 default: NOTREACHED;
7255 }
7256 }
7257 }
7258 goto done_direntry;
7259 }
7260 #endif
7261 if (directory_search_direntry_ok(namestring,&status)) {
7262 /* entry exists and is not unwanted. */
7263 if (S_ISDIR(status.st_mode)) { /* is it a directory? */
7264 /* entry is a directory. */
7265 if (recursively) { /* all recursive subdirectories wanted? */
7266 /* yes -> turn into a pathname and push to pathnames-to-insert
7267 (it is later inserted in front of pathname-list-rest): */
7268 pushSTACK(pathname_add_subdir(STACK_2/*pathname*/,STACK_0/*direntry*/));
7269 /* push this new pathname in front of pathname-to-insert: */
7270 PUSH_ON_STACK(0,1+3);
7271 skipSTACK(1);
7272 }
7273 if (TASK_DIR_P(next_task)) {
7274 /* match (car subdir-list) with direntry: */
7275 if (wildcard_match(Car(STACK_(1+4+3)),STACK_0)) {
7276 push_matching_subdir:
7277 /* subdirectory matches -> turn into a pathname
7278 and push onto new-pathname-list: */
7279 pushSTACK(pathname_add_subdir(STACK_2/*pathname*/,STACK_0/*direntry*/));
7280 /* push this new pathname in front of new-pathname-list: */
7281 PUSH_ON_STACK(0,4+3);
7282 skipSTACK(1);
7283 }
7284 }
7285 } else if (TASK_FILE_P(next_task)) { /* entry is a (halfway) normal File. */
7286 /* match name&type with direntry: */
7287 if (split_nametype_match(&STACK_(2+4+3),&STACK_2,&STACK_0)) {
7288 /* File matches -> push STACK_0 onto result-list: */
7289 push_matching_file:
7290 pushSTACK(STACK_0);
7291 pushSTACK(STACK_0);
7292 /* form truename (resolve symbolic links): */
7293 var struct file_status fs; file_status_init(&fs,&STACK_0);
7294 assure_dir_exists(&fs,true,true);
7295 if (!eq(nullobj,fs.fs_namestring) && file_exists(&fs)) {
7296 /* if file (still...) exists */
7297 if (dsp->full_p) /* :FULL wanted? */
7298 with_stat_info(&(fs.fs_stat)); /* yes -> extend STACK_0 */
7299 /* and push STACK_0 in front of result-list: */
7300 PUSH_ON_STACK(0,4+4+3+2);
7301 } else if (dsp->if_none == DIR_IF_NONE_KEEP)
7302 PUSH_ON_STACK(1/* unresolved pathname */,4+4+3+2);
7303 skipSTACK(2);
7304 }
7305 }
7306 } else
7307 switch (dsp->if_none) {
7308 case DIR_IF_NONE_IGNORE: case DIR_IF_NONE_DISCARD: break;
7309 case DIR_IF_NONE_ERROR:
7310 OS_file_error(namestring);
7311 case DIR_IF_NONE_KEEP:
7312 split_nametype_match(NULL,&STACK_2,&STACK_0);
7313 goto push_matching_file;
7314 default: NOTREACHED;
7315 }
7316 done_direntry:
7317 skipSTACK(1); /* forget direntry */
7318 }
7319 }
7320 begin_blocking_system_call();
7321 if (closedir(dirp)) { end_blocking_system_call(); OS_file_error(STACK_1); }
7322 end_blocking_system_call();
7323 clr_break_sem_4();
7324 }
7325 #elif defined(WIN32_NATIVE)
7326 {
7327 SUBDIR_PUSHSTACK(STACK_0); /* Directory-Name */
7328 pushSTACK(READDIR_wildnametype_suffix); /* and concatenate */
7329 var object namestring = string_concat(2); /* "*.*" resp. "*" */
7330 with_sstring_0(namestring,O(pathname_encoding),namestring_asciz, {
7331 /* scan directory, according to DOS- resp. Win32-convention: */
7332 READDIR_var_declarations;
7333 /* start of search, search for folders and normal files: */
7334 begin_blocking_system_call();
7335 do {
7336 /* readdir in resolved directory. directory was resolved earlier */
7337 READDIR_findfirst(namestring_asciz,{
7338 end_blocking_system_call();
7339 if (dsp->if_none == DIR_IF_NONE_IGNORE) {
7340 FREE_DYNAMIC_ARRAY(namestring_asciz); return;
7341 } else OS_file_error(STACK_1);
7342 }, break; );
7343 while (1) {
7344 end_blocking_system_call();
7345 /* convert directory-entry into string: */
7346 var object direntry = direntry_to_string(READDIR_entry_name(),-1);
7347 if (!nullp(direntry)) {
7348 var shell_shortcut_target_t rresolved = shell_shortcut_notresolved;
7349 pushSTACK(direntry);
7350 /* stack layout: ..., pathname, dir_namestring, direntry. */
7351 pushSTACK(NIL); /* will become found file full pathname, */
7352 /* changed with symbolic name for resolved (maybe nonfound) links */
7353 pushSTACK(NIL); /* true pathname of it or whatever result to return */
7354 pushSTACK(direntry); /* here will come filename to wildcard match */
7355 split_name_type(1);
7356 /* stack layout: ..., pathname, dir_namestring, direntry, NIL, NIL, direntry-name, direntry-type. */
7357
7358 /* make full name of found file - dir + direntry
7359 TODO: optimize to not do it when it not needed */
7360 if (READDIR_entry_ISDIR()) { /* pathname and direntry: */
7361 STACK_3 = pathname_add_subdir(STACK_6,STACK_4);
7362 } else {
7363 STACK_(3) = copy_pathname(STACK_(6));
7364 ThePathname(STACK_(3))->pathname_type = STACK_0;
7365 ThePathname(STACK_(3))->pathname_name = STACK_1;
7366 ThePathname(STACK_(3))->pathname_version = DEFAULT_VERSION;
7367 }
7368
7369 /* try to resolve .lnk files */
7370 if (!READDIR_entry_ISDIR() && !nullp(STACK_0)
7371 && string_equal(STACK_0,O(lnk_string)))
7372 {
7373 var char resolved[MAX_PATH];
7374 var char * full_resolved = resolved;
7375 with_sstring_0(whole_namestring(STACK_(3)),O(pathname_encoding),linkfile_asciz, {
7376 rresolved =
7377 resolve_shell_shortcut_more(linkfile_asciz,resolved);
7378 if (rresolved != shell_shortcut_notresolved) {
7379 var char resolved_f[MAX_PATH];
7380 if (FullName(resolved,resolved_f))
7381 full_resolved = resolved_f;
7382 /* hack direntry-pathname to make it a symbolic name
7383 symbolic link names are direntry-pathnames w/o ".lnk"
7384 so split the name again
7385 hack it in-place since lnk filename is not need anymore */
7386 pushSTACK(STACK_1);
7387 split_name_type(1);
7388 ThePathname(STACK_(3+2))->pathname_name = STACK_1;
7389 ThePathname(STACK_(3+2))->pathname_type = STACK_0;
7390 ThePathname(STACK_(3+2))->pathname_version = DEFAULT_VERSION;
7391 skipSTACK(2);
7392 /* what to use as a result */
7393 if (rresolved == shell_shortcut_notexists)
7394 STACK_(2) = STACK_(3); /* use symbolic names as a result when target is not found */
7395 else {
7396 STACK_(2) = coerce_pathname(direntry_to_string(full_resolved,-1));
7397 ThePathname(STACK_(2))->pathname_version = DEFAULT_VERSION;
7398 }
7399 }
7400 });
7401 }
7402
7403 if (rresolved == shell_shortcut_notresolved) {
7404 /* truename is the pathname itself */
7405 STACK_(2) = STACK_(3);
7406 /* nametomatch is direntry */
7407 STACK_(1) = STACK_(4);
7408 }
7409
7410 skipSTACK(1); /* drop direntry-type */
7411 /* stack layout: ..., pathname, dir_namestring, direntry,
7412 direntry-pathname, true-pathname, direntry-name-to-check. */
7413
7414 if (rresolved == shell_shortcut_notexists
7415 && dsp->if_none == DIR_IF_NONE_ERROR)
7416 error_file_not_exists();
7417
7418 if (rresolved != shell_shortcut_notexists
7419 || (dsp->if_none != DIR_IF_NONE_DISCARD
7420 && dsp->if_none != DIR_IF_NONE_IGNORE)) {
7421 if (READDIR_entry_ISDIR()
7422 || rresolved == shell_shortcut_directory) {
7423 /* nonfound shortcuts are treated as shortcuts to files */
7424 if (recursively) /* all recursive subdirectories wanted? */
7425 /* yes -> push truename onto pathnames-to-insert
7426 (it is inserted in front of pathname-list-rest later): */
7427 PUSH_ON_STACK(1,0+6);
7428 if (TASK_DIR_P(next_task)) {
7429 /* match (car subdir-list) with direntry: */
7430 if (wildcard_match(Car(STACK_(1+4+6)),STACK_0))
7431 /* Subdirectory matches -> push truename onto new-pathname-list: */
7432 PUSH_ON_STACK(1,3+6);
7433 }
7434 } else if (TASK_FILE_P(next_task)) { /* entry is a (halfway) normal file. */
7435 if (wildcard_match(STACK_(2+4+6),STACK_0)) {
7436 /* stack layout: ..., pathname, dir_namestring, direntry,
7437 direntry-maybhacked-pathname, true-pathname,
7438 direntry-name-to-check.
7439 test Full-Flag and poss. get more information: */
7440 if (dsp->full_p /* :FULL wanted? */
7441 && rresolved != shell_shortcut_notexists) { /* treat nonexisting as :FULL NIL */
7442 var decoded_time_t timepoint;
7443 var off_t entry_size;
7444 pushSTACK(STACK_(2)); /* newpathname as 1st list element */
7445 pushSTACK(STACK_(1+1)); /* resolved pathname as 2nd list element */
7446 /* get file attributes into timepoint & entry_size */
7447 if (rresolved == shell_shortcut_file) {
7448 /* need another readdir here */
7449 get_time_size(&STACK_0,&timepoint,&entry_size);
7450 } else { /* easy way */
7451 READDIR_entry_timedate(&timepoint);
7452 entry_size = READDIR_entry_size();
7453 }
7454 pack_full_info(&timepoint,&entry_size);
7455 PUSH_ON_STACK(0,4+4+6+2);
7456 skipSTACK(2); /* drop newname & full info list */
7457 } else PUSH_ON_STACK(1,4+4+6);
7458 }
7459 }
7460 }
7461 skipSTACK(4); /* forget all up to dir_namestring */
7462 }
7463 /* next file: */
7464 begin_blocking_system_call();
7465 READDIR_findnext({ end_blocking_system_call(); OS_file_error(STACK_1); }, break; );
7466 }
7467 } while (false);
7468 end_blocking_system_call();
7469 READDIR_end_declarations;
7470 });
7471 }
7472 #else
7473 #error directory_search_scandir is not defined
7474 #endif
7475 }
7476
directory_search(object pathname,dir_search_param_t * dsp)7477 local maygc object directory_search (object pathname, dir_search_param_t *dsp) {
7478 pathname = use_default_dir(pathname); /* insert default-directory */
7479 /* pathname is now new and an absolute pathname. */
7480 pushSTACK(NIL); /* result-list := NIL */
7481 pushSTACK(pathname);
7482 /* if name=NIL and type/=NIL: set name := "*". */
7483 if (nullp(ThePathname(pathname)->pathname_name)
7484 && !nullp(ThePathname(pathname)->pathname_type))
7485 ThePathname(pathname)->pathname_name = S(Kwild);
7486 /* for matching: separate slot for name&type: */
7487 if (nullp(ThePathname(pathname)->pathname_name)) {
7488 pushSTACK(NIL); /* name=NIL -> also type=NIL -> do not search files */
7489 } else {
7490 pushSTACK(pathname);
7491 }
7492 pushSTACK(ThePathname(pathname)->pathname_directory); /* subdir-list */
7493 /* copy pathname and thereby discard name and type and
7494 shorten directory to (:ABSOLUTE) resp. (:ABSOLUTE :ROOT) : */
7495 pathname = copy_pathname(pathname);
7496 ThePathname(pathname)->pathname_name = NIL;
7497 ThePathname(pathname)->pathname_type = NIL;
7498 ThePathname(pathname)->pathname_version = NIL;
7499 ThePathname(pathname)->pathname_directory = O(directory_absolute);
7500 pushSTACK(pathname);
7501 { /* pack into one-element list: */
7502 var object new_cons = allocate_cons();
7503 Car(new_cons) = STACK_0;
7504 STACK_0 = new_cons;
7505 }
7506 var bool recursively = /* Flag, if the next operation has to be applied */
7507 false; /* to all subdirectories. */
7508 while (1) {
7509 /* stack layout: result-list, pathname, name&type, subdir-list,
7510 pathname-list.
7511 result-list = list of finished pathnames/lists, reversed.
7512 name&type = NIL or pathname - against which the filenames are matched.
7513 pathname-list = list of directories to be processed.
7514 the pathnames from pathname-list contain the directory
7515 only so deep, that afterwards work continues with (cdr subdir-list) .
7516 process next subdir-level: */
7517 STACK_1 = Cdr(STACK_1); /* shorten subdir-list */
7518 var task_t next_task; /* what has to be done with the Dirs from pathname-list: */
7519 if (matomp(STACK_1)) { /* subdir-list finished? */
7520 var object nametype = STACK_2;
7521 if (nullp(nametype)) /* name=NIL and type=NIL -> do not search files */
7522 next_task = TASK_DONE;
7523 #if !defined(WIN32_NATIVE)
7524 else if (!(wild_p(ThePathname(nametype)->pathname_name,false)
7525 || (!nullp(ThePathname(nametype)->pathname_type)
7526 && wild_p(ThePathname(nametype)->pathname_type,false)))
7527 && (dsp->if_none != DIR_IF_NONE_IGNORE))
7528 next_task = TASK_FILE; /* search file */
7529 #endif
7530 else
7531 next_task = TASK_ALL_FILES; /* search files with wildcards */
7532 } else {
7533 var object next_subdir = Car(STACK_1);
7534 if (eq(next_subdir,S(Kwild_inferiors))) { /* '...' ? */
7535 /* will be treated at the next run */
7536 recursively = true; goto passed_subdir;
7537 }
7538 if (!wild_p(next_subdir,false))
7539 next_task = TASK_DIR; /* search subdir */
7540 else
7541 next_task = TASK_ALL_DIRS; /* search subdirs with wildcards */
7542 }
7543 /* traverse pathname-list and construct new list: */
7544 { pushSTACK(NIL); }
7545 if (dsp->circle_p) { /* query :CIRCLE-Flag */
7546 /* maintain hash-table of all scanned directories so far (as
7547 cons (dev . ino)) :
7548 (MAKE-HASH-TABLE :KEY-TYPE '(CONS INTEGER INTEGER) :VALUE-TYPE '(EQL T)
7549 :TEST 'EQUAL) */
7550 pushSTACK(S(Ktest)); pushSTACK(S(equal));
7551 funcall(L(make_hash_table),2);
7552 pushSTACK(value1);
7553 } else pushSTACK(NIL);
7554 pushSTACK(STACK_(0+2));
7555 while (1) {
7556 /* stack layout: ..., new-pathname-list, ht, pathname-list-rest. */
7557 var object pathname_list_rest = STACK_0;
7558 if (atomp(pathname_list_rest))
7559 break;
7560 STACK_0 = Cdr(pathname_list_rest); /* shorten list */
7561 pushSTACK(NIL); /* pathnames-to-insert := NIL */
7562 /* stack layout: ..., new-pathname-list, ht, pathname-list-rest,
7563 pathnames-to-insert. */
7564 {
7565 var object pathname = Car(pathname_list_rest); /* next directory */
7566 pushSTACK(pathname); /* into the stack */
7567 /* try to shorten the task a little: */
7568 if (!recursively) {
7569 switch (next_task) {
7570 case TASK_DONE: { /* return this directory pathname */
7571 ASSERT(namenullp(STACK_0));
7572 pushSTACK(copy_pathname(STACK_0));
7573 var struct file_status fs; file_status_init(&fs,&STACK_0);
7574 assure_dir_exists(&fs,false,false); /* first resolve links */
7575 if (dsp->full_p) /* assure_dir_exists does not fill fs_stat */
7576 with_stat_info_computed(&fs);
7577 /* and push STACK_0 in front of result-list: */
7578 PUSH_ON_STACK(0,4+4+2);
7579 skipSTACK(2);
7580 } goto next_pathname;
7581 #if !defined(WIN32_NATIVE)
7582 case TASK_FILE: { /* look in this pathname for a file */
7583 ThePathname(pathname)->pathname_name = /* insert name (/=NIL) */
7584 ThePathname(STACK_(3+4+1))->pathname_name;
7585 ThePathname(pathname)->pathname_type = /* insert type */
7586 ThePathname(STACK_(3+4+1))->pathname_type;
7587 ThePathname(pathname)->pathname_version =
7588 DEFAULT_VERSION; /* original may be :WILD! */
7589 pushSTACK(pathname);
7590 var struct file_status fs; file_status_init(&fs,&STACK_0);
7591 assure_dir_exists(&fs,true,true); /* resolve links, stat file */
7592 if (file_exists(&fs)) { /* if file exists */
7593 /* extend result-list: */
7594 if (dsp->full_p) /* :FULL wanted? */
7595 with_stat_info(&(fs.fs_stat)); /* yes -> extend STACK_0 */
7596 /* and push STACK_0 in front of result-list: */
7597 PUSH_ON_STACK(0,4+4+2);
7598 }
7599 skipSTACK(2);
7600 } goto next_pathname;
7601 #endif
7602 case TASK_DIR: { /* search for a subdirectory in this pathname */
7603 var struct file_status fs; file_status_init(&fs,&STACK_0);
7604 assure_dir_exists(&fs,true,false); /* resolve links, directory-namestring */
7605 pushSTACK(fs.fs_namestring); /* directory-namestring */
7606 {
7607 var object subdir = Car(STACK_(1+4+1+1)); /*(car subdir-list)*/
7608 SUBDIR_PUSHSTACK(subdir);
7609 }
7610 #if defined(WIN32_NATIVE)
7611 pushSTACK(O(backslash_string));
7612 fs.fs_namestring = string_concat(3); /* concatenate */
7613 #else
7614 fs.fs_namestring = string_concat(2);
7615 #endif
7616 /* get information: */
7617 directory_search_1subdir(&(STACK_(1+4+1)),fs.fs_namestring);
7618 skipSTACK(1);
7619 } goto next_pathname;
7620 default: ; /* do nothing */
7621 }
7622 }
7623 /* in order to finish the task, all entries in this directory
7624 have to be scanned: */
7625 {
7626 var struct file_status fs;
7627 pushSTACK(STACK_0);/*truename*/ file_status_init(&fs,&STACK_0);
7628 assure_dir_exists(&fs,false,false); /* resolve links, form directory-name */
7629 pushSTACK(fs.fs_namestring); /* save */
7630
7631 /* stack layout: ..., pathname, truename, dir_namestring. */
7632 if (dsp->circle_p) { /* query :CIRCLE flag */
7633 /* search pathname in the hash-table: */
7634 var object hashcode = directory_search_hashcode();
7635 if (eq(hashcode,nullobj)) {
7636 /* entry does not exist, however (this can happen to us
7637 only for symbolic links) -> will be skipped */
7638 skipSTACK(3); goto next_pathname;
7639 }
7640 /* and locate in the hash-table and store: */
7641 if (!nullp(shifthash(STACK_(2+3),hashcode,T,true))) {
7642 /* was already inside -> will be skipped */
7643 skipSTACK(3); goto next_pathname;
7644 }
7645 }
7646 if (next_task==TASK_DONE) { /* push pathname STACK_1 in front of result-list: */
7647 if (dsp->full_p) { /* assure_dir_exists does not fill fs_stat */
7648 pushSTACK(STACK_2);
7649 pushSTACK(STACK_(1+1));
7650 with_stat_info_computed(&fs);
7651 STACK_(1+2) = STACK_0;
7652 skipSTACK(2);
7653 }
7654 PUSH_ON_STACK(1,4+4+3);
7655 if (dsp->full_p)
7656 STACK_1 = Car(STACK_1);
7657 }
7658 }
7659 STACK_2 = STACK_1; STACK_1 = STACK_0; skipSTACK(1); /* drop pathname */
7660 directory_search_scandir(recursively,next_task,dsp);
7661 skipSTACK(2); /* forget pathname and dir_namestring */
7662 next_pathname: ;
7663 }
7664 /* stack layout: ..., new-pathname-list, ht, pathname-list-rest, pathnames-to-insert.
7665 Before advancing with pathname-list-rest :
7666 pathname-list-rest := (nreconc pathnames-to-insert pathname-list-rest): */
7667 var object pathnames_to_insert = popSTACK();
7668 STACK_0 = nreconc(pathnames_to_insert,STACK_0);
7669 }
7670 skipSTACK(2); /* forget empty pathname-list-rest and hash-table */
7671 { /* reverse new-pathname-list, replaces the emptied pathname-list: */
7672 var object new_pathname_list = popSTACK();
7673 STACK_0 = nreverse(new_pathname_list); /* new pathname-list */
7674 }
7675 /* we are finished with this subdir-stage. */
7676 if (matomp(STACK_1))
7677 break; /* (atom subdir-list) -> finished. */
7678 recursively = false; /* the next (preliminarily) non-recursive */
7679 passed_subdir: ;
7680 }
7681 /* stack layout: result-list pathname name&type subdir-list pathname-list
7682 subdir-list became =NIL , also pathname-list = NIL (because at the last
7683 loop iteration next_task is always DONE, FILE or ALL_FILES, so nothing
7684 was pushed on new-pathname-list). */
7685 skipSTACK(4);
7686 return popSTACK(); /* result-list as result */
7687 }
7688 #endif /* PATHNAME_NOEXT */
7689
7690 /* (DIRECTORY pathname [:circle] [:full] [:if-does-not-exist]),
7691 CLTL p. 427 */
7692 LISPFUN(directory,seclass_rd_sig,1,0,norest,key,3,
7693 ( kw(if_does_not_exist),kw(circle),kw(full) ))
7694 { /* stack layout: pathname, if-does-not-exist, circle, full. */
7695 var dir_search_param_t dsp;
7696 if (!boundp(STACK_2) || eq(STACK_2,S(Kdiscard)))
7697 /* :IF-DOES-NOT-EXIST defaults to :DISCARD */
7698 dsp.if_none = DIR_IF_NONE_DISCARD;
7699 else if (eq(STACK_2,S(Kerror)))
7700 dsp.if_none = DIR_IF_NONE_ERROR;
7701 else if (eq(STACK_2,S(Kkeep)))
7702 dsp.if_none = DIR_IF_NONE_KEEP;
7703 else if (eq(STACK_2,S(Kignore)))
7704 dsp.if_none = DIR_IF_NONE_IGNORE;
7705 else error_illegal_arg(STACK_2,O(type_directory_not_exist),
7706 S(Kif_does_not_exist));
7707 dsp.circle_p = !missingp(STACK_1); /* :CIRCLE argument defaults to NIL */
7708 dsp.full_p = !missingp(STACK_0); /* :FULL argument defaults to NIL */
7709 skipSTACK(3);
7710 /* check pathname-argument: */
7711 var object pathname = merge_defaults(coerce_pathname(STACK_0));
7712 /* let's go: */
7713 #ifdef PATHNAME_WIN32
7714 if (eq(ThePathname(pathname)->pathname_device,S(Kwild))) {
7715 /* Device = :WILD ? ==> scan all devices */
7716 STACK_0 = pathname;
7717 pushSTACK(NIL); /* pathname-list := NIL */
7718 { /* stack layout: pathname, pathname-list. */
7719 var char drive;
7720 for (drive='A'; drive<='Z'; drive++) /* traverse all drives */
7721 if (good_drive(drive)) {
7722 var object newpathname = copy_pathname(STACK_1);
7723 ThePathname(newpathname)->pathname_device =
7724 /* take over the device = one-element drive string */
7725 n_char_to_string(&drive,1,O(pathname_encoding));
7726 /* search within a drive: */
7727 var object newpathnames = directory_search(newpathname,&dsp);
7728 /* and attach pathname-list in front of STACK_0: */
7729 STACK_0 = nreconc(newpathnames,STACK_0);
7730 }
7731 }
7732 VALUES1(nreverse(STACK_0)); /* reverse pathname-list again */
7733 skipSTACK(2);
7734 } else
7735 /* only one device to scan */
7736 #endif
7737 {
7738 VALUES1(directory_search(pathname,&dsp)); /* form matching pathnames */
7739 skipSTACK(1);
7740 }
7741 }
7742
7743 /* UP: make sure that the supposed directory namestring ends with a slash
7744 returns a new string with a slash appended or the same stirng
7745 can trigger GC */
ensure_last_slash(object dir_string)7746 local maygc object ensure_last_slash (object dir_string) {
7747 ASSERT(stringp(dir_string));
7748 var uintL len, offset;
7749 var object str = unpack_string_ro(dir_string,&len,&offset);
7750 var chart ch = schar(str,len+offset-1);
7751 if (!pslashp(ch) && !lslashp(ch)) {
7752 var char sl = (looks_logical_p(dir_string) ? ';' : slash);
7753 with_sstring_0(str,O(pathname_encoding),asciz, {
7754 dir_string = asciz_add_char(asciz,len,sl,O(pathname_encoding));
7755 });
7756 }
7757 return dir_string;
7758 }
7759
7760 /* (CD [pathname]) sets the current drive and the current directory. */
7761 LISPFUN(cd,seclass_default,0,1,norest,nokey,0,NIL) {
7762 var object pathname = popSTACK();
7763 if (!boundp(pathname)) { pathname = O(empty_string); } /* "" */
7764 else if (stringp(pathname)) /* make sure it ends with a slash */
7765 pathname = ensure_last_slash(pathname);
7766 pathname = copy_pathname(coerce_pathname(pathname)); /* --> pathname */
7767 ThePathname(pathname)->pathname_name = NIL;
7768 ThePathname(pathname)->pathname_type = NIL;
7769 pushSTACK(pathname);
7770 var struct file_status fs; file_status_init(&fs,&STACK_0);
7771 true_namestring(&fs,false,false); /* the directory must exist */
7772 change_default(); /* set default drive and default directory */
7773 VALUES1(popSTACK()); /* new pathname as the value */
7774 }
7775 #undef lslashp
7776 #undef pslashp
7777
7778 /* UP: checks a pathname, if both name and type are =NIL ,
7779 and if the directory "almost" exists.
7780 shorter_directory(pathname,resolve_links)
7781 > pathname : Pathname-Argument
7782 > resolve_links : flag, if links have to be resolved (usually yes)
7783 < -(STACK) : absolute pathname */
7784 #ifdef WIN32_NATIVE
7785 /* < result: Directory-Namestring (for the OS, without '\' at the end, Normal-Simple-String) */
7786 #endif
7787 #ifdef UNIX
7788 /* < result: Directory-Namestring (for the OS, without '/' at the end, Normal-Simple-String) */
7789 #endif
7790 /* decrements STACK by 1.
7791 can trigger GC */
shorter_directory(object pathname,bool resolve_links)7792 local maygc object shorter_directory (object pathname, bool resolve_links) {
7793 pathname = merge_defaults(coerce_pathname(pathname)); /* --> pathname */
7794 check_no_wildcards(pathname); /* with wildcards -> error */
7795 pathname = use_default_dir(pathname); /* insert default-directory */
7796 check_notdir(pathname); /* ensure that Name=NIL and Type=NIL */
7797 pushSTACK(pathname); /* save new pathname */
7798 /* shorten the directory: */
7799 var object subdirs = ThePathname(pathname)->pathname_directory;
7800 if (nullp(Cdr(subdirs))) { /* root-directory ? */
7801 /* STACK_0 = pathname, FILE-ERROR slot PATHNAME */
7802 pushSTACK(STACK_0);
7803 error(file_error,GETTEXT("root directory not allowed here: ~S"));
7804 }
7805 subdirs = reverse(subdirs); /* copy list and reverse */
7806 pushSTACK(subdirs); /* save cons with last subdir as CAR */
7807 subdirs = Cdr(subdirs); /* all subdirs except for the last */
7808 subdirs = nreverse(subdirs); /* bring into right order again */
7809 pathname = STACK_1;
7810 ThePathname(pathname)->pathname_directory = subdirs; /* and store in the pathname */
7811 /* this directory must exist: */
7812 pushSTACK(pathname);
7813 /* stack layout: pathname, subdircons, pathname. */
7814 var struct file_status fs; file_status_init(&fs,&STACK_0);
7815 assure_dir_exists(&fs,!resolve_links,false);
7816 /* build subdir-string for the operating system: */
7817 STACK_0 = fs.fs_namestring; /* directory-namestring so far as 1st String */
7818 var uintC stringcount = /* the strings in the last subdir */
7819 subdir_namestring_parts(STACK_1,false);
7820 /* and no '\' at the end (for the OS)
7821 and no '/' at the end (for the OS) */
7822 var object dirstring = string_concat(1+stringcount); /* concatenate */
7823 skipSTACK(1);
7824 return dirstring;
7825 }
7826
7827 LISPFUNN(make_directory,1)
7828 { /* (MAKE-DIRECTORY pathname) makes a new subdirectory pathname. */
7829 var object pathstring = shorter_directory(STACK_0,true);
7830 with_sstring_0(pathstring,O(pathname_encoding),pathstring_asciz, {
7831 make_directory(pathstring_asciz);
7832 });
7833 skipSTACK(2);
7834 VALUES1(T);
7835 }
7836
7837 LISPFUNN(delete_directory,1)
7838 { /* (DELETE-DIRECTORY pathname) removes the subdirectory pathname. */
7839 var object pathstring = shorter_directory(STACK_0,true);
7840 with_sstring_0(pathstring,O(pathname_encoding),pathstring_asciz, {
7841 delete_directory(pathstring_asciz);
7842 });
7843 skipSTACK(2);
7844 VALUES1(T);
7845 }
7846
7847 LISPFUNN(rename_directory,2)
7848 { /* (RENAME-DIRECTORY dirname newname) renames an existing directory. */
7849 var object newdir = shorter_directory(STACK_0,true); STACK_0 = newdir;
7850 var object olddir = shorter_directory(STACK_2,true); STACK_2 = olddir;
7851 rename_existing_path(STACK_2,STACK_1);
7852 skipSTACK(4);
7853 VALUES1(T);
7854 }
7855
7856 /* (defun ensure-directories-exist (pathspec &key verbose)
7857 (let* ((dir (pathname-directory pathspec))
7858 (path (make-pathname :host (pathname-host pathspec)
7859 :device (pathname-device pathspec)
7860 :directory dir)))
7861 (when (wild-pathname-p path)
7862 (error (make-condition (quote file-error) :pathname pathspec)))
7863 (if (directory path)
7864 (values pathspec nil)
7865 (loop for i from 1 upto (length dir)
7866 for newpath = (make-pathname :host (pathname-host pathspec)
7867 :device (pathname-device pathspec)
7868 :directory (subseq dir 0 i))
7869 unless (directory newpath)
7870 do (let ((namestring (namestring newpath)))
7871 (when verbose
7872 (format *standard-output* "~&Creating directory: ~A~%"
7873 namestring))
7874 (ignore-errors (ext:make-directory namestring))
7875 (unless (directory newpath)
7876 (error (make-condition (quote file-error)
7877 :pathname pathspec))))))
7878 finally (return (values pathspec t)))) */
7879 LISPFUN(ensure_directories_exist,seclass_default,1,0,norest,key,1,
7880 (kw(verbose))) {
7881 var object pathname = coerce_pathname(STACK_1);
7882 pathname = merge_defaults(pathname); /* copy and discard name, type */
7883 ThePathname(pathname)->pathname_name = NIL;
7884 ThePathname(pathname)->pathname_type = NIL;
7885 check_no_wildcards(pathname); /* with wildcards -> error */
7886 pathname = use_default_dir(pathname); /* insert default-directory */
7887 pushSTACK(pathname); /* save new pathname */
7888 /* stack layout: pathspec, verbose, pathname. */
7889 if (directory_exists(pathname)) {
7890 skipSTACK(2); value2 = NIL; /* pathspec, NIL as values */
7891 } else {
7892 var object subdirs = copy_list(ThePathname(STACK_0)->pathname_directory);
7893 pushSTACK(subdirs); pushSTACK(Cdr(subdirs));
7894 Cdr(subdirs) = NIL;
7895 ThePathname(STACK_2)->pathname_directory = subdirs;
7896 /* stack layout: pathspec, verbose, pathname, (car (last subdirs)),
7897 remaining_subdirs. */
7898 while (mconsp(STACK_0)) {
7899 subdirs = STACK_0;
7900 Cdr(STACK_1) = subdirs; STACK_1 = subdirs; STACK_0 = Cdr(subdirs); Cdr(subdirs) = NIL;
7901 if (!directory_exists(STACK_2)) {
7902 if (!missingp(STACK_3)) { /* Verbose? */
7903 funcall(L(fresh_line),0); /* (FRESH-LINE [*standard-output*]) */
7904 pushSTACK(CLSTEXT("Creating directory: ")); funcall(L(write_string),1); /* (WRITE-STRING "..." [*standard-output*]) */
7905 pushSTACK(STACK_2); funcall(L(princ),1); /* (PRINC pathname [*standard-output*]) */
7906 funcall(L(terpri),0); /* (TERPRI [*standard-output*]) */
7907 }
7908 /* side remark: Do not need to resolve links here, because here we
7909 proceed step by step starting at the root, anyway. */
7910 var object pathstring = shorter_directory(STACK_2,false);
7911 with_sstring_0(pathstring,O(pathname_encoding),pathstring_asciz, {
7912 make_directory(pathstring_asciz);
7913 });
7914 skipSTACK(1);
7915 }
7916 }
7917 skipSTACK(4); value2 = T; /* pathspec, T as values */
7918 }
7919 value1 = popSTACK(); mv_count=2;
7920 }
7921
7922 #ifdef UNIX
7923 /* Returns the struct passwd entry for the current user.
7924 The return value points to static data, or is NULL upon failure. */
unix_user_pwd(void)7925 local struct passwd * unix_user_pwd (void) {
7926 var const char* username;
7927 var struct passwd * userpasswd = NULL;
7928 /* The Solaris manpage for GETLOGIN(3V) recommended
7929 first getpwnam(getlogin()), then getpwuid(getuid()).
7930 But getlogin() is too unreliable in general: it is sensitive to the
7931 controlling tty, stdin redirection, and has bugs with user names longer
7932 than 8 characters. */
7933 begin_system_call();
7934 /* 1. attempt: getpwnam(getenv("USER")) */
7935 username = getenv("USER");
7936 if (username != NULL) {
7937 errno = 0; userpasswd = getpwnam(username);
7938 if (userpasswd != NULL) goto ok;
7939 if (errno != 0) { OS_error(); }
7940 }
7941 /* 2. don't attempt: getpwnam(getlogin()) */
7942 /* 3. attempt: getpwuid(getuid()) */
7943 errno = 0; userpasswd = getpwuid(getuid());
7944 if (userpasswd != NULL) goto ok;
7945 if (errno != 0) { OS_error(); }
7946 /* Everything fails, userpasswd == NULL. */
7947 ok:
7948 end_system_call();
7949 return userpasswd;
7950 }
7951 #endif
7952
7953 /* UP: Initializes the pathname-system.
7954 init_pathnames();
7955 can trigger GC */
init_pathnames(void)7956 global maygc void init_pathnames (void) {
7957 #ifdef PATHNAME_WIN32
7958 { /* initialize default-drive: */
7959 var char drive = default_drive();
7960 O(default_drive) =
7961 (drive == 0 ? NIL /* network */
7962 : n_char_to_string(&drive,1,O(pathname_encoding))); /* local device */
7963 }
7964 #endif
7965 /* initialize *DEFAULT-PATHNAME-DEFAULTS* : */
7966 recalc_defaults_pathname();
7967 #if defined(UNIX)
7968 /* we retrieve the home-directory and the usable shell from the
7969 environment. It contains (almost) always at least the following variables:
7970 LOGNAME = Username at the first login ("true" identity of the user)
7971 USER = current username
7972 HOME = current home-directory, fetched from /etc/passwd
7973 SHELL = current standard-shell, fetched from /etc/passwd
7974 PATH = search path for program call
7975 TERM = terminal emulation
7976 we retrieve HOME (for "~" - translation) and SHELL (for EXECUTE).
7977 For "~username" we must scan the /etc/passwd - file. */
7978 { /* search in the environment for variable HOME: */
7979 begin_system_call();
7980 var const char* homedir = getenv("HOME");
7981 end_system_call();
7982 if (homedir != NULL) { /* found? */
7983 O(user_homedir) = asciz_dir_to_pathname(homedir,O(misc_encoding)); /* yes -> enter */
7984 } else { /* no -> get home-directory from the passwort-file: */
7985 var struct passwd * userpasswd = unix_user_pwd();
7986 if (userpasswd != NULL) { /* no -> enter homedir as pathname */
7987 O(user_homedir) = asciz_dir_to_pathname(userpasswd->pw_dir,O(misc_encoding));
7988 } else { /* no -> take current directory: */
7989 O(user_homedir) = default_directory();
7990 }
7991 }
7992 }
7993 #elif defined(WIN32)
7994 /* WinNT defines HOMEDRIVE and HOMEPATH. Win95 (which is actually not a
7995 multiuser OS) lets the user set HOME himself.
7996 In any case, we give preference to HOME, because the user can change this. */
7997 {
7998 var const char * home;
7999 begin_system_call();
8000 home = getenv("HOME");
8001 if (home != NULL) {
8002 end_system_call();
8003 O(user_homedir) = asciz_dir_to_pathname(home,O(misc_encoding));
8004 } else {
8005 var const char * homedrive = getenv("HOMEDRIVE");
8006 var const char * homepath = getenv("HOMEPATH");
8007 end_system_call();
8008 if (homedrive!=NULL && homepath!=NULL) {
8009 var DYNAMIC_ARRAY(homeall,char,asciz_length(homedrive)+asciz_length(homepath)+1);
8010 var char* ptr = homeall;
8011 while ((*ptr = *homedrive) != '\0') { homedrive++; ptr++; }
8012 while ((*ptr = *homepath) != '\0') { homepath++; ptr++; }
8013 *ptr = '\0';
8014 O(user_homedir) = asciz_dir_to_pathname(homeall,O(misc_encoding));
8015 FREE_DYNAMIC_ARRAY(homeall);
8016 } else {
8017 O(user_homedir) = use_default_dir(asciz_dir_to_pathname(".",Symbol_value(S(ascii))));
8018 }
8019 }
8020 }
8021 #else /* !UNIX & !WIN32 */
8022 #error O(user_homedir) not initialized
8023 #endif
8024 #if defined(UNIX)
8025 /* the command-shell O(command_shell) remains unchanged, otherwise
8026 we get too many portability problems. */
8027 { /* search the environment for variable SHELL: */
8028 begin_system_call();
8029 var const char* shell = getenv("SHELL");
8030 end_system_call();
8031 if (shell != NULL) { /* found? ==> enter */
8032 O(user_shell) = asciz_to_string(shell,O(misc_encoding));
8033 }
8034 /* else O(user_shell) remains on the default value "/bin/csh". */
8035 }
8036 #elif defined(WIN32_NATIVE)
8037 { /* search in the environment for variable COMSPEC: */
8038 begin_system_call();
8039 var const char* shell = getenv("COMSPEC");
8040 if (!(shell==NULL)) {
8041 end_system_call();
8042 O(command_shell) = asciz_to_string(shell,O(misc_encoding)); /* enter */
8043 } else {
8044 var OSVERSIONINFO v;
8045 v.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
8046 if (!GetVersionEx(&v)) { OS_error(); }
8047 if (v.dwPlatformId == VER_PLATFORM_WIN32_NT) { /* Windows NT */
8048 shell = "cmd.exe";
8049 } else { /* Windows 95 or else */
8050 shell = "command.com";
8051 }
8052 end_system_call();
8053 O(command_shell) = ascii_to_string(shell); /* enter */
8054 }
8055 }
8056 #else
8057 #error O(command_shell) not initialized
8058 #endif
8059 }
8060
8061 LISPFUNNS(file_write_date,1)
8062 { /* (FILE-WRITE-DATE file), CLTL p. 424 */
8063 #if defined(UNIX)
8064 var time_t file_datetime; /* buffer for date/time of a file */
8065 #elif defined(WIN32_NATIVE)
8066 var WIN32_FIND_DATA filedata;
8067 #else
8068 #error FILE-WRITE-DATE is not defined
8069 #endif
8070 var gcv_object_t *pathname = &STACK_0; /* pathname-argument */
8071 if (builtin_stream_p(*pathname)) { /* stream -> treat extra: */
8072 /* must be file-stream: */
8073 *pathname = as_file_stream(*pathname);
8074 /* streamtype file-stream */
8075 if ((TheStream(*pathname)->strmflags & strmflags_open_B)
8076 && (!nullp(TheStream(*pathname)->strm_buffered_channel))) {
8077 /* open file-stream
8078 work with the handle directly: */
8079 #if defined(UNIX)
8080 var struct stat status;
8081 begin_blocking_system_call();
8082 if (!( fstat(TheHandle(TheStream(*pathname)->strm_buffered_channel),&status) ==0)) {
8083 end_blocking_system_call(); OS_filestream_error(*pathname);
8084 }
8085 end_blocking_system_call();
8086 file_datetime = status.st_mtime;
8087 #elif defined(WIN32_NATIVE)
8088 var BY_HANDLE_FILE_INFORMATION fileinfo;
8089 var BOOL result;
8090 begin_blocking_system_call();
8091 result = GetFileInformationByHandle(TheHandle(TheStream(*pathname)->strm_buffered_channel),&fileinfo);
8092 end_blocking_system_call();
8093 if (result) {
8094 filedata.ftCreationTime = fileinfo.ftCreationTime;
8095 filedata.ftLastAccessTime = fileinfo.ftLastAccessTime;
8096 filedata.ftLastWriteTime = fileinfo.ftLastWriteTime;
8097 } else { /* If that failed, try the full pathname. */
8098 *pathname = file_stream_truename(*pathname);
8099 goto is_pathname;
8100 }
8101 #else
8102 #error FILE-WRITE-DATE is not defined
8103 #endif
8104 } else {
8105 /* closed file-stream -> use truename as pathname */
8106 *pathname = file_stream_truename(*pathname);
8107 goto is_pathname;
8108 }
8109 } else { /* turn into a pathname */
8110 *pathname = merge_defaults(coerce_pathname(*pathname));
8111 is_pathname: { /* pathname is now really a pathname */
8112 var struct file_status fs; file_status_init(&fs,&STACK_0);
8113 true_namestring(&fs,true,false);
8114 #if defined(UNIX)
8115 if (!file_exists(&fs)) { error_file_not_exists(); } /* file must exist */
8116 file_datetime = fs.fs_stat.st_mtime;
8117 #elif defined(WIN32_NATIVE)
8118 /* Only a directory search gives us the times. */
8119 with_sstring_0(fs.fs_namestring,O(pathname_encoding),namestring_asciz, {
8120 find_first_file(namestring_asciz,&filedata);
8121 });
8122 #else
8123 #error FILE-WRITE-DATE is not defined
8124 #endif
8125 }
8126 }
8127 skipSTACK(1); /* pathname */
8128 /* date/time no is in the buffer file_datetime.
8129 convert into Universal-Time-Format: */
8130 #if defined(UNIX)
8131 VALUES1(convert_time_to_universal(&file_datetime));
8132 #elif defined(WIN32_NATIVE)
8133 VALUES1(convert_time_to_universal(FIND_DATA_FWD(filedata)));
8134 #else
8135 #error FILE-WRITE-DATE is not defined
8136 #endif
8137 }
8138
8139 LISPFUNNS(file_author,1)
8140 { /* (FILE-AUTHOR file), CLTL p. 424 */
8141 var object pathname = popSTACK(); /* pathname-argument */
8142 if (builtin_stream_p(pathname)) { /* stream -> treat extra: */
8143 /* must be file-stream: */
8144 pathname = as_file_stream(pathname);
8145 /* streamtype file-stream */
8146 if (TheStream(pathname)->strmflags & strmflags_open_B) {
8147 /* open file-stream -> OK */
8148 } else { /* closed file-stream -> use truename as pathname */
8149 pathname = file_stream_truename(pathname);
8150 goto is_pathname;
8151 }
8152 } else {
8153 pathname = merge_defaults(coerce_pathname(pathname)); /* --> pathname */
8154 is_pathname: { /* pathname is now really a pathname */
8155 pushSTACK(pathname);
8156 var struct file_status fs; file_status_init(&fs,&STACK_0);
8157 true_namestring(&fs,true,false);
8158 if (!file_exists(&fs)) { error_file_not_exists(); } /* file must exist */
8159 skipSTACK(1);
8160 }
8161 }
8162 /* file exists -> NIL as value */
8163 VALUES1(NIL);
8164 }
8165
8166 #ifdef UNIX
8167
8168 /* UP: unblocks blocked signals - called in child (fork-ed) process
8169 (probably relevant only in MT builds but does not harm single thread) */
unblock_all_signals()8170 local inline void unblock_all_signals() {
8171 var sigset_t sigblock_mask;
8172 sigprocmask(SIG_BLOCK, NULL, &sigblock_mask); /* get */
8173 sigprocmask(SIG_UNBLOCK, &sigblock_mask, NULL); /* unblock */
8174 }
8175
8176 LISPFUN(execute,seclass_default,1,0,rest,nokey,0,NIL)
8177 { /* (EXECUTE file arg1 arg2 ...) calls a file with the given arguments. */
8178 var gcv_object_t* args_pointer = rest_args_pointer STACKop 1;
8179 {
8180 var gcv_object_t* argptr = args_pointer; /* Pointer to the arguments */
8181 { /* check file: */
8182 var gcv_object_t* file_ = &NEXT(argptr);
8183 /* we do not do follow symlinks here because some programs dispatch on
8184 argv[0], which could be a symbolic link.
8185 E.g., in Maemo Fremantle OS used in Nokia N900 phone,
8186 /bin/sh is a symlink to busybox, so RUN-PROGRAM which calls SHELL
8187 which calls EXECUTE fails because EXECUTE calls "busybox -c"
8188 instead of "/bin/sh -c".
8189 <https://sourceforge.net/p/clisp/mailman/message/24434416/>
8190 <http://article.gmane.org/gmane.lisp.clisp.devel/21219> */
8191 /* convert thet file to string, existence is checked by execv(2): */
8192 *file_ = physical_namestring(*file_);
8193 *file_ = string_to_asciz(*file_,O(misc_encoding));
8194 }
8195 { /* check the other arguments: */
8196 var uintC count;
8197 dotimesC(count,argcount, {
8198 var gcv_object_t* arg_ = &NEXT(argptr);
8199 pushSTACK(*arg_); funcall(L(string),1); /* convert next argument into a string */
8200 *arg_ = string_to_asciz(value1,O(misc_encoding)); /* and convert ASCIZ-string */
8201 });
8202 }
8203 }
8204 { /* build up argv-Array in stack and copy strings in the stack: */
8205 var uintL argvdata_length = 0;
8206 {
8207 var gcv_object_t* argptr = args_pointer;
8208 var uintC count;
8209 dotimespC(count,argcount+1, {
8210 var object arg = NEXT(argptr); /* next argument, ASCIZ-string */
8211 argvdata_length += Sbvector_length(arg);
8212 });
8213 }
8214 var DYNAMIC_ARRAY(argv,char*,1+(uintL)argcount+1);
8215 var DYNAMIC_ARRAY(argvdata,char,argvdata_length);
8216 {
8217 var gcv_object_t* argptr = args_pointer;
8218 var char** argvptr = &argv[0];
8219 var char* argvdataptr = &argvdata[0];
8220 var uintC count;
8221 dotimespC(count,argcount+1, {
8222 var object arg = NEXT(argptr); /* next argument, ASCIZ-string */
8223 var char* ptr = TheAsciz(arg);
8224 var uintL len = Sbvector_length(arg);
8225 *argvptr++ = argvdataptr; /* fill into argv */
8226 dotimespL(len,len, { *argvdataptr++ = *ptr++; } ); /* and copy */
8227 });
8228 *argvptr = NULL; /* and conclude with nullpointer */
8229 }
8230 { /* start a new process: */
8231 var int child;
8232 begin_system_call();
8233 begin_want_sigcld();
8234 if ((child = vfork()) ==0) {
8235 /* this program part is executed by the child-process: */
8236 close_all_fd();
8237 unblock_all_signals();
8238 execv(argv[0],argv); /* call program */
8239 _exit(-1); /* if this fails, end the child-process */
8240 }
8241 /* this program part is executed by the caller: */
8242 if (child==-1) {
8243 /* something failed, either in vfork or in execv.
8244 either way errno was set. */
8245 end_want_sigcld(); end_system_call(); OS_error();
8246 }
8247 /* wait, until the child-process is finished: */
8248 var int status;
8249 GC_SAFE_CALL(status = wait2(child));
8250 /* cf. WAIT(2V) and #include <sys/wait.h> :
8251 WIFSTOPPED(status) == ((status & 0xFF) == 0177)
8252 WEXITSTATUS(status) == ((status & 0xFF00) >> 8) */
8253 end_want_sigcld();
8254 end_system_call();
8255 /* finished. */
8256 set_args_end_pointer(args_pointer); /* clean up STACK */
8257 if (WIFEXITED(status)) {
8258 /* process ended normally (without signal, without core-dump) */
8259 var int exitcode = WEXITSTATUS(status);
8260 VALUES1(exitcode ? fixnum(exitcode) : NIL); /* exit-status as value */
8261 } else /* minus signal as value */
8262 VALUES1(WIFSIGNALED(status)
8263 ? negfixnum(- WTERMSIG(status))
8264 : negfixnum(- WSTOPSIG(status)));
8265 }
8266 FREE_DYNAMIC_ARRAY(argvdata);
8267 FREE_DYNAMIC_ARRAY(argv);
8268 }
8269 }
8270
8271 #endif
8272
8273 /* Duplicate an open file handle.
8274 handle_dup(oldfd)
8275 Similar to dup(oldfd), with error checking.
8276 To be called only inside begin/end_system_call(). */
handle_dup(Handle old_handle)8277 modexp Handle handle_dup (Handle old_handle) {
8278 Handle new_handle;
8279 #if defined(UNIX)
8280 new_handle = dup(old_handle);
8281 if (new_handle < 0) { OS_error(); }
8282 #elif defined(WIN32_NATIVE)
8283 new_handle = INVALID_HANDLE_VALUE;
8284 if (!DuplicateHandle(GetCurrentProcess(),old_handle,
8285 GetCurrentProcess(),&new_handle,
8286 0, true, DUPLICATE_SAME_ACCESS))
8287 OS_error();
8288 #else
8289 #error handle_dup is not defined
8290 #endif
8291 return new_handle;
8292 }
8293
8294 /* Duplicate an open file handle.
8295 handle_dup2(oldfd,newfd)
8296 Similar to dup2(oldfd,newfd), with error checking. The result may or may not
8297 be equal to newfd.
8298 To be called only inside begin/end_system_call(). */
handle_dup2(Handle old_handle,Handle new_handle)8299 modexp Handle handle_dup2 (Handle old_handle, Handle new_handle) {
8300 #if defined(UNIX)
8301 new_handle = dup2(old_handle,new_handle);
8302 if (new_handle < 0) { OS_error(); }
8303 #elif defined(WIN32_NATIVE)
8304 if (!DuplicateHandle(GetCurrentProcess(),old_handle,
8305 GetCurrentProcess(),&new_handle,
8306 0, true, DUPLICATE_SAME_ACCESS))
8307 OS_error();
8308 #else
8309 #error handle_dup2 is not defined
8310 #endif
8311 return new_handle;
8312 }
8313
8314 /* (SHELL) calls a shell.
8315 (SHELL command) calls a shell and lets it execute a command. */
8316
8317 #if defined(WIN32_NATIVE)
8318
8319 /* (SYSTEM::SHELL-NAME) returns the name of the command shell. */
8320 LISPFUNN(shell_name,0) {
8321 VALUES1(O(command_shell));
8322 }
8323
8324 LISPFUN(shell,seclass_default,0,1,norest,nokey,0,NIL) {
8325 var object command = popSTACK();
8326 if (missingp(command))
8327 command = O(command_shell);
8328 command = check_string(command);
8329 var HANDLE prochandle;
8330 with_string_0(command,O(misc_encoding),command_asciz, {
8331 /* Start new process. */
8332 var HANDLE stdinput;
8333 var HANDLE stdoutput;
8334 var HANDLE stderror;
8335 var PROCESS_INFORMATION pinfo;
8336 begin_system_call();
8337 stdinput = GetStdHandle(STD_INPUT_HANDLE);
8338 if (stdinput == INVALID_HANDLE_VALUE) { OS_error(); }
8339 stdoutput = GetStdHandle(STD_OUTPUT_HANDLE);
8340 if (stdoutput == INVALID_HANDLE_VALUE) { OS_error(); }
8341 stderror = GetStdHandle(STD_ERROR_HANDLE);
8342 if (stderror == INVALID_HANDLE_VALUE) { OS_error(); }
8343 if (!MyCreateProcess(command_asciz,stdinput,stdoutput,stderror,&pinfo))
8344 { OS_error(); }
8345 if (pinfo.hThread && !CloseHandle(pinfo.hThread)) { OS_error(); }
8346 prochandle = pinfo.hProcess;
8347 });
8348 /* Wait until it terminates, get its exit status code. */
8349 var DWORD exitcode;
8350 GC_SAFE_CALL(exitcode = WaitForSingleObject(prochandle,INFINITE));
8351 switch (exitcode) {
8352 case WAIT_FAILED:
8353 OS_error();
8354 case WAIT_OBJECT_0:
8355 break;
8356 default: NOTREACHED;
8357 }
8358 if (!GetExitCodeProcess(prochandle,&exitcode)) { OS_error(); }
8359 if (!CloseHandle(prochandle)) { OS_error(); }
8360 end_system_call();
8361 /* use return value: =0 (OK) -> NIL, >0 (not OK) -> exitcode */
8362 VALUES1(exitcode == 0 ? NIL : fixnum(exitcode));
8363 }
8364
8365 #elif defined(UNIX)
8366
8367 LISPFUN(shell,seclass_default,0,1,norest,nokey,0,NIL) {
8368 var object command = popSTACK();
8369 if (missingp(command)) {
8370 /* execute (EXECUTE shell) : */
8371 pushSTACK(O(user_shell)); /* Shell-Name */
8372 funcall(L(execute),1);
8373 } else {
8374 /* call (EXECUTE shell "-c" command): */
8375 pushSTACK(O(command_shell)); /* shell name */
8376 pushSTACK(O(command_shell_option)); /* shell option "-c" */
8377 pushSTACK(command);
8378 funcall(L(execute),3);
8379 }
8380 }
8381
8382 #else
8383 #error EXT:SHELL is not defined
8384 #endif
8385
8386 /* stringlist_to_ascizlist (stringlist, encoding)
8387 convert a stringlist to list of asciz strings
8388 and places it on the stack.
8389 returns total length of all asciz strings including zeros
8390 and listlength (if the pointer is not NULL)
8391 adds one element to STACK
8392 can trigger GC */
8393 #if !defined(ENABLE_UNICODE)
8394 #define stringlist_to_ascizlist(s,e,l) stringlist_to_ascizlist_(s,l)
stringlist_to_ascizlist_(object stringlist,uintL * listlength)8395 local maygc int stringlist_to_ascizlist_ (object stringlist,uintL *listlength)
8396 #else
8397 local maygc int stringlist_to_ascizlist (object stringlist,
8398 gcv_object_t *encoding_,
8399 uintL *listlength)
8400 #endif
8401 {
8402 var int length = 0;
8403 var int listlen = 0;
8404 pushSTACK(NIL)/*result head*/; pushSTACK(NIL)/*result tail*/;
8405 pushSTACK(stringlist);
8406 while (consp(STACK_0/*stringlist tail*/)) {
8407 var object tmp = allocate_cons();
8408 if (nullp(STACK_2/*result*/)) STACK_1 = STACK_2 = tmp;
8409 else { Cdr(STACK_1/*result tail*/) = tmp; STACK_1 = tmp; }
8410 tmp = check_string(Car(STACK_0));
8411 tmp = string_to_asciz(tmp,*encoding_);
8412 length += Sbvector_length(tmp) + 1;
8413 Car(STACK_1) = tmp;
8414 STACK_0 = Cdr(STACK_0);
8415 listlen++;
8416 }
8417 if (listlength) *listlength = listlen;
8418 skipSTACK(2); /* drop stringlist and result tail */
8419 return length;
8420 }
8421
8422 #ifdef WIN32_NATIVE
8423
8424 /* (SHELL-EXECUTE verb filename parameters defaultdir)
8425 ShellExecute wrapper
8426 See ShellExecute description at
8427 http://msdn.microsoft.com/library/default.asp?url=/library/en-us/shellcc/
8428 platform/Shell/reference/functions/shellexecute.asp
8429 verb: usually nil (for default),
8430 "edit", "explore", "open", "print", "properties"
8431 filename: filename or url to open
8432 parameters: list of arguments
8433 defaultdir: default directory for application (can be nil)
8434 returns: nil, but can signal an OS error*/
8435 LISPFUN(shell_execute,seclass_default,0,4,norest,nokey,0,NIL) {
8436 var object verb_arg = STACK_3;
8437 var object filename_arg = check_string(STACK_2);
8438 var object parameters_arg = STACK_1;
8439 var object defaultdir_arg = STACK_0;
8440 var int verb_len = 0;
8441 if (nullp(verb_arg)) pushSTACK(S(nil));
8442 else {
8443 pushSTACK(string_to_asciz(check_string(verb_arg),O(misc_encoding)));
8444 verb_len = Sbvector_length(STACK_0);
8445 }
8446 var int filename_len = 0;
8447 pushSTACK(string_to_asciz(check_string(filename_arg),
8448 O(misc_encoding)));
8449 filename_len = Sbvector_length(STACK_0);
8450 var int parameters_len =
8451 stringlist_to_ascizlist(parameters_arg,&O(misc_encoding),NULL);
8452 /* list of asciz strings is in the STACK */
8453 var DYNAMIC_ARRAY(parameters,char,parameters_len*2);
8454 var int parameter_pos = 0;
8455 while (!nullp(STACK_0)) {
8456 if (parameter_pos > 0) parameters[parameter_pos++] = ' ';
8457 parameter_pos +=
8458 shell_quote(parameters+parameter_pos,TheAsciz(Car(STACK_0)));
8459 ASSERT(parameter_pos < parameters_len*2);
8460 STACK_0 = Cdr(STACK_0);
8461 }
8462 skipSTACK(1);
8463 var int defaultdir_len = 0;
8464 if (nullp(defaultdir_arg)) pushSTACK(S(nil));
8465 else {
8466 pushSTACK(string_to_asciz(check_string(defaultdir_arg),
8467 O(misc_encoding)));
8468 defaultdir_len = Sbvector_length(STACK_0);
8469 }
8470 /* STACK: verb/nil, filename, defaultdir/nil */
8471 var DYNAMIC_ARRAY(verb,char,1+verb_len);
8472 var DYNAMIC_ARRAY(filename,char,1+filename_len);
8473 var DYNAMIC_ARRAY(defaultdir,char,1+defaultdir_len);
8474 var char *sp, *dp;
8475 if (!nullp(STACK_2))
8476 for (sp=TheAsciz(STACK_2),dp=verb;(*dp = *sp);sp++,dp++);
8477 for (sp=TheAsciz(STACK_1),dp=filename;(*dp = *sp);sp++,dp++);
8478 if (!nullp(STACK_0))
8479 for (sp=TheAsciz(STACK_0),dp=defaultdir;(*dp = *sp);sp++,dp++);
8480 begin_blocking_system_call();
8481 var DWORD result = (DWORD) ShellExecute(NULL,
8482 nullp(STACK_2)?NULL:verb,
8483 filename,
8484 parameters_len?parameters:NULL,
8485 nullp(STACK_0)?NULL:defaultdir,
8486 SW_SHOWNORMAL);
8487 end_blocking_system_call();
8488 if (result <= 32) OS_error();
8489 FREE_DYNAMIC_ARRAY(defaultdir);
8490 FREE_DYNAMIC_ARRAY(filename);
8491 FREE_DYNAMIC_ARRAY(verb);
8492 FREE_DYNAMIC_ARRAY(parameters);
8493 skipSTACK(3+4);
8494 VALUES1(S(nil));
8495 }
8496
8497 #endif
8498
8499 #if defined(UNIX)
8500
8501 /* /dev/null handle. */
nullfile(void)8502 local Handle nullfile (void) {
8503 var Handle result;
8504 begin_system_call();
8505 result = open("/dev/null",O_RDWR);
8506 end_system_call();
8507 return result;
8508 }
8509
8510 /* obtaining a pipe handle */
mkpipe(Handle * hin,bool dummy1,Handle * hout,bool dummy2)8511 local void mkpipe (Handle * hin, bool dummy1, Handle * hout, bool dummy2) {
8512 unused(dummy1); unused(dummy2);
8513 var int handles[2];
8514 begin_system_call();
8515 if (pipe(handles)) OS_error();
8516 end_system_call();
8517 *hin = (Handle) handles[0];
8518 *hout = (Handle) handles[1];
8519 }
8520
8521 #elif defined(WIN32_NATIVE)
8522
8523 /* /dev/null on NT/W95. */
nullfile(void)8524 local Handle nullfile (void) {
8525 var Handle result;
8526 begin_system_call();
8527 result = CreateFile("NUL", GENERIC_READ | GENERIC_WRITE,
8528 FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
8529 OPEN_EXISTING, 0, NULL);
8530 end_system_call();
8531 return result;
8532 }
8533
8534 /* obtaining pipe handle */
mkpipe(Handle * hin,bool dupinp,Handle * hout,bool dupoutp)8535 local void mkpipe (Handle * hin, bool dupinp, Handle * hout, bool dupoutp) {
8536 begin_system_call();
8537 if (!CreatePipe(hin,hout,NULL,0)) { OS_error(); }
8538 if (dupinp) {/* make it inheritable */
8539 var Handle hin1 = handle_dup(*hin);
8540 if (!CloseHandle(*hin)) { OS_error(); }
8541 *hin = hin1;
8542 }
8543 if (dupoutp) {
8544 var Handle hout1 = handle_dup(*hout);
8545 if (!CloseHandle(*hout)) { OS_error(); }
8546 *hout = hout1;
8547 }
8548 end_system_call();
8549 }
8550
8551 #else
8552
8553 #error nullfile & mkpipe are not defined
8554
8555 #endif
8556
init_launch_streamarg(gcv_object_t * streamarg,bool child_inputp,Handle stdhandle,Handle * h,Handle * ph,Handle * hnull,bool * wait_p)8557 local maygc bool init_launch_streamarg
8558 (gcv_object_t *streamarg, bool child_inputp, Handle stdhandle,
8559 Handle * h, Handle * ph, Handle * hnull, bool * wait_p) {
8560 var int handletype = 0;
8561 *h = INVALID_HANDLE;
8562 *ph = INVALID_HANDLE;
8563 if (!boundp(*streamarg) || eq(*streamarg,S(Kterminal)))
8564 *h = handle_dup(stdhandle);
8565 else if (nullp(*streamarg)) {
8566 if (*hnull == INVALID_HANDLE)
8567 *hnull = nullfile();
8568 *h = handle_dup(*hnull);
8569 } else if (eq(*streamarg,S(Kpipe))) {
8570 if (child_inputp)
8571 /* make an input pipe for child, ph = parent's handle */
8572 mkpipe(h,true,ph,false);
8573 else
8574 /* make an output pipe for child */
8575 mkpipe(ph,false,h,true);
8576 if (*ph == INVALID_HANDLE || *h == INVALID_HANDLE)
8577 return false;
8578 *wait_p = false; /* TODO: error when wait_p */
8579 } else {
8580 /* child i/o direction is the same as lisp user i/o direction */
8581 *h = handle_dup(stream_lend_handle(streamarg,child_inputp,&handletype));
8582 if (handletype != 1)
8583 return false;
8584 }
8585 return (*h != INVALID_HANDLE);
8586 }
8587
make_launch_pipe(gcv_object_t * ret,direction_t direction,Handle hparent_pipe,int childpid,gcv_object_t * enc,gcv_object_t * eltype,gcv_object_t * buffered)8588 local maygc void make_launch_pipe
8589 (gcv_object_t *ret, direction_t direction, Handle hparent_pipe, int childpid,
8590 gcv_object_t *enc, gcv_object_t *eltype, gcv_object_t *buffered) {
8591 if (hparent_pipe != INVALID_HANDLE) {
8592 pushSTACK(*enc); /* encoding */
8593 pushSTACK(*eltype); /* element-type */
8594 pushSTACK(*buffered); /* buffered */
8595 *ret = mk_pipe_from_handle(hparent_pipe,childpid,direction);
8596 /* stack has been cleaned by mk_pipe_from_handle */
8597 }
8598 }
8599
8600 /* on cygwin, <sigsegv.h> includes <windows.h> therefore *_PRIORITY_CLASS
8601 macros are already defined */
8602 #if !defined(NORMAL_PRIORITY_CLASS)
8603 #define NORMAL_PRIORITY_CLASS 0
8604 #define HIGH_PRIORITY_CLASS -10
8605 #define IDLE_PRIORITY_CLASS 10
8606 #define MY_LOCAL_PRIORITY_CLASSES
8607 #endif
8608 #if defined(UNIX)
8609 #define CloseHandle(h) (close(h)==0)
8610 #endif
8611 /* paranoidal close */
8612 #define ParaClose(h) if (!CloseHandle(h)) { end_system_call(); OS_error(); }
8613
interpret_launch_priority(object priority_arg)8614 local maygc sintL interpret_launch_priority (object priority_arg) {
8615 if (!boundp(priority_arg)) return NORMAL_PRIORITY_CLASS;
8616 restart_priority:
8617 if (eq(priority_arg,S(Khigh))) return HIGH_PRIORITY_CLASS;
8618 else if (eq(priority_arg,S(Klow))) return IDLE_PRIORITY_CLASS;
8619 else if (eq(priority_arg,S(Knormal))) return NORMAL_PRIORITY_CLASS;
8620 else if (integerp(priority_arg)) return I_to_L(priority_arg);
8621 pushSTACK(NIL); /* no PLACE */
8622 pushSTACK(priority_arg); /* TYPE-ERROR slot DATUM */
8623 pushSTACK(O(type_priority)); /* TYPE-ERROR slot EXPECTED-TYPE */
8624 pushSTACK(priority_arg);
8625 pushSTACK(S(Kpriority));
8626 pushSTACK(TheSubr(subr_self)->name);
8627 check_value(type_error,GETTEXT("~S: Illegal ~S argument ~S"));
8628 priority_arg = value1;
8629 goto restart_priority;
8630 }
8631
8632 /* (LAUNCH executable [:arguments] [:wait] [:input] [:output] [:error]
8633 [:element-type] [:external-format] [:buffered] [:priority])
8634 Launches a program.
8635 :arguments : a list of strings (*MISC-ENCODING* is used)
8636 :wait - nullp/not nullp - whether to wait for process to finish (default T)
8637 :input, :output, :error - i/o/e streams for process. basically file-streams,
8638 pipe streams or terminal-streams.
8639 see stream_lend_handle() in stream.d for full list of supported streams.
8640 Can be NIL (/dev/null), :pipe (pipe streams are created) or :terminal.
8641 :element-type, :external-format, :buffered : parameters for created
8642 pipe-stream if one or more of :input, :output, :error is :pipe.
8643
8644 FIXME: this is wrong: it does not allow different pipe types for i/o.
8645 The correct arguments should be:
8646 :input, :output, :error should take list arguments like this:
8647 (:buffered t :element-type (unsigned-byte 8))
8648 :PIPE should be removed from constobj.d
8649
8650 :priority : :HIGH/:LOW/:NORMAL or fixnum
8651 on UNIX - see nice(2)
8652 on Windows - see CreateProcess dwCreationFlags parameter.
8653 returns: value1: if wait exit code, child PID otherwise
8654 value2: NIL or created pipe-output-stream, input stream for child
8655 value3: NIL or created pipe-input-stream, output stream for child
8656 value4: NIL or created pipe-input-stream, error stream for child */
8657 LISPFUN(launch,seclass_default,1,0,norest,key,9,
8658 (kw(element_type),kw(external_format),kw(buffered),kw(arguments),
8659 kw(wait),kw(input),kw(output),kw(error),kw(priority))) {
8660 STACK_9 = check_string(STACK_9); /* command_arg */
8661 if (!boundp(STACK_5)) STACK_5 = NIL; /* arguments_arg */
8662 else STACK_5 = check_list(STACK_5);
8663 var long priority = interpret_launch_priority(STACK_0);
8664 var bool wait_p = !nullp(STACK_4); /* default: do wait! */
8665 var Handle hnull = INVALID_HANDLE;
8666 var Handle hinput;
8667 var Handle hparent_out; /* in case of pipe */
8668 /* STACK_3 == input_stream_arg */
8669 if (!init_launch_streamarg(&STACK_3, true, stdin_handle, &hinput,
8670 &hparent_out, &hnull, &wait_p))
8671 OS_error();
8672 var Handle houtput, hparent_in;
8673 /* STACK_2 == output_stream_arg */
8674 if (!init_launch_streamarg(&STACK_2, false, stdout_handle, &houtput,
8675 &hparent_in, &hnull, &wait_p)) {
8676 begin_system_call();
8677 if (hinput != INVALID_HANDLE && hinput != stdin_handle)
8678 ParaClose(hinput);
8679 if (hparent_out != INVALID_HANDLE)
8680 ParaClose(hparent_out);
8681 end_system_call();
8682 OS_error();
8683 }
8684 var Handle herror, hparent_errin;
8685 /* STACK_1 == error_stream_arg */
8686 if (!init_launch_streamarg(&STACK_1, false, stderr_handle, &herror,
8687 &hparent_errin, &hnull, &wait_p)) {
8688 begin_system_call();
8689 if (hinput != INVALID_HANDLE && hinput != stdin_handle)
8690 ParaClose(hinput);
8691 if (hparent_out != INVALID_HANDLE)
8692 ParaClose(hparent_out);
8693 if (houtput != INVALID_HANDLE && houtput != stdout_handle)
8694 ParaClose(houtput);
8695 if (hparent_in != INVALID_HANDLE)
8696 ParaClose(hparent_in);
8697 end_system_call();
8698 OS_error();
8699 }
8700 if (hnull != INVALID_HANDLE) {
8701 begin_system_call();
8702 ParaClose(hnull);
8703 end_system_call();
8704 }
8705 /* convert command and args to one asciz string list */
8706 pushSTACK(allocate_cons());
8707 Car(STACK_0) = STACK_(9+1); /* command_arg */
8708 Cdr(STACK_0) = STACK_(5+1); /* arguments_arg */
8709 var uintL arglist_count = 0;
8710 var uintL argbuf_len = 1 +
8711 stringlist_to_ascizlist(STACK_0,&O(misc_encoding),&arglist_count);
8712 /* STACK: cmdlist, ascizcmdlist */
8713 STACK_1 = STACK_0;
8714 skipSTACK(1);
8715 /* STACK: ascizcmdlist */
8716 var int child_id = 0;
8717 #if defined(UNIX)
8718 var DYNAMIC_ARRAY(argv,char*,1+(uintL)arglist_count+1);
8719 var DYNAMIC_ARRAY(argvdata,char,argbuf_len);
8720 var object curcons = STACK_0;
8721 var char** argvptr = &argv[0];
8722 var char* argvdataptr = &argvdata[0];
8723 while (consp(curcons)) {
8724 var uintL len = Sbvector_length(Car(curcons));
8725 var char* ptr = TheAsciz(Car(curcons));
8726 *argvptr++ = argvdataptr; /* fill into argv */
8727 dotimespL(len,len, { *argvdataptr++ = *ptr++; } ); /* and copy */
8728 curcons = Cdr(curcons);
8729 };
8730 *argvptr = NULL; /* and conclude with null */
8731 skipSTACK(1);
8732 /* STACK: -- */
8733 begin_system_call();
8734 begin_want_sigcld();
8735 child_id = vfork();
8736 if (child_id == 0) {/* What ?! I am the clone ?! */
8737 /* TODO: close ALL unused opened handles since unclosed handles
8738 (to previously opened pipes) can prevent childs to end up properly */
8739 #define CHILD_DUP(from,to) \
8740 if (dup2(from,to) < 0) { \
8741 fprintf(stderr,"clisp/child: cannot duplicate %d to %d: %s\n", \
8742 from,to,strerror(errno)); \
8743 _exit(-1); \
8744 } \
8745 if (from>2) \
8746 close(from)
8747 CHILD_DUP(hinput,0);
8748 CHILD_DUP(houtput,1);
8749 CHILD_DUP(herror,2);
8750 #undef CHILD_DUP
8751 /* close child copies of parent's handles */
8752 if (hparent_out >= 0) close(hparent_out);
8753 if (hparent_in >= 0) close(hparent_in);
8754 if (hparent_errin >= 0) close(hparent_errin);
8755 #ifdef HAVE_NICE
8756 errno = 0; nice(priority);
8757 if (errno) {
8758 fprintf(stderr,"clisp/child: cannot set priority to %ld: %s\n",
8759 priority,strerror(errno));
8760 _exit(-1);
8761 }
8762 #endif
8763 close_all_fd();
8764 unblock_all_signals();
8765 execvp(*argv,argv);
8766 fprintf(stderr,"clisp/child: execvp failed: %s\n",strerror(errno));
8767 _exit(-1);
8768 } else if (child_id < 0) {
8769 /* TODO: FIXME: no easy way to be aware of dup2 or exec failures */
8770 end_want_sigcld();
8771 end_system_call();
8772 OS_error();
8773 }
8774 var int exit_code = 0;
8775 if (wait_p) {
8776 var int status;
8777 GC_SAFE_CALL(status = wait2(child_id));
8778 exit_code = WEXITSTATUS(status);
8779 }
8780 end_want_sigcld();
8781 /* close our copies of child's handles */
8782 if (hinput!=stdin_handle) ParaClose(hinput);
8783 if (houtput!=stdout_handle) ParaClose(houtput);
8784 if (herror!=stderr_handle) ParaClose(herror);
8785 end_system_call();
8786 FREE_DYNAMIC_ARRAY(argvdata);
8787 FREE_DYNAMIC_ARRAY(argv);
8788 #elif defined(WIN32_NATIVE)
8789 var DYNAMIC_ARRAY(command_data,char,argbuf_len*2);
8790 /* argbuf_len is multiplied by 2 for quoting sake */
8791 var int command_pos = 0;
8792 while (!nullp(STACK_0)) {
8793 if (command_pos > 0) command_data[command_pos++] = ' ';
8794 command_pos += shell_quote(command_data+command_pos,
8795 TheAsciz(Car(STACK_0)));
8796 ASSERT(command_pos < argbuf_len*2);
8797 STACK_0 = Cdr(STACK_0);
8798 }
8799 skipSTACK(1);
8800 /* STACK: -- */
8801
8802 /* Start new process. */
8803 var HANDLE prochandle;
8804 var PROCESS_INFORMATION pinfo;
8805 var STARTUPINFO sinfo;
8806 sinfo.cb = sizeof(STARTUPINFO);
8807 sinfo.lpReserved = NULL;
8808 sinfo.lpDesktop = NULL;
8809 sinfo.lpTitle = NULL;
8810 sinfo.cbReserved2 = 0;
8811 sinfo.lpReserved2 = NULL;
8812 sinfo.dwFlags = STARTF_USESTDHANDLES;
8813 sinfo.hStdInput = hinput;
8814 sinfo.hStdOutput = houtput;
8815 sinfo.hStdError = herror;
8816 begin_system_call();
8817 if (!CreateProcess(NULL, command_data, NULL, NULL, true,
8818 (DWORD)priority & 0x1E0,
8819 NULL, NULL, &sinfo, &pinfo))
8820 { end_system_call(); OS_error(); }
8821 if (pinfo.hThread) /* zero for 16 bit programs in NT */
8822 ParaClose(pinfo.hThread);
8823 prochandle = pinfo.hProcess;
8824 child_id = pinfo.dwProcessId;
8825 FREE_DYNAMIC_ARRAY(command_data);
8826 var DWORD exit_code = 0;
8827 if (wait_p) {
8828 /* Wait until it terminates, get its exit status code. */
8829 var DWORD waitret;
8830 GC_SAFE_CALL(waitret = WaitForSingleObject(prochandle,INFINITE));
8831 switch (waitret) {
8832 case WAIT_FAILED:
8833 end_system_call(); OS_error();
8834 case WAIT_OBJECT_0:
8835 break;
8836 default: NOTREACHED;
8837 }
8838 if (!GetExitCodeProcess(prochandle,(DWORD*)&exit_code))
8839 { end_system_call(); OS_error(); }
8840 }
8841 /* we can safely close handle of a running process - it doesn't
8842 lead to process termination */
8843 ParaClose(prochandle);
8844 /* close our copies of child's handles */
8845 if (hinput!=stdin_handle) ParaClose(hinput);
8846 if (houtput!=stdout_handle) ParaClose(houtput);
8847 if (herror!=stderr_handle) ParaClose(herror);
8848 end_system_call();
8849 #else
8850 #error LAUNCH is not defined
8851 #endif
8852 { /* make pipe-streams */
8853 gcv_object_t *buff = &STACK_6; /* :BUFFERED */
8854 gcv_object_t *enc = &STACK_7; /* :ENCODING */
8855 gcv_object_t *eltype = &STACK_8; /* :ELEMENT-TYPE */
8856 /* child's input stream, pipe-output from our side */
8857 make_launch_pipe(&(STACK_3),DIRECTION_OUTPUT,hparent_out,
8858 child_id,enc,eltype,buff);
8859 /* child's output stream, pipe-input from our side
8860 double analysis of buffered, eltype,encoding
8861 drawback: slow; advantage: simple iface with stream.d */
8862 make_launch_pipe(&(STACK_2),DIRECTION_INPUT,hparent_in,
8863 child_id,enc,eltype,buff);
8864 /* child's error stream, pipe-input from our side */
8865 make_launch_pipe(&(STACK_1),DIRECTION_INPUT,hparent_errin,
8866 child_id,enc,eltype,buff);
8867 }
8868 value1 = wait_p ? fixnum(exit_code) : L_to_I(child_id);
8869 value2 = (hparent_out != INVALID_HANDLE) ? (object)STACK_3 : NIL; /*INPUT*/
8870 value3 = (hparent_in != INVALID_HANDLE) ? (object)STACK_2 : NIL; /*OUTPUT*/
8871 value4 = (hparent_errin != INVALID_HANDLE) ? (object)STACK_1 : NIL; /*ERROR*/
8872 mv_count = 4;
8873
8874 skipSTACK(10);
8875 }
8876
8877 #if defined(MY_LOCAL_PRIORITY_CLASSES)
8878 #undef MY_LOCAL_PRIORITY_CLASSES
8879 #undef NORMAL_PRIORITY_CLASS
8880 #undef HIGH_PRIORITY_CLASS
8881 #undef IDLE_PRIORITY_CLASS
8882 #endif
8883 #if defined(UNIX)
8884 #undef CloseHandle
8885 #endif
8886
8887 #undef ParaClose
8888
8889 /* (SAVEMEM pathname executable) stores the memory image at pathname. */
8890 LISPFUNN(savemem,2) {
8891 #ifdef MULTITHREAD
8892 extern bool single_running_threadp();
8893 /* ensure that we are the only running thread. currently threads
8894 do not survive savemem()/loadmem() */
8895 if (!single_running_threadp()) {
8896 /* signal an error */
8897 pushSTACK(NIL); /* CELL-ERROR Slot NAME */
8898 pushSTACK(subr_self);
8899 error(control_error,GETTEXT("~S: There are multiple running threads. Currently they do not survive image saving/loading."));
8900 }
8901 /* we are the only one running. let's check mutexes. we do not allow
8902 to have locked mutexes saved in the memory image */
8903 var object list = O(all_mutexes);
8904 while (!endp(list)) {
8905 if (!nullp(TheMutex(Car(list))->xmu_owner)) {
8906 /* we have a locked mutex. this is an error. */
8907 pushSTACK(NIL); /* CELL-ERROR Slot NAME */
8908 /* mutex owner (should be eq() to current thread) */
8909 pushSTACK(TheMutex(Car(list))->xmu_owner);
8910 pushSTACK(Car(list)); /* mutex */
8911 pushSTACK(subr_self);
8912 error(control_error,GETTEXT("~S: Mutex ~S is locked by thread ~S. Currently locked mutexes are not allowed in memory files."));
8913 }
8914 list = Cdr(list);
8915 }
8916 #endif
8917 var uintL executable = nullp(STACK_0) ? 0 : eq(Fixnum_0,STACK_0) ? 2 : 1;
8918 skipSTACK(1); /* drop executable */
8919 /* execute (OPEN pathname :direction :output) :
8920 pathname as 1st argument */
8921 pushSTACK(S(Kdirection)); /* :DIRECTION as 2nd Argument */
8922 pushSTACK(S(Koutput)); /* :OUTPUT as 3rd Argument */
8923 #ifdef UNIX
8924 /* On Unix with mmap() existing .mem-Files may not be simply
8925 overwritten, because running Lisp-processes would crash due to this.
8926 So therefore :if-exists :rename-and-delete. */
8927 #if defined(UNIX_LINUX) && defined(SINGLEMAP_MEMORY)
8928 /* Under Linux 1.3.20, when the mem file to be saved is on an NFS volume
8929 and has the same filename as the mem file we started with, the GC
8930 done by savemem (once the new mem file has been created and still has
8931 size 0) will crash. Looks like a bug in the Linux NFS client, which
8932 causes random pages to be mapped in instead of pages from the renamed
8933 old mem file. Workaround: Do a full GC, forcing all the old mem file's
8934 contents into memory immediately. */
8935 gar_col(1);
8936 #endif
8937 pushSTACK(S(Kif_exists)); /* :IF-EXISTS as 4th Argument */
8938 pushSTACK(S(Krename_and_delete)); /* :RENAME-AND-DELETE as 5th Argument */
8939 funcall(L(open),5);
8940 #else
8941 funcall(L(open),3);
8942 #endif
8943 /* write memory image into the file:
8944 (the stream has to be closed by function savemem(),
8945 also in case of an error.) */
8946 var off_t file_size = savemem(value1,executable);
8947 VALUES1(off_to_I(file_size));
8948 }
8949
8950 #ifdef DYNAMIC_MODULES
8951
8952 /* (SYSTEM::DYNLOAD-MODULES pathname stringlist)
8953 loads a shared library, containing a number of modules. */
8954 LISPFUNN(dynload_modules,2) {
8955 /* convert pathname into string */
8956 STACK_1 = coerce_pathname(STACK_1);
8957 check_no_wildcards(STACK_1);
8958 STACK_1 = whole_namestring(use_default_dir(STACK_1));
8959 var uintL stringcount = llength(STACK_0);
8960 var gcv_object_t* arg_ = &STACK_0;
8961 var gcv_object_t* libpath_ = &STACK_1;
8962 { /* print loading message */
8963 dynamic_bind(S(load_level),fixnum_inc(Symbol_value(S(load_level)),1));
8964 pushSTACK(CLSTEXT("Loading module~P ~{~A~^, ~} from ~A"));
8965 pushSTACK(fixnum(stringcount)); pushSTACK(*arg_); pushSTACK(*libpath_);
8966 funcall(S(loading_message),4); /* defined in init.lisp */
8967 }
8968 { /* check strings and store in the stack: */
8969 pushSTACK(*arg_); /* tail */
8970 while (!endp(STACK_0)) {
8971 Car(STACK_0) = check_string(Car(STACK_0));
8972 pushSTACK(Cdr(STACK_0)); /* for the next iteration */
8973 STACK_1 = string_to_asciz(Car(STACK_1),Symbol_value(S(ascii)));
8974 }
8975 skipSTACK(1); /* drop tail */
8976 }
8977 {
8978 var DYNAMIC_ARRAY(modnames,const char *,stringcount);
8979 if (stringcount > 0) {
8980 var uintL count;
8981 var gcv_object_t* ptr1 = STACK STACKop stringcount;
8982 var const char * * ptr2 = modnames;
8983 dotimespL(count,stringcount, { *ptr2++ = TheAsciz(NEXT(ptr1)); });
8984 }
8985 with_string_0(*libpath_,O(pathname_encoding),libpath, {
8986 dynload_modules(libpath,stringcount,modnames);
8987 });
8988 FREE_DYNAMIC_ARRAY(modnames);
8989 }
8990 skipSTACK(stringcount);
8991 { /* print loading message */
8992 pushSTACK(CLSTEXT("Loaded module~P ~{~A~^, ~} from ~A"));
8993 pushSTACK(fixnum(stringcount)); pushSTACK(*arg_); pushSTACK(*libpath_);
8994 funcall(S(loading_message),4); /* defined in init.lisp */
8995 dynamic_unbind(S(load_level));
8996 }
8997 VALUES1(*libpath_); /* Library-Name as value */
8998 skipSTACK(2);
8999 }
9000
9001 #endif
9002
9003 /* =================================================================== */
9004
9005 #include "execname.c"
9006 LISPFUNN(program_name,0)
9007 { /* (SYS::PROGRAM-NAME) returns the executable's name. */
9008 VALUES1(asciz_to_string(executable_name,O(pathname_encoding)));
9009 }
9010
9011 LISPFUNN(lib_directory,0)
9012 { /* (SYS::LIB-DIRECTORY) returns CLISP's private library directory
9013 (called $(lisplibdir) in the Makefile). */
9014 if (!nullp(O(lib_dir))) {
9015 VALUES1(O(lib_dir));
9016 } else {
9017 pushSTACK(TheSubr(subr_self)->name);
9018 error(error_condition,GETTEXT("~S: installation directory is not known, use the -B command line option to specify it or set *LIB-DIRECTORY*"));
9019 }
9020 }
9021
9022 LISPFUNN(set_lib_directory,1)
9023 { /* (SYS::SET-LIB-DIRECTORY path) sets the CLISP's private library directory
9024 (called $(lisplibdir) in the Makefile) */
9025 if (stringp(STACK_0)) STACK_0 = ensure_last_slash(STACK_0);
9026 funcall(L(truename),1); O(lib_dir) = value1;
9027 }
9028
9029 /* ===================================================================== */
9030
9031 #ifdef DEBUG_TRANSLATE_PATHNAME
9032 #undef DEBUG_TRANSLATE_PATHNAME
9033 #undef DOUT
9034 #endif
9035