1 #ifdef RCSID
2 static char RCSid[] =
3 "$Header: d:/cvsroot/tads/TADS2/OSNOUI.C,v 1.3 1999/07/11 00:46:30 MJRoberts Exp $";
4 #endif
5 
6 /*
7  *   Copyright (c) 1997, 2002 Michael J. Roberts.  All Rights Reserved.
8  *
9  *   Please see the accompanying license file, LICENSE.TXT, for information
10  *   on using and copying this software.
11  */
12 /*
13 Name
14   osnoui.c - general-purpose implementations of OS routines with no UI
15 Function
16   This file provides implementations for certain OS routines that have
17   no UI component and can be implemented in general for a range of
18   operating systems.
19 Notes
20 
21 Modified
22   04/11/99 CNebel        - Improve const-ness; fix C++ errors.
23   11/02/97 MJRoberts  - Creation
24 */
25 
26 #include <stdio.h>
27 #ifdef MSDOS
28 #include <io.h>
29 #endif
30 #include <stdlib.h>
31 #include <string.h>
32 #include <stdarg.h>
33 #include <ctype.h>
34 #include <time.h>
35 #include <sys/stat.h>
36 
37 #include "os.h"
38 #include "run.h"
39 
40 /*
41  *   Ports with MS-DOS-like file systems (Atari ST, OS/2, Macintosh, and,
42  *   of course, MS-DOS itself) can use the os_defext and os_remext
43  *   routines below by defining USE_DOSEXT.  Unix and VMS filenames will
44  *   also be parsed correctly by these implementations, but untranslated
45  *   VMS logical names may not be.
46  */
47 
48 #ifdef USE_DOSEXT
49 /*
50  *   os_defext(fn, ext) should append the default extension ext to the
51  *   filename in fn.  It is assumed that the buffer at fn is big enough to
52  *   hold the added characters in the extension.  The result should be
53  *   null-terminated.  When an extension is already present in the
54  *   filename at fn, no action should be taken.  On systems without an
55  *   analogue of extensions, this routine should do nothing.
56  */
os_defext(char * fn,const char * ext)57 void os_defext(char *fn, const char *ext)
58 {
59     char *p;
60 
61     /*
62      *   Scan backwards from the end of the string, looking for the last
63      *   dot ('.') in the filename.  Stop if we encounter a path separator
64      *   character of some kind, because that means we reached the start
65      *   of the filename without encountering a period.
66      */
67     p = fn + strlen(fn);
68     while (p > fn)
69     {
70         /* on to the previous character */
71         p--;
72 
73         /*
74          *   if it's a period, return without doing anything - this
75          *   filename already has an extension, so don't apply a default
76          */
77         if (*p == '.')
78             return;
79 
80         /*
81          *   if this is a path separator character, we're no longer in the
82          *   filename, so stop looking for a period
83          */
84         if (*p == OSPATHCHAR || strchr(OSPATHALT, *p) != 0)
85             break;
86     }
87 
88     /* we didn't find an extension - add the dot and the extension */
89     strcat(fn, ".");
90     strcat(fn, ext);
91 }
92 
93 /*
94  *   Add an extension, even if the filename currently has one
95  */
os_addext(char * fn,const char * ext)96 void os_addext(char *fn, const char *ext)
97 {
98     strcat(fn, ".");
99     strcat(fn, ext);
100 }
101 
102 /*
103  *   os_remext(fn) removes the extension from fn, if present.  The buffer
104  *   at fn should be modified in place.  If no extension is present, no
105  *   action should be taken.  For systems without an analogue of
106  *   extensions, this routine should do nothing.
107  */
os_remext(char * fn)108 void os_remext(char *fn)
109 {
110     char *p;
111 
112     /* scan backwards from the end of the string, looking for a dot */
113     p = fn + strlen(fn);
114     while ( p>fn )
115     {
116         /* move to the previous character */
117         p--;
118 
119         /* if it's a period, we've found the extension */
120         if ( *p=='.' )
121         {
122             /* terminate the string here to remove the extension */
123             *p = '\0';
124 
125             /* we're done */
126             return;
127         }
128 
129         /*
130          *   if this is a path separator, there's no extension, so we can
131          *   stop looking
132          */
133         if (*p == OSPATHCHAR || strchr(OSPATHALT, *p) != 0)
134             return;
135     }
136 }
137 
138 /*
139  *   Get a pointer to the root name portion of a filename.  Note that this
140  *   implementation is included in the ifdef USE_DOSEXT section, since it
141  *   seems safe to assume that if a platform has filenames that are
142  *   sufficiently DOS-like for the extension parsing routines, the same
143  *   will be true of path parsing.
144  */
os_get_root_name(char * buf)145 char *os_get_root_name(char *buf)
146 {
147     char *rootname;
148 
149     /* scan the name for path separators */
150     for (rootname = buf ; *buf != '\0' ; ++buf)
151     {
152         /* if this is a path separator, remember it */
153         if (*buf == OSPATHCHAR || strchr(OSPATHALT, *buf) != 0)
154         {
155             /*
156              *   It's a path separators - for now, assume the root will
157              *   start at the next character after this separator.  If we
158              *   find another separator later, we'll forget about this one
159              *   and use the later one instead.
160              */
161             rootname = buf + 1;
162         }
163     }
164 
165     /* return the last root name candidate */
166     return rootname;
167 }
168 
169 /*
170  *   Extract the path from a filename
171  */
os_get_path_name(char * pathbuf,size_t pathbuflen,const char * fname)172 void os_get_path_name(char *pathbuf, size_t pathbuflen, const char *fname)
173 {
174     const char *lastsep;
175     const char *p;
176     size_t len;
177     int root_path;
178 
179     /* find the last separator in the filename */
180     for (p = fname, lastsep = fname ; *p != '\0' ; ++p)
181     {
182         /*
183          *   if it's a path separator character, remember it as the last one
184          *   we've found so far
185          */
186         if (*p == OSPATHCHAR || strchr(OSPATHALT, *p)  != 0)
187             lastsep = p;
188     }
189 
190     /* get the length of the prefix, not including the separator */
191     len = lastsep - fname;
192 
193     /*
194      *   Normally, we don't include the last path separator in the path; for
195      *   example, on Unix, the path of "/a/b/c" is "/a/b", not "/a/b/".
196      *   However, on Unix/DOS-like file systems, a root path *does* require
197      *   the last path separator: the path of "/a" is "/", not an empty
198      *   string.  So, we need to check to see if the file is in a root path,
199      *   and if so, include the final path separator character in the path.
200      */
201     for (p = fname, root_path = FALSE ; p != lastsep ; ++p)
202     {
203         /*
204          *   if this is NOT a path separator character, we don't have all
205          *   path separator characters before the filename, so we don't have
206          *   a root path
207          */
208         if (*p != OSPATHCHAR && strchr(OSPATHALT, *p) == 0)
209         {
210             /* note that we don't have a root path */
211             root_path = FALSE;
212 
213             /* no need to look any further */
214             break;
215         }
216     }
217 
218     /* if we have a root path, keep the final path separator in the path */
219     if (root_path)
220         ++len;
221 
222 #ifdef MSDOS
223     /*
224      *   On DOS, we have a special case: if the path is of the form "x:\",
225      *   where "x" is any letter, then we have a root filename and we want to
226      *   include the backslash.
227      */
228     if (lastsep == fname + 2
229         && isalpha(fname[0]) && fname[1] == ':' && fname[2] == '\\')
230     {
231         /* we have an absolute path - use the full "x:\" sequence */
232         len = 3;
233     }
234 #endif
235 
236     /* make sure it fits in our buffer (with a null terminator) */
237     if (len > pathbuflen - 1)
238         len = pathbuflen - 1;
239 
240     /* copy it and null-terminate it */
241     memcpy(pathbuf, fname, len);
242     pathbuf[len] = '\0';
243 }
244 
245 /*
246  *   Build a full path name given a path and a filename
247  */
os_build_full_path(char * fullpathbuf,size_t fullpathbuflen,const char * path,const char * filename)248 void os_build_full_path(char *fullpathbuf, size_t fullpathbuflen,
249                         const char *path, const char *filename)
250 {
251     size_t plen, flen;
252     int add_sep;
253 
254     /*
255      *   Note whether we need to add a separator.  If the path prefix ends
256      *   in a separator, don't add another; otherwise, add the standard
257      *   system separator character.
258      *
259      *   Do not add a separator if the path is completely empty, since
260      *   this simply means that we want to use the current directory.
261      */
262     plen = strlen(path);
263     add_sep = (plen != 0
264                && path[plen-1] != OSPATHCHAR
265                && strchr(OSPATHALT, path[plen-1]) == 0);
266 
267     /* copy the path to the full path buffer, limiting to the buffer length */
268     if (plen > fullpathbuflen - 1)
269         plen = fullpathbuflen - 1;
270     memcpy(fullpathbuf, path, plen);
271 
272     /* add the path separator if necessary (and if there's room) */
273     if (add_sep && plen + 2 < fullpathbuflen)
274         fullpathbuf[plen++] = OSPATHCHAR;
275 
276     /* add the filename after the path, if there's room */
277     flen = strlen(filename);
278     if (flen > fullpathbuflen - plen - 1)
279         flen = fullpathbuflen - plen - 1;
280     memcpy(fullpathbuf + plen, filename, flen);
281 
282     /* add a null terminator */
283     fullpathbuf[plen + flen] = '\0';
284 }
285 
286 /*
287  *   Determine if a path is absolute or relative.  If the path starts with
288  *   a path separator character, we consider it absolute, otherwise we
289  *   consider it relative.
290  *
291  *   Note that, on DOS, an absolute path can also follow a drive letter.
292  *   So, if the path contains a letter followed by a colon, we'll consider
293  *   the path to be absolute.
294  */
os_is_file_absolute(const char * fname)295 int os_is_file_absolute(const char *fname)
296 {
297     /* if the name starts with a path separator, it's absolute */
298     if (fname[0] == OSPATHCHAR || strchr(OSPATHALT, fname[0])  != 0)
299         return TRUE;
300 
301 #ifdef MSDOS
302     /* on DOS, a file is absolute if it starts with a drive letter */
303     if (isalpha(fname[0]) && fname[1] == ':')
304         return TRUE;
305 #endif /* MSDOS */
306 
307     /* the path is relative */
308     return FALSE;
309 }
310 
311 
312 #endif /* USE_DOSEXT */
313 
314 /* ------------------------------------------------------------------------ */
315 
316 /*
317  *   A port can define USE_TIMERAND if it wishes to randomize from the
318  *   system clock.  This should be usable by most ports.
319  */
320 #ifdef USE_TIMERAND
321 # include <time.h>
322 
os_rand(long * seed)323 void os_rand(long *seed)
324 {
325     time_t t;
326 
327     time( &t );
328     *seed = (long)t;
329 }
330 #endif /* USE_TIMERAND */
331 
332 #ifdef USE_PATHSEARCH
333 /* search a path specified in the environment for a tads file */
pathfind(const char * fname,int flen,const char * pathvar,char * buf,size_t bufsiz)334 static int pathfind(const char *fname, int flen, const char *pathvar,
335                     char *buf, size_t bufsiz)
336 {
337     char   *e;
338 
339     if ( !(e = getenv(pathvar)) )
340         return(0);
341     for ( ;; )
342     {
343         char   *sep;
344         size_t  len;
345 
346         if ( (sep = strchr(e, OSPATHSEP)) )
347         {
348             len = (size_t)(sep-e);
349             if (!len) continue;
350         }
351         else
352         {
353             len = strlen(e);
354             if (!len) break;
355         }
356         memcpy(buf, e, len);
357         if (buf[len-1] != OSPATHCHAR && !strchr(OSPATHALT, buf[len-1]))
358             buf[len++] = OSPATHCHAR;
359         memcpy(buf+len, fname, flen);
360         buf[len+flen] = 0;
361         if (osfacc(buf) == 0) return(1);
362         if (!sep) break;
363         e = sep+1;
364     }
365     return(0);
366 }
367 #endif /* USE_PATHSEARCH */
368 
369 /*
370  *   Look for a tads-related file in the standard locations and, if the
371  *   search is successful, store the result file name in the given buffer.
372  *   Return 1 if the file was located, 0 if not.
373  *
374  *   Search the following areas for the file: current directory, program
375  *   directory (as derived from argv[0]), and the TADS path.
376  */
os_locate(const char * fname,int flen,const char * arg0,char * buf,size_t bufsiz)377 int os_locate(const char *fname, int flen, const char *arg0,
378               char *buf, size_t bufsiz)
379 {
380     /* Check the current directory */
381     if (osfacc(fname) == 0)
382     {
383         memcpy(buf, fname, flen);
384         buf[flen] = 0;
385         return(1);
386     }
387 
388     /* Check the program directory */
389     if (arg0 && *arg0)
390     {
391         const char *p;
392 
393         /* find the end of the directory name of argv[0] */
394         for ( p = arg0 + strlen(arg0);
395               p > arg0 && *(p-1) != OSPATHCHAR && !strchr(OSPATHALT, *(p-1));
396               --p )
397             ;
398 
399         /* don't bother if there's no directory on argv[0] */
400         if (p > arg0)
401         {
402             size_t  len = (size_t)(p - arg0);
403 
404             memcpy(buf, arg0, len);
405             memcpy(buf+len, fname, flen);
406             buf[len+flen] = 0;
407             if (osfacc(buf) == 0) return(1);
408         }
409     }
410 
411 #ifdef USE_PATHSEARCH
412     /* Check TADS path */
413     if ( pathfind(fname, flen, "TADS", buf, bufsiz) )
414         return(1);
415 #endif /* USE_PATHSEARCH */
416 
417     return(0);
418 }
419 
420 /*
421  *   Create and open a temporary file
422  */
os_create_tempfile(const char * swapname,char * buf)423 osfildef *os_create_tempfile(const char *swapname, char *buf)
424 {
425     osfildef *fp;
426 
427     /* if a name wasn't provided, generate a name */
428     if (swapname == 0)
429     {
430         int     attempt;
431         size_t  len;
432         time_t  timer;
433         int     found;
434         int     tries;
435 
436         /*
437          *   try once with the temporary file path; if that fails, simply
438          *   try using the current working directory
439          */
440         for (found = FALSE, tries = 0 ; !found && tries < 2 ; ++tries)
441         {
442             /*
443              *   If this is the first try, get the appropriate path for a
444              *   temp file.  If that failed, try using the current
445              *   directory.
446              */
447             if (tries == 0)
448             {
449                 /* get the temporary path */
450                 os_get_tmp_path(buf);
451                 len = strlen(buf);
452             }
453             else
454             {
455                 /*
456                  *   the temporary path didn't work, so this time try it in
457                  *   the current directory
458                  */
459                 buf[0] = '\0';
460                 len = 0;
461             }
462 
463             /* get the current time, as a basis for a unique identifier */
464             time(&timer);
465 
466             /* try until we find a non-existent filename */
467             for (attempt = 0 ; attempt < 100 ; ++attempt)
468             {
469                 /* generate a name based on time and try number */
470                 sprintf(buf + len, "SW%06ld.%03d",
471                         (long)timer % 999999, attempt);
472 
473                 /* check to see if a file by this name already exists */
474                 if (osfacc(buf))
475                 {
476                     /* the file doesn't exist - try creating it */
477                     fp = osfoprwtb(buf, OSFTSWAP);
478 
479                     /* if that succeeded, return this file */
480                     if (fp != 0)
481                     {
482                         /* set the file's type in the OS, if necessary */
483                         os_settype(buf, OSFTSWAP);
484 
485                         /* we're done - return the open file handle */
486                         return fp;
487                     }
488                 }
489             }
490         }
491 
492         /* we failed to find a name we could use - give up */
493         return 0;
494     }
495     else
496     {
497         /* open the file they gave us */
498         fp = osfoprwtb(swapname, OSFTSWAP);
499 
500         /* set the file's type in the OS, if necessary */
501         os_settype(swapname, OSFTSWAP);
502 
503         /* return the file pointer */
504         return fp;
505     }
506 }
507 
508 /*
509  *   Delete a temporary file.  Since our generic implementation of
510  *   os_create_tempfile() simply uses osfoprwtb() to open the file, a
511  *   temporary file's handle is not any different from any other file
512  *   handle - in particular, the OS doesn't automatically delete the file
513  *   when closed.  Hence, we need to delete the file explicitly here.
514  */
osfdel_temp(const char * fname)515 int osfdel_temp(const char *fname)
516 {
517     /* delete the file using the normal mechanism */
518     return osfdel(fname);
519 }
520 
521 
522 /*
523  *   print a null-terminated string to osfildef* file
524  */
os_fprintz(osfildef * fp,const char * str)525 void os_fprintz(osfildef *fp, const char *str)
526 {
527     fprintf(fp, "%s", str);
528 }
529 
530 /*
531  *   print a counted-length string (which might not be null-terminated) to a
532  *   file
533  */
os_fprint(osfildef * fp,const char * str,size_t len)534 void os_fprint(osfildef *fp, const char *str, size_t len)
535 {
536     fprintf(fp, "%.*s", (int)len, str);
537 }
538 
539 #ifdef MSDOS
540 /*
541  *   MS-DOS implementation - Get the temporary file path.  Tries getting
542  *   the values of various environment variables that are typically used
543  *   to define the location for temporary files.
544  */
os_get_tmp_path(char * buf)545 void os_get_tmp_path(char *buf)
546 {
547     static char *vars[] =
548     {
549         "TEMPDIR",
550         "TMPDIR",
551         "TEMP",
552         "TMP",
553         0
554     };
555     char **varp;
556 
557     /* look for an environment variable from our list */
558     for (varp = vars ; *varp ; ++varp)
559     {
560         char *val;
561 
562         /* get this variable's value */
563         val = getenv(*varp);
564         if (val)
565         {
566             size_t  len;
567 
568             /* use this value */
569             strcpy(buf, val);
570 
571             /* add a backslash if necessary */
572             if ((len = strlen(buf)) != 0
573                 && buf[len-1] != '/' && buf[len-1] != '\\')
574             {
575                 buf[len] = '\\';
576                 buf[len+1] = '\0';
577             }
578 
579             /* use this value */
580             return;
581         }
582     }
583 
584     /* didn't find anything - leave the prefix empty */
585     buf[0] = '\0';
586 }
587 #endif /* MSDOS */
588 
589 #ifdef USE_NULLSTYPE
os_settype(const char * f,os_filetype_t t)590 void os_settype(const char *f, os_filetype_t t)
591 {
592     /* nothing needs to be done on this system */
593 }
594 #endif /* USE_NULLSTYPE */
595 
596 /*
597  *   Convert an OS filename path to a relative URL
598  */
os_cvt_dir_url(char * result_buf,size_t result_buf_size,const char * src_path,int end_sep)599 void os_cvt_dir_url(char *result_buf, size_t result_buf_size,
600                     const char *src_path, int end_sep)
601 {
602     char *dst;
603     const char *src;
604     size_t rem;
605     int last_was_sep;
606     static const char quoted[] = ":%";
607 
608     /*
609      *   Run through the source buffer, copying characters to the output
610      *   buffer.  If we encounter a path separator character, replace it
611      *   with a forward slash.
612      */
613     for (last_was_sep = FALSE, dst = result_buf, src = src_path,
614          rem = result_buf_size ;
615          *src != '\0' && rem > 1 ; ++dst, ++src, --rem)
616     {
617         /* presume this will not be a path separator character */
618         last_was_sep = FALSE;
619 
620         /*
621          *   If this is a local path separator character, replace it with the
622          *   URL-style path separator character.  If it's an illegal URL
623          *   character, quote it with "%" notation.  Otherwise, copy it
624          *   unchanged.
625          */
626         if (strchr(OSPATHURL, *src) != 0)
627         {
628             /* add the URL-style path separator instead of the local one */
629             *dst = '/';
630 
631             /* note that we just added a path separator character */
632             last_was_sep = TRUE;
633         }
634         else if (strchr(quoted, *src) != 0)
635         {
636             /* if we have room for three characters, add the "%" sequence */
637             if (rem > 3)
638             {
639                 int dig;
640 
641                 /* add the '%' */
642                 *dst++ = '%';
643 
644                 /* add the high-order digit */
645                 dig = ((int)(unsigned char)*src) >> 4;
646                 *dst++ = (dig < 10 ? dig + '0' : dig + 'A' - 10);
647 
648                 /* add the low-order digit */
649                 dig = ((int)(unsigned char)*src) & 0x0F;
650                 *dst = (dig < 10 ? dig + '0' : dig + 'A' - 10);
651 
652                 /* deduct the extra two characters beyond the expected one */
653                 rem -= 2;
654             }
655         }
656         else
657         {
658             /* add the character unchanged */
659             *dst = *src;
660         }
661     }
662 
663     /*
664      *   add an additional ending separator if desired and if the last
665      *   character wasn't a separator
666      */
667     if (end_sep && rem > 1 && !last_was_sep)
668         *dst++ = '/';
669 
670     /* add a null terminator and we're done */
671     *dst = '\0';
672 }
673 
674 /*
675  *   Convert a relative URL to a relative file system path name
676  */
os_cvt_url_dir(char * result_buf,size_t result_buf_size,const char * src_url,int end_sep)677 void os_cvt_url_dir(char *result_buf, size_t result_buf_size,
678                     const char *src_url, int end_sep)
679 {
680     char *dst;
681     const char *src;
682     size_t rem;
683     int last_was_sep;
684 
685     /*
686      *   Run through the source buffer, copying characters to the output
687      *   buffer.  If we encounter a '/', convert it to a path separator
688      *   character.
689      */
690     for (last_was_sep = FALSE, dst = result_buf, src = src_url,
691          rem = result_buf_size ;
692          *src != '\0' && rem > 1 ; ++dst, ++src, --rem)
693     {
694         /*
695          *   replace slashes with path separators; expand '%' sequences; copy
696          *   all other characters unchanged
697          */
698         if (*src == '/')
699         {
700             *dst = OSPATHCHAR;
701             last_was_sep = TRUE;
702         }
703         else if (*src == '%'
704                  && isxdigit((uchar)*(src+1))
705                  && isxdigit((uchar)*(src+2)))
706         {
707             unsigned char c;
708             unsigned char acc;
709 
710             /* convert the '%xx' sequence to its character code */
711             c = *++src;
712             acc = (c - (c >= 'A' && c <= 'F'
713                         ? 'A' - 10
714                         : c >= 'a' && c <= 'f'
715                         ? 'a' - 10
716                         : '0')) << 4;
717             c = *++src;
718             acc += (c - (c >= 'A' && c <= 'F'
719                          ? 'A' - 10
720                          : c >= 'a' && c <= 'f'
721                          ? 'a' - 10
722                          : '0'));
723 
724             /* set the character */
725             *dst = acc;
726 
727             /* it's not a separator */
728             last_was_sep = FALSE;
729         }
730         else
731         {
732             *dst = *src;
733             last_was_sep = FALSE;
734         }
735     }
736 
737     /*
738      *   add an additional ending separator if desired and if the last
739      *   character wasn't a separator
740      */
741     if (end_sep && rem > 1 && !last_was_sep)
742         *dst++ = OSPATHCHAR;
743 
744     /* add a null terminator and we're done */
745     *dst = '\0';
746 }
747 
748 
749 /* ------------------------------------------------------------------------ */
750 /*
751  *   service routine for searching - build the full output path name
752  */
oss_build_outpathbuf(char * outpathbuf,size_t outpathbufsiz,const char * path,const char * fname)753 static void oss_build_outpathbuf(char *outpathbuf, size_t outpathbufsiz,
754                                  const char *path, const char *fname)
755 {
756     /* if there's a full path buffer, build the full path */
757     if (outpathbuf != 0)
758     {
759         size_t lp;
760         size_t lf;
761 
762         /* copy the path prefix */
763         lp = strlen(path);
764         if (lp > outpathbufsiz - 1)
765             lp = outpathbufsiz - 1;
766         memcpy(outpathbuf, path, lp);
767 
768         /* add the filename if there's any room */
769         lf = strlen(fname);
770         if (lf > outpathbufsiz - lp - 1)
771             lf = outpathbufsiz - lp - 1;
772         memcpy(outpathbuf + lp, fname, lf);
773 
774         /* null-terminate the result */
775         outpathbuf[lp + lf] = '\0';
776     }
777 }
778 
779 /* ------------------------------------------------------------------------ */
780 /*
781  *   Borland C implementation of directory functions
782  */
783 #if defined(TURBO) || defined(DJGPP)
784 
785 #include <dir.h>
786 #include <dos.h>
787 
788 /*
789  *   search context structure
790  */
791 struct oss_find_ctx_t
792 {
793     /* C library find-file block */
794     struct ffblk ff;
795 
796     /* original search path prefix (we'll allocate more to fit the string) */
797     char path[1];
798 };
799 
800 /*
801  *   find first matching file
802  */
os_find_first_file(const char * dir,const char * pattern,char * outbuf,size_t outbufsiz,int * isdir,char * outpathbuf,size_t outpathbufsiz)803 void *os_find_first_file(const char *dir, const char *pattern, char *outbuf,
804                          size_t outbufsiz, int *isdir,
805                          char *outpathbuf, size_t outpathbufsiz)
806 {
807     struct oss_find_ctx_t *ctx;
808     char realpat[OSFNMAX];
809     size_t l;
810     size_t path_len;
811     const char *lastsep;
812     const char *p;
813 
814     /*
815      *   construct the filename pattern from the directory and pattern
816      *   segments, using "*" as the default wildcard pattern if no
817      *   explicit pattern was provided
818      */
819     strcpy(realpat, dir);
820     if ((l = strlen(realpat)) != 0 && realpat[l - 1] != '\\')
821         realpat[l++] = '\\';
822     if (pattern == 0)
823         strcpy(realpat + l, "*.*");
824     else
825         strcpy(realpat + l, pattern);
826 
827     /* find the last separator in the original path */
828     for (p = realpat, lastsep = 0 ; *p != '\0' ; ++p)
829     {
830         /* if this is a separator, remember it */
831         if (*p == '\\' || *p == '/' || *p == ':')
832             lastsep = p;
833     }
834 
835     /*
836      *   if we found a separator, the path prefix is everything up to and
837      *   including the separator; otherwise, there's no path prefix
838      */
839     if (lastsep != 0)
840     {
841         /* the length of the path includes the separator */
842         path_len = lastsep + 1 - realpat;
843     }
844     else
845     {
846         /* there's no path prefix - everything is in the current directory */
847         path_len = 0;
848     }
849 
850     /* allocate a context */
851     ctx = (struct oss_find_ctx_t *)malloc(sizeof(struct oss_find_ctx_t)
852                                           + path_len);
853 
854     /* copy the path to the context */
855     memcpy(ctx->path, realpat, path_len);
856     ctx->path[path_len] = '\0';
857 
858     /* call DOS to search for a matching file */
859     if (findfirst(realpat, &ctx->ff, FA_DIREC))
860     {
861         /* no dice - delete the context and return failure */
862         free(ctx);
863         return 0;
864     }
865 
866     /* copy the filename into the caller's buffer */
867     l = strlen(ctx->ff.ff_name);
868     if (l > outbufsiz - 1)
869         l = outbufsiz - 1;
870     memcpy(outbuf, ctx->ff.ff_name, l);
871     outbuf[l] = '\0';
872 
873     /* build the full path, if desired */
874     oss_build_outpathbuf(outpathbuf, outpathbufsiz,
875                          ctx->path, ctx->ff.ff_name);
876 
877     /* return the directory indication  */
878     *isdir = (ctx->ff.ff_attrib & FA_DIREC) != 0;
879 
880     /*
881      *   return the context - it will be freed later when find-next
882      *   reaches the last file or find-close is called to cancel the
883      *   search
884      */
885     return ctx;
886 }
887 
888 /* find the next file */
os_find_next_file(void * ctx0,char * outbuf,size_t outbufsiz,int * isdir,char * outpathbuf,size_t outpathbufsiz)889 void *os_find_next_file(void *ctx0, char *outbuf, size_t outbufsiz,
890                         int *isdir, char *outpathbuf, size_t outpathbufsiz)
891 {
892     struct oss_find_ctx_t *ctx = (struct oss_find_ctx_t *)ctx0;
893     size_t l;
894 
895     /* if the search has already ended, do nothing */
896     if (ctx == 0)
897         return 0;
898 
899     /* try searching for the next file */
900     if (findnext(&ctx->ff))
901     {
902         /* no more files - delete the context and give up */
903         free(ctx);
904 
905         /* return null to indicate that the search is finished */
906         return 0;
907     }
908 
909     /* return the name */
910     l = strlen(ctx->ff.ff_name);
911     if (l > outbufsiz - 1) l = outbufsiz - 1;
912     memcpy(outbuf, ctx->ff.ff_name, l);
913     outbuf[l] = '\0';
914 
915     /* return the directory indication  */
916     *isdir = (ctx->ff.ff_attrib & FA_DIREC) != 0;
917 
918     /* build the full path, if desired */
919     oss_build_outpathbuf(outpathbuf, outpathbufsiz,
920                          ctx->path, ctx->ff.ff_name);
921 
922     /*
923      *   indicate that the search was successful by returning the non-null
924      *   context pointer -- the context has been updated for the new
925      *   position in the search, so we just return the same context
926      *   structure that we've been using all along
927      */
928     return ctx;
929 }
930 
931 /* cancel a search */
os_find_close(void * ctx0)932 void os_find_close(void *ctx0)
933 {
934     struct oss_find_ctx_t *ctx = (struct oss_find_ctx_t *)ctx0;
935 
936     /* if the search was already cancelled, do nothing */
937     if (ctx == 0)
938         return;
939 
940     /* delete the search context */
941     free(ctx);
942 }
943 
944 /*
945  *   Time-zone initialization
946  */
os_tzset()947 void os_tzset()
948 {
949     /* let the run-time library initialize the time zone */
950     tzset();
951 }
952 
953 /* ------------------------------------------------------------------------ */
954 /*
955  *   Get file times
956  */
957 
958 /*
959  *   get file creation time
960  */
os_get_file_cre_time(os_file_time_t * t,const char * fname)961 int os_get_file_cre_time(os_file_time_t *t, const char *fname)
962 {
963     struct stat info;
964 
965     /* get the file information */
966     if (stat(fname, &info))
967         return 1;
968 
969     /* set the creation time in the return structure */
970     t->t = info.st_ctime;
971     return 0;
972 }
973 
974 /*
975  *   get file modification time
976  */
os_get_file_mod_time(os_file_time_t * t,const char * fname)977 int os_get_file_mod_time(os_file_time_t *t, const char *fname)
978 {
979     struct stat info;
980 
981     /* get the file information */
982     if (stat(fname, &info))
983         return 1;
984 
985     /* set the modification time in the return structure */
986     t->t = info.st_mtime;
987     return 0;
988 }
989 
990 /*
991  *   get file last access time
992  */
os_get_file_acc_time(os_file_time_t * t,const char * fname)993 int os_get_file_acc_time(os_file_time_t *t, const char *fname)
994 {
995     struct stat info;
996 
997     /* get the file information */
998     if (stat(fname, &info))
999         return 1;
1000 
1001     /* set the access time in the return structure */
1002     t->t = info.st_atime;
1003     return 0;
1004 }
1005 
1006 /*
1007  *   compare two file time structures
1008  */
os_cmp_file_times(const os_file_time_t * a,const os_file_time_t * b)1009 int os_cmp_file_times(const os_file_time_t *a, const os_file_time_t *b)
1010 {
1011     if (a->t < b->t)
1012         return -1;
1013     else if (a->t == b->t)
1014         return 0;
1015     else
1016         return 1;
1017 }
1018 
1019 #endif /* TURBO || DJGPP */
1020 
1021 /* ------------------------------------------------------------------------ */
1022 /*
1023  *   Microsoft C implementation of directory functions
1024  */
1025 #ifdef MICROSOFT
1026 
1027 typedef struct
1028 {
1029     /* search handle */
1030     long handle;
1031 
1032     /* found data structure */
1033     struct _finddata_t data;
1034 
1035     /* full original search path */
1036     char path[1];
1037 } ossfcx;
1038 
1039 /*
1040  *   find first matching file
1041  */
os_find_first_file(const char * dir,const char * pattern,char * outbuf,size_t outbufsiz,int * isdir,char * outpathbuf,size_t outpathbufsiz)1042 void *os_find_first_file(const char *dir, const char *pattern, char *outbuf,
1043                          size_t outbufsiz, int *isdir,
1044                          char *outpathbuf, size_t outpathbufsiz)
1045 {
1046     ossfcx *ctx;
1047     char realpat[OSFNMAX];
1048     size_t l;
1049     size_t path_len;
1050     const char *lastsep;
1051     const char *p;
1052 
1053     /*
1054      *   construct the filename pattern from the directory and pattern
1055      *   segments, using "*" as the default wildcard pattern if no
1056      *   explicit pattern was provided
1057      */
1058     strcpy(realpat, dir);
1059     if ((l = strlen(realpat)) != 0 && realpat[l - 1] != '\\')
1060         realpat[l++] = '\\';
1061     if (pattern == 0)
1062         strcpy(realpat + l, "*");
1063     else
1064         strcpy(realpat + l, pattern);
1065 
1066     /* find the last separator in the original path */
1067     for (p = realpat, lastsep = 0 ; *p != '\0' ; ++p)
1068     {
1069         /* if this is a separator, remember it */
1070         if (*p == '\\' || *p == '/' || *p == ':')
1071             lastsep = p;
1072     }
1073 
1074     /*
1075      *   if we found a separator, the path prefix is everything up to and
1076      *   including the separator; otherwise, there's no path prefix
1077      */
1078     if (lastsep != 0)
1079     {
1080         /* the length of the path includes the separator */
1081         path_len = lastsep + 1 - realpat;
1082     }
1083     else
1084     {
1085         /* there's no path prefix - everything is in the current directory */
1086         path_len = 0;
1087     }
1088 
1089     /* allocate a context */
1090     ctx = (ossfcx *)malloc(sizeof(ossfcx) + path_len);
1091 
1092     /* copy the path to the context */
1093     memcpy(ctx->path, realpat, path_len);
1094     ctx->path[path_len] = '\0';
1095 
1096     /* call DOS to search for a matching file */
1097     ctx->handle = _findfirst(realpat, &ctx->data);
1098     if (ctx->handle == -1L)
1099     {
1100         /* no dice - delete the context and return failure */
1101         free(ctx);
1102         return 0;
1103     }
1104 
1105     /* return the name */
1106     l = strlen(ctx->data.name);
1107     if (l > outbufsiz - 1) l = outbufsiz - 1;
1108     memcpy(outbuf, ctx->data.name, l);
1109     outbuf[l] = '\0';
1110 
1111     /* return the directory indication  */
1112     *isdir = (ctx->data.attrib & _A_SUBDIR) != 0;
1113 
1114     /* build the full path, if desired */
1115     oss_build_outpathbuf(outpathbuf, outpathbufsiz,
1116                          ctx->path, ctx->data.name);
1117 
1118     /*
1119      *   return the context - it will be freed later when find-next
1120      *   reaches the last file or find-close is called to cancel the
1121      *   search
1122      */
1123     return ctx;
1124 }
1125 
1126 /* find the next file */
os_find_next_file(void * ctx0,char * outbuf,size_t outbufsiz,int * isdir,char * outpathbuf,size_t outpathbufsiz)1127 void *os_find_next_file(void *ctx0, char *outbuf, size_t outbufsiz,
1128                         int *isdir, char *outpathbuf, size_t outpathbufsiz)
1129 {
1130     ossfcx *ctx = (ossfcx *)ctx0;
1131     size_t l;
1132 
1133     /* if the search has already ended, do nothing */
1134     if (ctx == 0)
1135         return 0;
1136 
1137     /* try searching for the next file */
1138     if (_findnext(ctx->handle, &ctx->data) != 0)
1139     {
1140         /* no more files - close the search and delete the context */
1141         _findclose(ctx->handle);
1142         free(ctx);
1143 
1144         /* return null to indicate that the search is finished */
1145         return 0;
1146     }
1147 
1148     /* return the name */
1149     l = strlen(ctx->data.name);
1150     if (l > outbufsiz - 1) l = outbufsiz - 1;
1151     memcpy(outbuf, ctx->data.name, l);
1152     outbuf[l] = '\0';
1153 
1154     /* return the directory indication  */
1155     *isdir = (ctx->data.attrib & _A_SUBDIR) != 0;
1156 
1157     /* build the full path, if desired */
1158     oss_build_outpathbuf(outpathbuf, outpathbufsiz,
1159                          ctx->path, ctx->data.name);
1160 
1161     /*
1162      *   indicate that the search was successful by returning the non-null
1163      *   context pointer -- the context has been updated for the new
1164      *   position in the search, so we just return the same context
1165      *   structure that we've been using all along
1166      */
1167     return ctx;
1168 }
1169 
1170 /* cancel a search */
os_find_close(void * ctx0)1171 void os_find_close(void *ctx0)
1172 {
1173     ossfcx *ctx = (ossfcx *)ctx0;
1174 
1175     /* if the search was already cancelled, do nothing */
1176     if (ctx == 0)
1177         return;
1178 
1179     /* close the search and delete the context structure */
1180     _findclose(ctx->handle);
1181     free(ctx);
1182 }
1183 
1184 /*
1185  *   Time-zone initialization
1186  */
os_tzset()1187 void os_tzset()
1188 {
1189     /* let the run-time library initialize the time zone */
1190     tzset();
1191 }
1192 
1193 /* ------------------------------------------------------------------------ */
1194 /*
1195  *   Get file times
1196  */
1197 
1198 /*
1199  *   get file creation time
1200  */
os_get_file_cre_time(os_file_time_t * t,const char * fname)1201 int os_get_file_cre_time(os_file_time_t *t, const char *fname)
1202 {
1203     struct stat info;
1204 
1205     /* get the file information */
1206     if (stat(fname, &info))
1207         return 1;
1208 
1209     /* set the creation time in the return structure */
1210     t->t = info.st_ctime;
1211     return 0;
1212 }
1213 
1214 /*
1215  *   get file modification time
1216  */
os_get_file_mod_time(os_file_time_t * t,const char * fname)1217 int os_get_file_mod_time(os_file_time_t *t, const char *fname)
1218 {
1219     struct stat info;
1220 
1221     /* get the file information */
1222     if (stat(fname, &info))
1223         return 1;
1224 
1225     /* set the modification time in the return structure */
1226     t->t = info.st_mtime;
1227     return 0;
1228 }
1229 
1230 /*
1231  *   get file last access time
1232  */
os_get_file_acc_time(os_file_time_t * t,const char * fname)1233 int os_get_file_acc_time(os_file_time_t *t, const char *fname)
1234 {
1235     struct stat info;
1236 
1237     /* get the file information */
1238     if (stat(fname, &info))
1239         return 1;
1240 
1241     /* set the access time in the return structure */
1242     t->t = info.st_atime;
1243     return 0;
1244 }
1245 
1246 /*
1247  *   compare two file time structures
1248  */
os_cmp_file_times(const os_file_time_t * a,const os_file_time_t * b)1249 int os_cmp_file_times(const os_file_time_t *a, const os_file_time_t *b)
1250 {
1251     if (a->t < b->t)
1252         return -1;
1253     else if (a->t == b->t)
1254         return 0;
1255     else
1256         return 1;
1257 }
1258 
1259 #endif /* MICROSOFT */
1260 
1261 /* ------------------------------------------------------------------------ */
1262 /*
1263  *   OS/2 implementation of directory functions
1264  */
1265 #ifdef MSOS2
1266 
1267 /* C library context structure used for file searches */
1268 #ifdef __32BIT__
1269 # define DosFindFirst(a,b,c,d,e,f)  DosFindFirst(a,b,c,d,e,f,FIL_STANDARD)
1270 typedef FILEFINDBUF3    oss_c_ffcx;
1271 typedef ULONG           count_t;
1272 #else /* !__32BIT__ */
1273 # define DosFindFirst(a,b,c,d,e,f)  DosFindFirst(a,b,c,d,e,f,0L)
1274 typedef FILEFINDBUF     oss_c_ffcx;
1275 typedef USHORT          count_t;
1276 #endif /* __32BIT__ */
1277 
1278 typedef struct
1279 {
1280     /* C library context structure */
1281     oss_c_ffcx ff;
1282 
1283     /* original search path */
1284     char path[1];
1285 } ossfcx;
1286 
os_find_first_file(const char * dir,const char * pattern,char * outbuf,size_t outbufsiz,int * isdir,char * outpathbuf,size_t outpathbufsiz)1287 void *os_find_first_file(const char *dir, const char *pattern, char *outbuf,
1288                          size_t outbufsiz, int *isdir,
1289                          char *outpathbuf, size_t outpathbufsiz)
1290 {
1291     ossfcx  *ctx;
1292     char     realpat[OSFNMAX];
1293     size_t   l;
1294     HDIR     hdir = HDIR_SYSTEM;
1295     count_t  scnt = 1;
1296     size_t path_len;
1297     const char *lastsep;
1298     const char *p;
1299 
1300     /*
1301      *   construct the filename pattern from the directory and pattern
1302      *   segments, using "*" as the default wildcard pattern if no
1303      *   explicit pattern was provided
1304      */
1305     strcpy(realpat, dir);
1306     if ((l = strlen(realpat)) != 0 && realpat[l - 1] != '\\')
1307         realpat[l++] = '\\';
1308     if (pattern == 0)
1309         strcpy(realpat + l, "*");
1310     else
1311         strcpy(realpat + l, pattern);
1312 
1313     /* find the last separator in the original path */
1314     for (p = realpat, lastsep = 0 ; *p != '\0' ; ++p)
1315     {
1316         /* if this is a separator, remember it */
1317         if (*p == '\\' || *p == '/' || *p == ':')
1318             lastsep = p;
1319     }
1320 
1321     /*
1322      *   if we found a separator, the path prefix is everything up to and
1323      *   including the separator; otherwise, there's no path prefix
1324      */
1325     if (lastsep != 0)
1326     {
1327         /* the length of the path includes the separator */
1328         path_len = lastsep + 1 - realpat;
1329     }
1330     else
1331     {
1332         /* there's no path prefix - everything is in the current directory */
1333         path_len = 0;
1334     }
1335 
1336     /* allocate a context */
1337     ctx = (ossfcx *)malloc(sizeof(ossfcx) + path_len);
1338 
1339     /* call DOS to search for a matching file */
1340     if (DosFindFirst(realpat, &hdir, FILE_DIRECTORY | FILE_NORMAL,
1341                      &ctx->ff, sizeof(ctx->ff), &scnt))
1342     {
1343         /* no dice - delete the context and return failure */
1344         free(ctx);
1345         return 0;
1346     }
1347 
1348     /* copy the path to the context */
1349     memcpy(ctx->path, realpat, path_len);
1350     ctx->path[path_len] = '\0';
1351 
1352     /* return the name */
1353     l = ctx->ff.cchName;
1354     if (l > outbufsiz - 1) l = outbufsiz - 1;
1355     memcpy(outbuf, ctx->ff.achName, l);
1356     outbuf[l] = '\0';
1357 
1358     /* return the directory indication  */
1359     *isdir = (ctx->ff.attrFile & FILE_DIRECTORY) != 0;
1360 
1361     /* build the full path, if desired */
1362     oss_build_outpathbuf(outpathbuf, outpathbufsiz,
1363                          ctx->path, ctx->ff.data.name);
1364 
1365     /*
1366      *   return the context - it will be freed later when find-next
1367      *   reaches the last file or find-close is called to cancel the
1368      *   search
1369      */
1370     return ctx;
1371 }
1372 
1373 /* find the next file */
os_find_next_file(void * ctx0,char * outbuf,size_t outbufsiz,int * isdir,char * outpathbuf,size_t outpathbufsiz)1374 void *os_find_next_file(void *ctx0, char *outbuf, size_t outbufsiz,
1375                         int *isdir, char *outpathbuf, size_t outpathbufsiz)
1376 {
1377     ossfcx *ctx = (ossfcx *)ctx0;
1378     size_t l;
1379 
1380     /* if the search has already ended, do nothing */
1381     if (ctx == 0)
1382         return 0;
1383 
1384     /* try searching for the next file */
1385     if (DosFindNext(HDIR_SYSTEM, &ctx->ff, sizeof(ctx->ff), &scnt))
1386     {
1387         /* no more files - delete the context and give up */
1388         free(ctx);
1389 
1390         /* return null to indicate that the search is finished */
1391         return 0;
1392     }
1393 
1394     /* return the name */
1395     l = ctx->ff.cchName;
1396     if (l > outbufsiz - 1) l = outbufsiz - 1;
1397     memcpy(outbuf, ctx->ff.achName, l);
1398     outbuf[l] = '\0';
1399 
1400     /* return the directory indication  */
1401     *isdir = (ctx->ff.attrFile & FILE_DIRECTORY) != 0;
1402 
1403     /* build the full path, if desired */
1404     oss_build_outpathbuf(outpathbuf, outpathbufsiz,
1405                          ctx->ff.path, ctx->ff.data.name);
1406 
1407     /*
1408      *   indicate that the search was successful by returning the non-null
1409      *   context pointer -- the context has been updated for the new
1410      *   position in the search, so we just return the same context
1411      *   structure that we've been using all along
1412      */
1413     return ctx;
1414 }
1415 
1416 /* cancel a search */
os_find_close(void * ctx0)1417 void os_find_close(void *ctx0)
1418 {
1419     ossfcx *ctx = (ossfcx *)ctx0;
1420 
1421     /* if the search was already cancelled, do nothing */
1422     if (ctx == 0)
1423         return;
1424 
1425     /* delete the context structure */
1426     free(ctx);
1427 }
1428 
1429 #endif /* MSOS2 */
1430 
1431 #ifdef MSDOS
1432 /*
1433  *   Check for a special filename
1434  */
os_is_special_file(const char * fname)1435 enum os_specfile_t os_is_special_file(const char *fname)
1436 {
1437     /* check for '.' */
1438     if (strcmp(fname, ".") == 0)
1439         return OS_SPECFILE_SELF;
1440 
1441     /* check for '..' */
1442     if (strcmp(fname, "..") == 0)
1443         return OS_SPECFILE_PARENT;
1444 
1445     /* not a special file */
1446     return OS_SPECFILE_NONE;
1447 }
1448 
1449 #endif /* MSDOS */
1450