Initial import package mtools: Programs for accessing MS-DOS disks without mounting...
[pkgs/m/mtools.git] / mdir.c
1 /*  Copyright 1986-1992 Emmet P. Gray.
2  *  Copyright 1996-2002,2004,2007-2009 Alain Knaff.
3  *  This file is part of mtools.
4  *
5  *  Mtools is free software: you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation, either version 3 of the License, or
8  *  (at your option) any later version.
9  *
10  *  Mtools is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with Mtools.  If not, see <http://www.gnu.org/licenses/>.
17  *
18  * mdir.c:
19  * Display an MSDOS directory
20  */
21
22 #include "sysincludes.h"
23 #include "msdos.h"
24 #include "vfat.h"
25 #include "mtools.h"
26 #include "file.h"
27 #include "mainloop.h"
28 #include "fs.h"
29 #include "codepage.h"
30 #include "file_name.h"
31
32 #ifdef TEST_SIZE
33 #include "fsP.h"
34 #endif
35
36 static int recursive;
37 static int wide;
38 static int all;
39 static int concise;
40 static int fast=0;
41 #if 0
42 static int testmode = 0;
43 #endif
44 static const char *dirPath;
45 static char *dynDirPath;
46 static char currentDrive;
47 static Stream_t *currentDir;
48
49 static int filesInDir; /* files in current dir */
50 static int filesOnDrive; /* files on drive */
51         
52 static int dirsOnDrive; /* number of listed directories on this drive */
53
54 static int debug = 0; /* debug mode */
55
56 static mt_size_t bytesInDir;
57 static mt_size_t bytesOnDrive;
58 static Stream_t *RootDir;       
59
60
61 static char global_shortname[13];
62 static char global_longname[VBUFSIZE];
63
64
65 /*
66  * Print an MSDOS directory date stamp.
67  */
68 static __inline__ void print_date(struct directory *dir)
69 {
70         char year[5];
71         char day[3];
72         char month[3];
73         const char *p;
74
75         sprintf(year, "%04d", DOS_YEAR(dir));
76         sprintf(day, "%02d", DOS_DAY(dir));
77         sprintf(month, "%02d", DOS_MONTH(dir));
78
79         for(p=mtools_date_string; *p; p++) {
80                 if(!strncasecmp(p, "yyyy", 4)) {
81                         printf("%04d", DOS_YEAR(dir));
82                         p+= 3;
83                         continue;
84                 } else if(!strncasecmp(p, "yy", 2)) {
85                         printf("%02d", DOS_YEAR(dir) % 100);
86                         p++;
87                         continue;
88                 } else if(!strncasecmp(p, "dd", 2)) {
89                         printf("%02d", DOS_DAY(dir));
90                         p++;
91                         continue;
92                 } else if(!strncasecmp(p, "mm", 2)) {
93                         printf("%02d", DOS_MONTH(dir));
94                         p++;
95                         continue;
96                 }
97                 putchar(*p);
98         }
99 }
100
101 /*
102  * Print an MSDOS directory time stamp.
103  */
104 static __inline__ void print_time(struct directory *dir)
105 {
106         char am_pm;
107         int hour = DOS_HOUR(dir);
108        
109         if(!mtools_twenty_four_hour_clock) {
110                 am_pm = (hour >= 12) ? 'p' : 'a';
111                 if (hour > 12)
112                         hour = hour - 12;
113                 if (hour == 0)
114                         hour = 12;
115         } else
116                 am_pm = ' ';
117
118         printf("%2d:%02d%c", hour, DOS_MINUTE(dir), am_pm);
119 }
120
121 /*
122  * Return a number in dotted notation
123  */
124 static const char *dotted_num(mt_size_t num, int width, char **buf)
125 {
126         int      len;
127         register char *srcp, *dstp;
128         int size;
129
130         unsigned long numlo;
131         unsigned long numhi;
132
133         if (num < 0) {
134             /* warn about negative numbers here.  They should not occur */
135             fprintf(stderr, "Invalid negative number\n");
136         }
137
138         size = width + width;
139         *buf = malloc(size+1);
140
141         if (*buf == NULL)
142                 return "";
143         
144         /* Create the number in maximum width; make sure that the string
145          * length is not exceeded (in %6ld, the result can be longer than 6!)
146          */
147
148         numlo = num % 1000000000;
149         numhi = num / 1000000000;
150
151         if(numhi && size > 9) {
152                 sprintf(*buf, "%.*lu%09lu", size-9, numhi, numlo);
153         } else {
154                 sprintf(*buf, "%.*lu", size, numlo);
155         }
156
157         for (srcp=*buf; srcp[1] != '\0'; ++srcp)
158                 if (srcp[0] == '0')
159                         srcp[0] = ' ';
160                 else
161                         break;
162         
163         len = strlen(*buf);
164         srcp = (*buf)+len;
165         dstp = (*buf)+len+1;
166
167         for ( ; dstp >= (*buf)+4 && isdigit (srcp[-1]); ) {
168                 srcp -= 3;  /* from here we copy three digits */
169                 dstp -= 4;  /* that's where we put these 3 digits */
170         }
171
172         /* now finally copy the 3-byte blocks to their new place */
173         while (dstp < (*buf) + len) {
174                 dstp[0] = srcp[0];
175                 dstp[1] = srcp[1];
176                 dstp[2] = srcp[2];
177                 if (dstp + 3 < (*buf) + len)
178                         /* use spaces instead of dots: they please both
179                          * Americans and Europeans */
180                         dstp[3] = ' ';          
181                 srcp += 3;
182                 dstp += 4;
183         }
184
185         return (*buf) + len-width;
186 }
187
188 static __inline__ int print_volume_label(Stream_t *Dir, char drive)
189 {
190         Stream_t *Stream = GetFs(Dir);
191         direntry_t entry;
192         DeclareThis(FsPublic_t);
193         char shortname[13];
194         char longname[VBUFSIZE];
195         int r;
196
197         RootDir = OpenRoot(Stream);
198         if(concise)
199                 return 0;
200         
201         /* find the volume label */
202
203         initializeDirentry(&entry, RootDir);
204         if((r=vfat_lookup(&entry, 0, 0, ACCEPT_LABEL | MATCH_ANY,
205                           shortname, longname)) ) {
206                 if (r == -2) {
207                         /* I/O Error */
208                         return -1;
209                 }
210                 printf(" Volume in drive %c has no label", drive);
211         } else if (*longname)
212                 printf(" Volume in drive %c is %s (abbr=%s)",
213                        drive, longname, shortname);
214         else
215                 printf(" Volume in drive %c is %s",
216                        drive, shortname);
217         if(This->serialized)
218                 printf("\n Volume Serial Number is %04lX-%04lX",
219                        (This->serial_number >> 16) & 0xffff, 
220                        This->serial_number & 0xffff);
221         return 0;
222 }
223
224
225 static void printSummary(int files, mt_size_t bytes)
226 {
227         if(!filesInDir)
228                 printf("No files\n");
229         else {          
230                 char *s1 = NULL;
231                 printf("      %3d file", files);
232                 if(files == 1)
233                         putchar(' ');
234                 else
235                         putchar('s');
236                 printf("       %s bytes\n",
237                        dotted_num(bytes, 13, &s1));
238                 if(s1)
239                         free(s1);
240         }
241 }
242
243 static void leaveDirectory(int haveError);
244
245 static void leaveDrive(int haveError)
246 {
247         if(!currentDrive)
248                 return;
249         leaveDirectory(haveError);
250         if(!concise && !haveError) {
251
252                 if(dirsOnDrive > 1) {
253                         printf("\nTotal files listed:\n");
254                         printSummary(filesOnDrive, bytesOnDrive);
255                 }
256                 if(RootDir && !fast) {
257                         char *s1 = NULL;
258                         mt_off_t bytes = getfree(RootDir);
259                         if(bytes == -1) {
260                                 fprintf(stderr, "Fat error\n");
261                                 goto exit_1;
262                         }
263                         printf("                  %s bytes free\n\n",
264                                dotted_num(bytes,17, &s1));
265 #ifdef TEST_SIZE
266                         ((Fs_t*)GetFs(RootDir))->freeSpace = 0;
267                         bytes = getfree(RootDir);
268                         printf("                  %s bytes free\n\n",
269                                dotted_num(bytes,17, &s1));
270 #endif
271                         if(s1)
272                                 free(s1);
273                 }
274         }
275  exit_1:
276         FREE(&RootDir);
277         currentDrive = '\0';
278 }
279
280
281 static int enterDrive(Stream_t *Dir, char drive)
282 {
283         int r;
284         if(currentDrive == drive)
285                 return 0; /* still the same */
286         
287         leaveDrive(0);
288         currentDrive = drive;
289         
290         r = print_volume_label(Dir, drive);
291         if (r)
292                 return r;
293
294
295         bytesOnDrive = 0;
296         filesOnDrive = 0;
297         dirsOnDrive = 0;
298         return 0;
299 }
300
301 static const char *emptyString="<out-of-memory>";
302
303 static void leaveDirectory(int haveError)
304 {
305         if(!currentDir)
306                 return;
307
308         if (!haveError) {
309                 if(dirPath && dirPath != emptyString)
310                         free(dynDirPath);
311                 if(wide)
312                         putchar('\n');
313                 
314                 if(!concise)
315                         printSummary(filesInDir, bytesInDir);
316         }
317         FREE(&currentDir);
318 }
319
320 static int enterDirectory(Stream_t *Dir)
321 {
322         int r;
323         char drive;
324         if(currentDir == Dir)
325                 return 0; /* still the same directory */
326
327         leaveDirectory(0);
328
329         drive = getDrive(Dir);
330         r=enterDrive(Dir, drive);
331         if(r)
332                 return r;
333         currentDir = COPY(Dir);
334
335         dynDirPath = getPwd(getDirentry(Dir));
336         if(!dynDirPath)
337                 dirPath=emptyString;
338         else {
339                 if(!dynDirPath[3] && concise)
340                         dynDirPath[2]='\0';
341                 dirPath=dynDirPath;
342         }
343
344         /* print directory title */
345         if(!concise)
346                 printf("\nDirectory for %s\n", dirPath);
347
348         if(!wide && !concise)
349                 printf("\n");
350
351         dirsOnDrive++;
352         bytesInDir = 0;
353         filesInDir = 0;
354         return 0;
355 }
356
357 static int list_file(direntry_t *entry, MainParam_t *mp)
358 {
359         unsigned long size;
360         int i;
361         int Case;
362         int r;
363
364         wchar_t ext[4];
365         wchar_t name[9];
366         doscp_t *cp;
367
368         if(!all && (entry->dir.attr & 0x6))
369                 return 0;
370
371         if(concise && isSpecialW(entry->name))
372                 return 0;
373
374         r=enterDirectory(entry->Dir);
375         if (r)
376                 return ERROR_ONE;
377         if (wide) {
378                 if(filesInDir % 5)
379                         putchar(' ');                           
380                 else
381                         putchar('\n');
382         }
383         
384         if(IS_DIR(entry)){
385                 size = 0;
386         } else
387                 size = FILE_SIZE(&entry->dir);
388         
389         Case = entry->dir.Case;
390         if(!(Case & (BASECASE | EXTCASE)) && 
391            mtools_ignore_short_case)
392                 Case |= BASECASE | EXTCASE;
393         
394         cp = GET_DOSCONVERT(entry->Dir);
395         dos_to_wchar(cp, entry->dir.ext, ext, 3);
396         if(Case & EXTCASE){
397                 for(i=0; i<3;i++)
398                         ext[i] = towlower(ext[i]);
399         }
400         ext[3] = '\0';
401         dos_to_wchar(cp, entry->dir.name, name, 8);
402         if(Case & BASECASE){
403                 for(i=0; i<8;i++)
404                         name[i] = towlower(name[i]);
405         }
406         name[8]='\0';
407         if(wide){
408                 if(IS_DIR(entry))
409                         printf("[%s]%*s", global_shortname,
410                                (int) (15 - 2 - strlen(global_shortname)), "");
411                 else
412                         printf("%-15s", global_shortname);
413         } else if(!concise) {                           
414                 char tmpBasename[4*8+1];
415                 char tmpExt[4*8+1];
416                 wchar_to_native(name,tmpBasename,8);
417                 wchar_to_native(ext,tmpExt,3);
418
419                 if (name[0] == ' ') 
420                         printf("             ");
421                 else if(mtools_dotted_dir)
422                         printf("%s", global_shortname);
423                 else
424                         printf("%s %s ", tmpBasename, tmpExt);
425                 /* is a subdirectory */
426                 if(IS_DIR(entry))
427                         printf("<DIR>    ");
428                 else
429                         printf(" %8ld", (long) size);
430                 printf(" ");
431                 print_date(&entry->dir);
432                 printf("  ");
433                 print_time(&entry->dir);
434
435                 if(debug)
436                         printf(" %s %d ", tmpBasename, START(&entry->dir));
437                 
438                 if(*global_longname)
439                         printf(" %s", global_longname);
440                 printf("\n");
441         } else {
442                 char tmp[4*MAX_VNAMELEN+1];
443                 wchar_to_native(entry->name,tmp,MAX_VNAMELEN);
444
445                 printf("%s/%s", dirPath, tmp);
446                 if(IS_DIR(entry))
447                         putchar('/');
448                 putchar('\n');
449         }
450
451         filesOnDrive++;
452         filesInDir++;
453
454         bytesOnDrive += (mt_size_t) size;
455         bytesInDir += (mt_size_t) size;
456         return GOT_ONE;
457 }
458
459 static int list_non_recurs_directory(direntry_t *entry, MainParam_t *mp)
460 {
461         int r;
462         /* list top-level directory
463          *   If this was matched by wildcard in the basename, list it as
464          *   file, otherwise, list it as directory */
465         if (mp->basenameHasWildcard) {
466                 /* wildcard, list it as file */
467                 return list_file(entry, mp);
468         } else {
469                 /* no wildcard, list it as directory */
470                 MainParam_t subMp;
471
472                 r=enterDirectory(mp->File);
473                 if(r)
474                         return ERROR_ONE;
475
476                 subMp = *mp;
477                 subMp.dirCallback = subMp.callback;
478                 return mp->loop(mp->File, &subMp, "*") | GOT_ONE;
479         }
480 }
481
482
483 static int list_recurs_directory(direntry_t *entry, MainParam_t *mp)
484 {
485         MainParam_t subMp;
486         int ret;
487
488         /* first list the files */
489         subMp = *mp;
490         subMp.lookupflags = ACCEPT_DIR | ACCEPT_PLAIN;
491         subMp.dirCallback = list_file;
492         subMp.callback = list_file;
493
494         ret = mp->loop(mp->File, &subMp, "*");
495
496         /* then list subdirectories */
497         subMp = *mp;
498         subMp.lookupflags = ACCEPT_DIR | NO_DOTS | NO_MSG | DO_OPEN;
499         return ret | mp->loop(mp->File, &subMp, "*");
500 }
501
502 #if 0
503 static int test_directory(direntry_t *entry, MainParam_t *mp)
504 {
505         Stream_t *File=mp->File;
506         Stream_t *Target;
507         char errmsg[80];
508
509         if ((Target = SimpleFileOpen(0, 0, "-",
510                                      O_WRONLY,
511                                      errmsg, 0, 0, 0))) {
512                 copyfile(File, Target);
513                 FREE(&Target);
514         }
515         return GOT_ONE;
516 }
517 #endif
518
519 static void usage(int ret) NORETURN;
520 static void usage(int ret)
521 {
522                 fprintf(stderr, "Mtools version %s, dated %s\n",
523                         mversion, mdate);
524                 fprintf(stderr, "Usage: %s: [-V] [-w] [-a] [-b] [-s] [-f] msdosdirectory\n",
525                         progname);
526                 fprintf(stderr,
527                         "       %s: [-V] [-w] [-a] [-b] [-s] [-f] msdosfile [msdosfiles...]\n",
528                         progname);
529                 exit(ret);
530 }
531
532
533 void mdir(int argc, char **argv, int type)
534 {
535         int ret;
536         MainParam_t mp;
537         int faked;
538         int c;
539         const char *fakedArgv[] = { "." };
540
541         concise = 0;
542         recursive = 0;
543         wide = all = 0;
544                                         /* first argument */
545         if(helpFlag(argc, argv))
546                 usage(0);
547         while ((c = getopt(argc, argv, "i:waXbfds/h")) != EOF) {
548                 switch(c) {
549                         case 'i':
550                                 set_cmd_line_image(optarg, 0);
551                                 break;
552                         case 'w':
553                                 wide = 1;
554                                 break;
555                         case 'a':
556                                 all = 1;
557                                 break;
558                         case 'b':
559                         case 'X':
560                                 concise = 1;
561                                 /*recursive = 1;*/
562                                 break;
563                         case 's':
564                         case '/':
565                                 recursive = 1;
566                                 break;
567                         case 'f':
568                                 fast = 1;
569                                 break;
570                         case 'd':
571                                 debug = 1;
572                                 break;
573 #if 0
574                         case 't': /* test mode */
575                                 testmode = 1;
576                                 break;
577 #endif
578                         case 'h':
579                                 usage(0);
580                         default:
581                                 usage(1);
582                 }
583         }
584
585         /* fake an argument */
586         faked = 0;
587         if (optind == argc) {
588                 argv = (char **)fakedArgv;
589                 argc = 1;
590                 optind = 0;
591         }
592
593         init_mp(&mp);
594         currentDrive = '\0';
595         currentDir = 0;
596         RootDir = 0;
597         dirPath = 0;
598 #if 0
599         if (testmode) {
600                 mp.lookupflags = ACCEPT_DIR | NO_DOTS;
601                 mp.dirCallback = test_directory;
602         } else 
603 #endif
604                 if(recursive) {
605                 mp.lookupflags = ACCEPT_DIR | DO_OPEN_DIRS | NO_DOTS;
606                 mp.dirCallback = list_recurs_directory;
607         } else {
608                 mp.lookupflags = ACCEPT_DIR | ACCEPT_PLAIN | DO_OPEN_DIRS;
609                 mp.dirCallback = list_non_recurs_directory;
610                 mp.callback = list_file;
611         }
612         mp.longname = global_longname;
613         mp.shortname = global_shortname;
614         ret=main_loop(&mp, argv + optind, argc - optind);
615         leaveDirectory(ret);
616         leaveDrive(ret);
617         exit(ret);
618 }