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