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
36static int recursive;
37static int wide;
38static int all;
39static int concise;
40static int fast=0;
41#if 0
42static int testmode = 0;
43#endif
44static const char *dirPath;
45static char *dynDirPath;
46static char currentDrive;
47static Stream_t *currentDir;
48
49static int filesInDir; /* files in current dir */
50static int filesOnDrive; /* files on drive */
51
52static int dirsOnDrive; /* number of listed directories on this drive */
53
54static int debug = 0; /* debug mode */
55
56static mt_size_t bytesInDir;
57static mt_size_t bytesOnDrive;
58static Stream_t *RootDir;
59
60
61static char global_shortname[13];
62static char global_longname[VBUFSIZE];
63
64
65/*
66 * Print an MSDOS directory date stamp.
67 */
68static __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 */
104static __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 */
124static 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
188static __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
225static 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
243static void leaveDirectory(int haveError);
244
245static 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
281static 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
301static const char *emptyString="<out-of-memory>";
302
303static 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
320static 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
357static 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		/* is a subdirectory */
420		if(mtools_dotted_dir)
421			printf("%s", global_shortname);
422		else
423			printf("%s %s ", tmpBasename, tmpExt);
424		if(IS_DIR(entry))
425			printf("<DIR>    ");
426		else
427			printf(" %8ld", (long) size);
428		printf(" ");
429		print_date(&entry->dir);
430		printf("  ");
431		print_time(&entry->dir);
432
433		if(debug)
434			printf(" %s %d ", tmpBasename, START(&entry->dir));
435
436		if(*global_longname)
437			printf(" %s", global_longname);
438		printf("\n");
439	} else {
440		char tmp[4*MAX_VNAMELEN+1];
441		wchar_to_native(entry->name,tmp,MAX_VNAMELEN);
442
443		printf("%s/%s", dirPath, tmp);
444		if(IS_DIR(entry))
445			putchar('/');
446		putchar('\n');
447	}
448
449	filesOnDrive++;
450	filesInDir++;
451
452	bytesOnDrive += (mt_size_t) size;
453	bytesInDir += (mt_size_t) size;
454	return GOT_ONE;
455}
456
457static int list_non_recurs_directory(direntry_t *entry, MainParam_t *mp)
458{
459	int r;
460	/* list top-level directory
461	 *   If this was matched by wildcard in the basename, list it as
462	 *   file, otherwise, list it as directory */
463	if (mp->basenameHasWildcard) {
464		/* wildcard, list it as file */
465		return list_file(entry, mp);
466	} else {
467		/* no wildcard, list it as directory */
468		MainParam_t subMp;
469
470		r=enterDirectory(mp->File);
471		if(r)
472			return ERROR_ONE;
473
474		subMp = *mp;
475		subMp.dirCallback = subMp.callback;
476		return mp->loop(mp->File, &subMp, "*") | GOT_ONE;
477	}
478}
479
480
481static int list_recurs_directory(direntry_t *entry, MainParam_t *mp)
482{
483	MainParam_t subMp;
484	int ret;
485
486	/* first list the files */
487	subMp = *mp;
488	subMp.lookupflags = ACCEPT_DIR | ACCEPT_PLAIN;
489	subMp.dirCallback = list_file;
490	subMp.callback = list_file;
491
492	ret = mp->loop(mp->File, &subMp, "*");
493
494	/* then list subdirectories */
495	subMp = *mp;
496	subMp.lookupflags = ACCEPT_DIR | NO_DOTS | NO_MSG | DO_OPEN;
497	return ret | mp->loop(mp->File, &subMp, "*");
498}
499
500#if 0
501static int test_directory(direntry_t *entry, MainParam_t *mp)
502{
503	Stream_t *File=mp->File;
504	Stream_t *Target;
505	char errmsg[80];
506
507	if ((Target = SimpleFileOpen(0, 0, "-",
508				     O_WRONLY,
509				     errmsg, 0, 0, 0))) {
510		copyfile(File, Target);
511		FREE(&Target);
512	}
513	return GOT_ONE;
514}
515#endif
516
517static void usage(int ret) NORETURN;
518static void usage(int ret)
519{
520		fprintf(stderr, "Mtools version %s, dated %s\n",
521			mversion, mdate);
522		fprintf(stderr, "Usage: %s: [-V] [-w] [-a] [-b] [-s] [-f] msdosdirectory\n",
523			progname);
524		fprintf(stderr,
525			"       %s: [-V] [-w] [-a] [-b] [-s] [-f] msdosfile [msdosfiles...]\n",
526			progname);
527		exit(ret);
528}
529
530
531void mdir(int argc, char **argv, int type)
532{
533	int ret;
534	MainParam_t mp;
535	int faked;
536	int c;
537	const char *fakedArgv[] = { "." };
538
539	concise = 0;
540	recursive = 0;
541	wide = all = 0;
542					/* first argument */
543	if(helpFlag(argc, argv))
544		usage(0);
545	while ((c = getopt(argc, argv, "i:waXbfds/h")) != EOF) {
546		switch(c) {
547			case 'i':
548				set_cmd_line_image(optarg, 0);
549				break;
550			case 'w':
551				wide = 1;
552				break;
553			case 'a':
554				all = 1;
555				break;
556			case 'b':
557			case 'X':
558				concise = 1;
559				/*recursive = 1;*/
560				break;
561			case 's':
562			case '/':
563				recursive = 1;
564				break;
565			case 'f':
566				fast = 1;
567				break;
568			case 'd':
569				debug = 1;
570				break;
571#if 0
572			case 't': /* test mode */
573				testmode = 1;
574				break;
575#endif
576			case 'h':
577				usage(0);
578			default:
579				usage(1);
580		}
581	}
582
583	/* fake an argument */
584	faked = 0;
585	if (optind == argc) {
586		argv = (char **)fakedArgv;
587		argc = 1;
588		optind = 0;
589	}
590
591	init_mp(&mp);
592	currentDrive = '\0';
593	currentDir = 0;
594	RootDir = 0;
595	dirPath = 0;
596#if 0
597	if (testmode) {
598		mp.lookupflags = ACCEPT_DIR | NO_DOTS;
599		mp.dirCallback = test_directory;
600	} else
601#endif
602		if(recursive) {
603		mp.lookupflags = ACCEPT_DIR | DO_OPEN_DIRS | NO_DOTS;
604		mp.dirCallback = list_recurs_directory;
605	} else {
606		mp.lookupflags = ACCEPT_DIR | ACCEPT_PLAIN | DO_OPEN_DIRS;
607		mp.dirCallback = list_non_recurs_directory;
608		mp.callback = list_file;
609	}
610	mp.longname = global_longname;
611	mp.shortname = global_shortname;
612	ret=main_loop(&mp, argv + optind, argc - optind);
613	leaveDirectory(ret);
614	leaveDrive(ret);
615	exit(ret);
616}
617