]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - usr.bin/doscmd/cwd.c
Enable the new libmp in the build, and disable libgmp and its
[FreeBSD/FreeBSD.git] / usr.bin / doscmd / cwd.c
1 /*
2  * Copyright (c) 1992, 1993, 1996
3  *      Berkeley Software Design, Inc.  All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  * 3. All advertising materials mentioning features or use of this software
14  *    must display the following acknowledgement:
15  *      This product includes software developed by Berkeley Software
16  *      Design, Inc.
17  *
18  * THIS SOFTWARE IS PROVIDED BY Berkeley Software Design, Inc. ``AS IS'' AND
19  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21  * ARE DISCLAIMED.  IN NO EVENT SHALL Berkeley Software Design, Inc. BE LIABLE
22  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28  * SUCH DAMAGE.
29  *
30  *      BSDI cwd.c,v 2.2 1996/04/08 19:32:25 bostic Exp
31  *
32  * $FreeBSD$
33  */
34
35 #include <sys/types.h>
36 #include <sys/param.h>
37 #include <sys/mount.h>
38 #include <dirent.h>
39 #include <errno.h>
40 #include <unistd.h>
41 #include <stdlib.h>
42 #include <string.h>
43 #include <ctype.h>
44 #include <stdio.h>
45
46 #include "doscmd.h"
47 #include "cwd.h"
48
49 #define D_REDIR         0x0080000       /* XXX - ack */
50 #define D_TRAPS3        0x0200000
51
52 typedef struct {
53     u_char              *path;
54     u_char              *cwd;
55     int                 len;
56     int                 maxlen;
57     int                 read_only:1;
58 } Path_t;
59
60 typedef struct Name_t {
61     u_char              *real;
62     struct Name_t       *next;
63     u_char              name[9];
64     u_char              ext[4];
65 } Name_t;
66
67
68 #define MAX_DRIVE       26
69
70 static Path_t paths[MAX_DRIVE];
71 static Name_t *names;
72
73 extern int diskdrive;
74
75 /*
76  * Initialize the drive to be based at 'base' in the BSD filesystem
77  */
78 void
79 init_path(int drive, u_char *base, u_char *dir)
80 {
81     Path_t *d;
82
83     if (drive < 0 || drive >= MAX_DRIVE)
84         return;
85
86     debug(D_TRAPS3, "init_path(%d, %s, %s)\n", drive, base, dir);
87
88     d = &paths[drive];
89
90     if (d->path)
91         free(d->path);
92
93     if ((d->path = ustrdup(base)) == NULL)
94         fatal("strdup in init_path for %c:%s: %s", drntol(drive), base,
95               strerror(errno));
96
97     if (d->maxlen < 2) {
98         d->maxlen = 128;
99         if ((d->cwd = (u_char *)malloc(d->maxlen)) == NULL)
100             fatal("malloc in init_path for %c:%s: %s", drntol(drive), base,
101                   strerror(errno));
102     }
103
104     d->cwd[0] = '\\';
105     d->cwd[1] = 0;
106     d->len = 1;
107     if (dir) {
108         if (ustrncmp(base, dir, ustrlen(base)) == 0)
109                 dir += ustrlen(base);
110         while (*dir == '/')
111             ++dir;
112
113         while (*dir) {
114             u_char dosname[15];
115             u_char realname[256];
116             u_char *r = realname;;
117
118             while ((*r = *dir) && *dir++ != '/') {
119                 ++r;
120             }
121             *r = 0;
122             while (*dir == '/')
123                 ++dir;
124
125             dosname[0] = drntol(drive);
126             dosname[1] = ':';
127             real_to_dos(realname, &dosname[2]);
128
129             if (dos_setcwd(dosname)) {
130                 fprintf(stderr, "Failed to CD to directory %s in %s\n",
131                                  dosname, d->cwd);
132             }
133         }
134     }
135 }
136
137 /*
138  * Mark this drive as read only
139  */
140 void
141 dos_makereadonly(int drive)
142 {
143
144     if (drive < 0 || drive >= MAX_DRIVE)
145         return;
146     paths[drive].read_only = 1;
147 }
148
149 /*
150  * Return read-only status of drive
151  */
152 int
153 dos_readonly(int drive)
154 {
155
156     if (drive < 0 || drive >= MAX_DRIVE)
157         return (0);
158     debug(D_REDIR, "dos_readonly(%d) -> %d\n", drive, paths[drive].read_only);
159     return (paths[drive].read_only);
160 }
161
162 /*
163  * Return DOS's idea of the CWD for drive
164  * Return 0 if the drive specified is not mapped (or bad)
165  */
166 u_char *
167 dos_getcwd(int drive)
168 {
169
170     if (drive < 0 || drive >= MAX_DRIVE)
171         return (0);
172     debug(D_REDIR, "dos_getcwd(%d) -> %s\n", drive, paths[drive].cwd);
173     return (paths[drive].cwd);
174 }
175
176 /*
177  * Return DOS's idea of the CWD for drive
178  * Return 0 if the drive specified is not mapped (or bad)
179  */
180 u_char *
181 dos_getpath(int drive)
182 {
183
184     if (drive < 0 || drive >= MAX_DRIVE)
185         return (0);
186     debug(D_REDIR, "dos_getpath(%d) -> %s\n", drive, paths[drive].path);
187     return (paths[drive].path);
188 }
189
190 /*
191  * Fix up a DOS path name.  Strip out all '.' and '..' entries, turn
192  * '/' into '\\' and convert all lowercase to uppercase.
193  * Returns 0 on success or DOS errno
194  */
195 int
196 dos_makepath(u_char *where, u_char *newpath)
197 {
198     int drive;
199     u_char **dirs;
200     u_char *np;
201     Path_t *d;
202     u_char tmppath[1024];
203     u_char *snewpath = newpath;
204
205     if (where[0] != '\0' && where[1] == ':') {
206         drive = drlton(*where);
207         *newpath++ = *where++;
208         *newpath++ = *where++;
209     } else {
210         drive = diskdrive;
211         *newpath++ = drntol(diskdrive);
212         *newpath++ = ':';
213     }
214
215     if (drive < 0 || drive >= MAX_DRIVE) {
216         debug(D_REDIR,"drive %c invalid\n", drntol(drive));
217         return (DISK_DRIVE_INVALID);
218     }
219
220     d = &paths[drive];
221     if (d->cwd == NULL) {
222         debug(D_REDIR,"no cwd for drive %c\n",drntol(drive));
223         return (DISK_DRIVE_INVALID);
224     }
225
226     debug(D_REDIR, "dos_makepath(%d, %s)\n", drive, where);
227
228     np = newpath;
229     if (*where != '\\' && *where != '/') {
230         ustrncpy(tmppath, d->cwd, 1024);
231         if (d->cwd[1])
232             ustrncat(tmppath, (u_char *)"/", 1024 - ustrlen(tmppath));
233         ustrncat(tmppath, where, 1024 - ustrlen(tmppath));
234     } else {
235         ustrncpy(tmppath, where, 1024 - ustrlen(tmppath));
236     }
237
238     dirs = get_entries(tmppath);
239     if (dirs == NULL)
240         return (PATH_NOT_FOUND);
241
242     np = newpath;
243     while (*dirs) {
244         u_char *dir = *dirs++;
245         if (*dir == '/' || *dir == '\\') {
246             np = newpath + 1;
247             newpath[0] = '\\';
248         } else if (dir[0] == '.' && dir[1] == 0) {
249             ;
250         } else if (dir[0] == '.' && dir[1] == '.' && dir[2] == '\0') {
251             while (np[-1] != '/' && np[-1] != '\\')
252                 --np;
253             if (np - 1 > newpath)
254                 --np;
255         } else {
256             if (np[-1] != '\\')
257                 *np++ = '\\';
258             while ((*np = *dir++) && np - snewpath < 1023)
259                 ++np;
260         }
261     }
262     *np = 0;
263
264     return (0);
265 }
266
267 /*
268  * Set DOS's idea of the CWD for drive to be where.
269  * Returns DOS errno on failuer.
270  */
271 int
272 dos_setcwd(u_char *where)
273 {
274     u_char new_path[1024];
275     u_char real_path[1024];
276     int drive;
277     struct stat sb;
278     Path_t *d;
279     int error;
280
281     debug(D_REDIR, "dos_setcwd(%s)\n", where);
282
283     error = dos_makepath(where, new_path);
284     if (error)
285         return (error);
286
287     error = dos_to_real_path(new_path, real_path, &drive);
288     if (error)
289         return (error);
290     
291     if (ustat(real_path, &sb) < 0 || !S_ISDIR(sb.st_mode))
292         return (PATH_NOT_FOUND);
293     if (uaccess(real_path, R_OK | X_OK))
294         return (PATH_NOT_FOUND);
295     
296     d = &paths[drive];
297     d->len = ustrlen(new_path + 2);
298
299     if (d->len + 1 > d->maxlen) {
300         free(d->cwd);
301         d->maxlen = d->len + 1 + 32;
302         d->cwd = (u_char *)malloc(d->maxlen);
303         if (d->cwd == NULL)
304             fatal("malloc in dos_setcwd for %c:%s: %s", drntol(drive),
305                   new_path, strerror(errno));
306     }
307     ustrncpy(d->cwd, new_path + 2, d->maxlen - d->len);
308     return (0);
309 }
310
311 /*
312  * Given a DOS path dos_path and a drive, convert it to a BSD pathname
313  * and store the result in real_path.
314  * Return DOS errno on failure.
315  */
316 int
317 dos_to_real_path(u_char *dos_path, u_char *real_path, int *drivep)
318 {
319     Path_t *d;
320     u_char new_path[1024];
321     u_char *rp;
322     u_char **dirs;
323     u_char *dir;
324     int drive;
325
326     debug(D_REDIR, "dos_to_real_path(%s)\n", dos_path);
327
328     if (dos_path[0] != '\0' && dos_path[1] == ':') {
329         drive = drlton(*dos_path);
330         dos_path++;
331         dos_path++;
332     } else {
333         drive = diskdrive;
334     }
335
336     d = &paths[drive];
337     if (d->cwd == NULL)
338         return (DISK_DRIVE_INVALID);
339
340     ustrcpy(real_path, d->path);
341
342     rp = real_path;
343     while (*rp)
344         ++rp;
345
346     ustrncpy(new_path, dos_path, 1024 - ustrlen(new_path));
347
348     dirs = get_entries(new_path);
349     if (dirs == NULL)
350         return (PATH_NOT_FOUND);
351
352     /*
353      * Skip the leading /
354      * There are no . or .. entries to worry about either
355      */
356
357     while ((dir = *++dirs) != 0) {
358         *rp++ = '/';
359         dos_to_real(dir, rp);
360         while (*rp)
361             ++rp;
362     }
363
364     *drivep = drive;
365     return (0);
366 }
367
368 /*
369  * Provide a few istype() style functions.
370  * isvalid:     True if the character is a valid DOS filename character
371  * isdot:       True if '.'
372  * isslash:     True if '/' or '\'
373  *
374  * 0 - invalid
375  * 1 - okay
376  * 2 - *
377  * 3 - dot
378  * 4 - slash
379  * 5 - colon
380  * 6 - ?
381  * 7 - lowercase
382  */
383 u_char cattr[256] = {
384     0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0,    /* 0x00 */
385     0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0,    /* 0x10 */
386     0, 1, 0, 1, 1, 1, 1, 1,  1, 1, 2, 0, 0, 1, 3, 4,    /* 0x20 */
387     1, 1, 1, 1, 1, 1, 1, 1,  1, 1, 5, 0, 0, 0, 0, 6,    /* 0x30 */
388     1, 1, 1, 1, 1, 1, 1, 1,  1, 1, 1, 1, 1, 1, 1, 1,    /* 0x40 */
389     1, 1, 1, 1, 1, 1, 1, 1,  1, 1, 1, 0, 4, 0, 1, 1,    /* 0x50 */
390     1, 7, 7, 7, 7, 7, 7, 7,  7, 7, 7, 7, 7, 7, 7, 7,    /* 0x60 */
391     7, 7, 7, 7, 7, 7, 7, 7,  7, 7, 7, 1, 0, 1, 1, 0,    /* 0x70 */
392     1, 1, 1, 1, 1, 1, 1, 1,  1, 1, 1, 1, 1, 1, 1, 1,    /* 0x80 */
393     1, 1, 1, 1, 1, 1, 1, 1,  1, 1, 1, 1, 1, 1, 1, 1,
394     1, 1, 1, 1, 1, 1, 1, 1,  1, 1, 1, 1, 1, 1, 1, 1,
395     1, 1, 1, 1, 1, 1, 1, 1,  1, 1, 1, 1, 1, 1, 1, 1,
396     1, 1, 1, 1, 1, 1, 1, 1,  1, 1, 1, 1, 1, 1, 1, 1,
397     1, 1, 1, 1, 1, 1, 1, 1,  1, 1, 1, 1, 1, 1, 1, 1,
398     1, 1, 1, 1, 1, 1, 1, 1,  1, 1, 1, 1, 1, 1, 1, 1,
399     1, 1, 1, 1, 1, 1, 1, 1,  1, 1, 1, 1, 1, 1, 1, 1,
400 };
401
402 inline int
403 isvalid(unsigned c)
404 {
405     return (cattr[c & 0xff] == 1);
406 }
407
408 inline int
409 isdot(unsigned c)
410 {
411     return (cattr[c & 0xff] == 3);
412 }
413
414 inline int
415 isslash(unsigned c)
416 {
417     return (cattr[c & 0xff] == 4);
418 }
419
420 /*
421  * Given a real component, compute the DOS component.
422  */
423 void
424 real_to_dos(u_char *real, u_char *dos)
425 {
426     Name_t *n;
427     Name_t *nn;
428     u_char *p;
429     u_char nm[9], ex[4];
430     int ncnt, ecnt;
431     int echar = '0';
432     int nchar = '0';
433
434     if (real[0] == '.' && (real[1] == '\0'
435                            || (real[1] == '.' && real[2] == '\0'))) {
436         sprintf((char *)dos, "%.8s", real);
437         return;
438     }
439
440     n = names;
441     while (n) {
442         if (ustrcmp(real, n->real) == 0) {
443             if (n->ext[0])
444                 sprintf((char *)dos, "%.8s.%.3s", n->name, n->ext);
445             else
446                 sprintf((char *)dos, "%.8s", n->name);
447             return;
448         }
449         n = n->next;
450     }
451
452     p = real;
453     ncnt = ecnt = 0;
454     while (isvalid(*p) && ncnt < 8) {
455         nm[ncnt] = *p;
456         ++ncnt;
457         ++p;
458     }
459     if (isdot(*p)) {
460         ++p;
461         while (isvalid(*p) && ecnt < 3) {
462             ex[ecnt] = *p;
463             ++ecnt;
464             ++p;
465         }
466     }
467     nm[ncnt] = '\0';
468     ex[ecnt] = '\0';
469
470     if (!*p && ncnt <= 8 && ecnt <= 3) {
471         n = names;
472         while (n) {
473             if (ustrncmp(n->name, nm, 8) == 0 && ustrncmp(n->ext, ex, 3) == 0) {
474                 break;
475             }
476             n = n->next;
477         }
478         if (n == 0) {
479             ustrcpy(dos, real);
480             return;
481         }
482     }
483
484     n = (Name_t *)malloc(sizeof(Name_t));
485
486     if (!n)
487         fatal("malloc in real_to_dos: %s\n", strerror(errno));
488
489     n->real = ustrdup(real);
490
491     if (!n->real)
492         fatal("strdup in real_to_dos: %s\n", strerror(errno));
493
494     p = real;
495     ncnt = ecnt = 0;
496     while (*p && ncnt < 8) {
497         if (isvalid(*p))
498             n->name[ncnt] = *p;
499         else if (islower(*p))
500             n->name[ncnt] = toupper(*p);
501         else if (isdot(*p))
502             break;
503         else
504             n->name[ncnt] = (*p |= 0x80);
505         ++ncnt;
506         ++p;
507     }
508     if (isdot(*p)) {
509         ++p;
510         while (*p && ecnt < 3) { 
511             if (isvalid(*p))
512                 n->ext[ecnt] = *p;
513             else if (islower(*p))
514                 n->ext[ecnt] = toupper(*p);
515 #if 0
516             else if (isdot(*p))
517                 ERROR
518 #endif
519             else
520                 n->ext[ecnt] = (*p |= 0x80);
521             ++ecnt;
522             ++p;
523         }
524     }
525     n->name[ncnt] = '\0';
526     n->ext[ecnt] = '\0';
527
528     for (;;) {
529         nn = names;
530         while (nn) {
531             if (ustrncmp(n->name, nn->name, 8) == 0 &&
532                 ustrncmp(n->ext, nn->ext, 3) == 0) {
533                     break;
534             }
535             nn = nn->next;
536         }
537         if (!nn)
538             break;
539         /*      
540          * Dang, this name was already in the cache.
541          * Let's munge it a little and try again.
542          */
543         if (ecnt < 3) {
544             n->ext[ecnt] = echar;
545             if (echar == '9') {
546                 echar = 'A';
547             } else if (echar == 'Z') {
548                 ++ecnt;
549                 echar = '0';
550             } else {
551                 ++echar;
552             }
553         } else if (ncnt < 8) {
554             n->name[ncnt] = nchar;
555             if (nchar == '9') {
556                 nchar = 'A';
557             } else if (nchar == 'Z') {
558                 ++ncnt;
559                 nchar = '0';
560             } else {
561                 ++nchar;
562             }
563         } else if (n->ext[2] < 'Z')
564             n->ext[2]++;
565         else if (n->ext[1] < 'Z')
566             n->ext[1]++;
567         else if (n->ext[0] < 'Z')
568             n->ext[0]++;
569         else if (n->name[7] < 'Z')
570             n->name[7]++;
571         else if (n->name[6] < 'Z')
572             n->name[6]++;
573         else if (n->name[5] < 'Z')
574             n->name[5]++;
575         else if (n->name[4] < 'Z')
576             n->name[4]++;
577         else if (n->name[3] < 'Z')
578             n->name[3]++;
579         else if (n->name[2] < 'Z')
580             n->name[2]++;
581         else if (n->name[1] < 'Z')
582             n->name[1]++;
583         else if (n->name[0] < 'Z')
584             n->name[0]++;
585         else
586             break;
587     }
588
589     if (n->ext[0])
590         sprintf((char *)dos, "%.8s.%.3s", n->name, n->ext);
591     else
592         sprintf((char *)dos, "%.8s", n->name);
593     n->next = names;
594     names = n;
595 }
596
597
598 /*
599  * Given a DOS component, compute the REAL component.
600  */
601 void
602 dos_to_real(u_char *dos, u_char *real)
603 {
604     int ncnt = 0;
605     int ecnt = 0;
606     u_char name[8];
607     u_char ext[3];
608     Name_t *n = names;
609
610     while (ncnt < 8 && (isvalid(*dos) || islower(*dos))) {
611         name[ncnt++] = islower(*dos) ? toupper(*dos) : *dos;
612         ++dos;
613     }
614     if (ncnt < 8)
615         name[ncnt] = 0;
616
617     if (isdot(*dos)) {
618         while (ecnt < 3 && (isvalid(*++dos) || islower(*dos))) {
619             ext[ecnt++] = islower(*dos) ? toupper(*dos) : *dos;
620         }
621     }
622     if (ecnt < 3)
623         ext[ecnt] = 0;
624
625     while (n) {
626         if (!ustrncmp(name, n->name, 8) && !ustrncmp(ext, n->ext, 3)) {
627             ustrcpy(real, n->real);
628             return;
629         }
630         n = n->next;
631     }
632
633     if (ext[0])
634         sprintf((char *)real, "%-.8s.%-.3s", name, ext);
635     else
636         sprintf((char *)real, "%-.8s", name);
637
638     while (*real) {
639         if (isupper(*real))
640             *real = tolower(*real);
641         ++real;
642     }
643 }
644
645 /*
646  * convert a path into an argv[] like vector of components.
647  * If the path starts with a '/' or '\' then the first entry
648  * will be "/" or "\".  This is the only case in which a "/"
649  * or "\" may appear in an entry.
650  * Also convert all lowercase to uppercase.
651  * The data returned is in a static area, so a second call will
652  * erase the data of the first.
653  */
654 u_char **
655 get_entries(u_char *path)
656 {
657     static u_char *entries[128];        /* Maximum depth... */
658     static u_char mypath[1024];
659     u_char **e = entries;
660     u_char *p = mypath;
661
662     ustrncpy(mypath+1, path, 1022);
663     p = mypath+1;
664     mypath[1023] = 0;
665     if (path[0] == '/' || path[0] == '\\') {
666         mypath[0] = path[0];
667         *e++ = mypath;
668         *p++ = 0;
669     }
670     while (*p && e < entries + 127) {
671         while (*p && (*p == '/' || *p == '\\')) {
672             ++p;
673         }
674
675         if (!*p)
676             break;
677         *e++ = p;
678         while (*p && (*p != '/' && *p != '\\')) {
679             if (islower(*p))
680                 *p = tolower(*p);
681             ++p;
682         }
683         /*
684          * skip over the '/' or '\'
685          */
686         if (*p)
687             *p++ = 0;
688     }
689     *e = 0;
690     return (entries);
691 }
692
693 /*
694  * Return file system statistics for drive.
695  * Return the DOS errno on failure.
696  */
697 int
698 get_space(int drive, fsstat_t *fs)
699 {
700     Path_t *d;
701     struct statfs *buf;
702     int nfs;
703     int i;
704     struct statfs *me = 0;
705
706     if (drive < 0 || drive >= MAX_DRIVE)
707         return (DISK_DRIVE_INVALID);
708
709     d = &paths[drive];
710
711     if (!d->path)
712         return (DISK_DRIVE_INVALID);
713
714     nfs = getfsstat(0, 0, MNT_WAIT);
715
716     buf = (struct statfs *)malloc(sizeof(struct statfs) * nfs);
717     if (buf == NULL) {
718         perror("get_space");
719         return (DISK_DRIVE_INVALID);
720     }
721     nfs = getfsstat(buf, sizeof(struct statfs) * nfs, MNT_WAIT);
722
723     for (i = 0; i < nfs; ++i) {
724         if (strncmp(buf[i].f_mntonname, (char *)d->path, strlen(buf[i].f_mntonname)))
725             continue;
726         if (me && strlen(me->f_mntonname) > strlen(buf[i].f_mntonname))
727             continue;
728         me = buf + i;
729     }
730     if (!me) {
731         free(buf);
732         return (3);
733     }
734     fs->bytes_sector = 512;
735     fs->sectors_cluster = me->f_bsize / fs->bytes_sector;
736     fs->total_clusters = me->f_blocks / fs->sectors_cluster;
737     while (fs->total_clusters > 0xFFFF) {
738         fs->sectors_cluster *= 2;
739         fs->total_clusters = me->f_blocks / fs->sectors_cluster;
740     }
741     fs->avail_clusters = me->f_bavail / fs->sectors_cluster;
742     free(buf);
743     return (0);
744 }
745
746 #if 0
747 DIR *dp = 0;
748 u_char searchdir[1024];
749 u_char *searchend;
750 #endif
751
752 /*
753  * Convert a dos filename into normal form (8.3 format, space padded)
754  */
755 void
756 to_dos_fcb(u_char *p, u_char *expr)
757 {
758     int i;
759
760     if (expr[0] == '.') {
761         p[0] = '.';
762         if (expr[1] == '\0') {
763             for (i = 1; i < 11; i++)
764                 p[i] = ' ';
765             return;
766         }
767         if (expr[1] == '.') {
768             p[1] = '.';
769             if (expr[2] == '\0') {
770                 for (i = 2; i < 11; i++)
771                     p[i] = ' ';
772                 return;
773             }
774         }
775     }
776
777     for (i = 8; i > 0; i--) {
778         switch (*expr) {
779         case '\0':
780         case '.':
781                 for (; i > 0; i--)
782                         *p++ = ' ';
783                 break;
784         case '*':
785                 for (; i > 0; i--)
786                         *p++ = '?';
787                 break;
788         default:
789                 if (islower(*expr)) {
790                         *p++ = toupper(*expr++);
791                         break;
792                 }
793         case '?':
794                 *p++ = *expr++;
795                 break;
796         }
797     }
798
799     while (*expr != '\0' && *expr != '.')
800         ++expr;
801     if (*expr)
802         ++expr;
803
804     for (i = 3; i > 0; i--) {
805         switch (*expr) {
806         case '\0':
807         case '.':
808                 for (; i > 0; i--)
809                         *p++ = ' ';
810                 break;
811         case '*':
812                 for (; i > 0; i--)
813                         *p++ = '?';
814                 break;
815         default:
816                 if (islower(*expr)) {
817                         *p++ = toupper(*expr++);
818                         break;
819                 }
820         case '?':
821                 *p++ = *expr++;
822                 break;
823         }
824     }
825 }
826
827 /*
828 ** DOS can't handle multiple concurrent searches, and if we leave the
829 ** search instance in the DTA we get screwed as soon as someone starts lots
830 ** of searches without finishing them properly.
831 ** We allocate a single search structure, and recycle it if find_first()
832 ** is called before a search ends.
833 */
834 static search_t dir_search = {dp : NULL};
835
836 /*
837  * Find the first file on drive which matches the path with the given
838  * attributes attr.
839  * If found, the result is placed in dir (32 bytes).
840  * The DTA is populated as required by DOS, but the state area is ignored.
841  * Returns DOS errno on failure.
842  */
843 int
844 find_first(u_char *path, int attr, dosdir_t *dir, find_block_t *dta)
845 {
846     u_char new_path[1024], real_path[1024];
847     u_char *expr, *slash;
848     int drive;
849     int error;
850     search_t *search = &dir_search;
851
852     debug(D_REDIR, "find_first(%s, %x, %x)\n", path, attr, dta);
853
854     error = dos_makepath(path, new_path);
855     if (error)
856         return (error);
857
858     expr = new_path;
859     slash = 0;
860     while (*expr != '\0') {
861         if (*expr == '\\' || *expr == '/')
862             slash = expr;
863         expr++;
864     }
865     *slash++ = '\0';
866
867     error = dos_to_real_path(new_path, real_path, &drive);
868     if (error)
869         return (error);
870
871     if (attr == VOLUME_LABEL)   /* never find a volume label */
872         return (NO_MORE_FILES);
873
874     if (search->dp)             /* stale search? */
875         closedir(search->dp);
876
877     search->dp = opendir(real_path);
878     if (search->dp == NULL)
879         return (PATH_NOT_FOUND);
880
881     ustrncpy(search->searchdir, real_path, 1024 - ustrlen(real_path));
882     search->searchend = search->searchdir;
883     while (*search->searchend)
884         ++search->searchend;
885     *search->searchend++ = '/';
886
887     search->dp->dd_fd = squirrel_fd(search->dp->dd_fd);
888
889     dta->drive = drive | 0x80;
890     to_dos_fcb(dta->pattern, slash);
891     dta->flag = attr;
892
893     return (find_next(dir, dta));
894 }
895
896 /*
897  * Continue on where find_first left off.
898  * The results will be placed in dir.
899  * DTA state area is ignored.
900  */
901 int
902 find_next(dosdir_t *dir, find_block_t *dta)
903 {
904     search_t *search = &dir_search;
905     struct dirent *d;
906     struct stat sb;
907     u_char name[16];
908
909     if (!search->dp)
910         return (NO_MORE_FILES);
911
912 #if 0
913     debug(D_REDIR, "find_next()\n");
914 #endif
915
916     while ((d = readdir(search->dp)) != 0) {
917         real_to_dos((u_char *)d->d_name, name);
918         to_dos_fcb(dir->name, name);
919 #if 0
920 printf("find_next: |%-11.11s| |%-11.11s| |%s| |%s|\n", dta->pattern, dir->name, d->d_name, name);
921 #endif
922         if (dos_match(dta->pattern, dir->name) == 0)
923             continue;
924
925         ustrcpy(search->searchend, (u_char *)d->d_name);
926         if (ustat(search->searchdir, &sb) < 0)
927             continue;
928 #if 0
929 printf("find_next: %x\n", sb.st_mode);
930 #endif
931         if (S_ISDIR(sb.st_mode)) {
932             if (!(dta->flag & DIRECTORY)) {
933                 continue;
934             }
935         }
936         dir->attr = (S_ISDIR(sb.st_mode) ? DIRECTORY : 0) |
937                     (uaccess(search->searchdir, W_OK) < 0 ? READ_ONLY_FILE : 0);
938         encode_dos_file_time(sb.st_mtime, &dir->date, &dir->time);
939         dir->start = 1;
940         dir->size = sb.st_size;
941 #if 0
942 printf("find_next: found %s\n",name);
943 #endif
944         return (0);
945     }
946     closedir(search->dp);
947     search->dp = NULL;
948     return (NO_MORE_FILES);
949 }
950
951 /*
952  * perfrom hokey DOS pattern matching.  pattern may contain the wild cards
953  * '*' and '?' only.  Follow the DOS convention that '?*', '*?' and '**' all
954  * are the same as '*'.  Also, allow '?' to match the blank padding in a
955  * name (hence, ???? matchs all of "a", "ab", "abc" and "abcd" but not "abcde")
956  * Return 1 if a match is found, 0 if not.
957  * 
958  * XXX This appears to be severely busted! (no * handling - normal?)
959  */
960 int
961 dos_match(u_char *pattern, u_char *string)
962 {
963     int i;
964
965     /*
966      * Check the base part first
967      */
968     for (i = 11; i > 0; i--) {
969         if (*pattern != '?' && *string != *pattern)
970             return (0);
971         pattern++, string++;
972     }
973     return (1);
974 }