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