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