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(¤tDir); 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