1 /** 2 * ntfsls - Part of the Linux-NTFS project. 3 * 4 * Copyright (c) 2003 Lode Leroy 5 * Copyright (c) 2003-2005 Anton Altaparmakov 6 * Copyright (c) 2003 Richard Russon 7 * Copyright (c) 2004 Carmelo Kintana 8 * Copyright (c) 2004 Giang Nguyen 9 * 10 * This utility will list a directory's files. 11 * 12 * This program is free software; you can redistribute it and/or modify 13 * it under the terms of the GNU General Public License as published by 14 * the Free Software Foundation; either version 2 of the License, or 15 * (at your option) any later version. 16 * 17 * This program is distributed in the hope that it will be useful, 18 * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 * GNU General Public License for more details. 21 * 22 * You should have received a copy of the GNU General Public License 23 * along with this program (in the main directory of the Linux-NTFS 24 * distribution in the file COPYING); if not, write to the Free Software 25 * Foundation,Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 26 */ 27 #include "config.h" 28 29 #ifdef HAVE_STDIO_H 30 #include <stdio.h> 31 #endif 32 #ifdef HAVE_STDLIB_H 33 #include <stdlib.h> 34 #endif 35 #ifdef HAVE_TIME_H 36 #include <time.h> 37 #endif 38 #ifdef HAVE_GETOPT_H 39 #include <getopt.h> 40 #endif 41 #ifdef HAVE_STRING_H 42 #include <string.h> 43 #endif 44 45 #include "compat.h" 46 #include "types.h" 47 #include "mft.h" 48 #include "attrib.h" 49 #include "layout.h" 50 #include "inode.h" 51 #include "utils.h" 52 #include "dir.h" 53 #include "list.h" 54 #include "ntfstime.h" 55 #include "version.h" 56 #include "logging.h" 57 58 static const char *EXEC_NAME = "ntfsls"; 59 60 /** 61 * To hold sub-directory information for recursive listing. 62 * @depth: the level of this dir relative to opts.path 63 */ 64 struct dir { 65 struct list_head list; 66 ntfs_inode *ni; 67 char name[MAX_PATH]; 68 int depth; 69 }; 70 71 /** 72 * path_component - to store path component strings 73 * 74 * @name: string pointer 75 * 76 * NOTE: @name is not directly allocated memory. It simply points to the 77 * character array name in struct dir. 78 */ 79 struct path_component { 80 struct list_head list; 81 const char *name; 82 }; 83 84 /* The list of sub-dirs is like a "horizontal" tree. The root of 85 * the tree is opts.path, but it is not part of the list because 86 * that's not necessary. The rules of the list are (in order of 87 * precedence): 88 * 1. directories immediately follow their parent. 89 * 2. siblings are next to one another. 90 * 91 * For example, if: 92 * 1. opts.path is / 93 * 2. / has 2 sub-dirs: dir1 and dir2 94 * 3. dir1 has 2 sub-dirs: dir11 and dir12 95 * 4. dir2 has 0 sub-dirs 96 * then the list will be: 97 * dummy head -> dir1 -> dir11 -> dir12 -> dir2 98 * 99 * dir_list_insert_pos keeps track of where to insert a sub-dir 100 * into the list. 101 */ 102 static struct list_head *dir_list_insert_pos = NULL; 103 104 /* The global depth relative to opts.path. 105 * ie: opts.path has depth 0, a sub-dir of opts.path has depth 1 106 */ 107 static int depth = 0; 108 109 static struct options { 110 char *device; /* Device/File to work with */ 111 int quiet; /* Less output */ 112 int verbose; /* Extra output */ 113 int force; /* Override common sense */ 114 int all; 115 int system; 116 int dos; 117 int lng; 118 int inode; 119 int classify; 120 int recursive; 121 const char *path; 122 } opts; 123 124 typedef struct { 125 ntfs_volume *vol; 126 } ntfsls_dirent; 127 128 static int list_dir_entry(ntfsls_dirent * dirent, const ntfschar * name, 129 const int name_len, const int name_type, 130 const s64 pos, const MFT_REF mref, 131 const unsigned dt_type); 132 133 /** 134 * version - Print version information about the program 135 * 136 * Print a copyright statement and a brief description of the program. 137 * 138 * Return: none 139 */ 140 static void version(void) 141 { 142 printf("\n%s v%s (libntfs %s) - Display information about an NTFS " 143 "Volume.\n\n", EXEC_NAME, VERSION, 144 ntfs_libntfs_version()); 145 printf("Copyright (c) 2003 Lode Leroy\n"); 146 printf("Copyright (c) 2003-2005 Anton Altaparmakov\n"); 147 printf("Copyright (c) 2003 Richard Russon\n"); 148 printf("Copyright (c) 2004 Carmelo Kintana\n"); 149 printf("Copyright (c) 2004 Giang Nguyen\n"); 150 printf("\n%s\n%s%s\n", ntfs_gpl, ntfs_bugs, ntfs_home); 151 } 152 153 /** 154 * usage - Print a list of the parameters to the program 155 * 156 * Print a list of the parameters and options for the program. 157 * 158 * Return: none 159 */ 160 static void usage(void) 161 { 162 printf("\nUsage: %s [options] device\n" 163 "\n" 164 " -a, --all Display all files\n" 165 " -F, --classify Display classification\n" 166 " -f, --force Use less caution\n" 167 " -h, --help Display this help\n" 168 " -i, --inode Display inode numbers\n" 169 " -l, --long Display long info\n" 170 " -p, --path PATH Directory whose contents to list\n" 171 " -q, --quiet Less output\n" 172 " -R, --recursive Recursively list subdirectories\n" 173 " -s, --system Display system files\n" 174 " -V, --version Display version information\n" 175 " -v, --verbose More output\n" 176 " -x, --dos Use short (DOS 8.3) names\n" 177 "\n", 178 EXEC_NAME); 179 180 printf("NOTE: If neither -a nor -s is specified, the program defaults to -a.\n\n"); 181 182 printf("%s%s\n", ntfs_bugs, ntfs_home); 183 } 184 185 /** 186 * parse_options - Read and validate the programs command line 187 * 188 * Read the command line, verify the syntax and parse the options. 189 * This function is very long, but quite simple. 190 * 191 * Return: 1 Success 192 * 0 Error, one or more problems 193 */ 194 static int parse_options(int argc, char *argv[]) 195 { 196 static const char *sopt = "-aFfh?ilp:qRsVvx"; 197 static const struct option lopt[] = { 198 { "all", no_argument, NULL, 'a' }, 199 { "classify", no_argument, NULL, 'F' }, 200 { "force", no_argument, NULL, 'f' }, 201 { "help", no_argument, NULL, 'h' }, 202 { "inode", no_argument, NULL, 'i' }, 203 { "long", no_argument, NULL, 'l' }, 204 { "path", required_argument, NULL, 'p' }, 205 { "recursive", no_argument, NULL, 'R' }, 206 { "quiet", no_argument, NULL, 'q' }, 207 { "system", no_argument, NULL, 's' }, 208 { "version", no_argument, NULL, 'V' }, 209 { "verbose", no_argument, NULL, 'v' }, 210 { "dos", no_argument, NULL, 'x' }, 211 { NULL, 0, NULL, 0 }, 212 }; 213 214 int c = -1; 215 int err = 0; 216 int ver = 0; 217 int help = 0; 218 int levels = 0; 219 220 opterr = 0; /* We'll handle the errors, thank you. */ 221 222 memset(&opts, 0, sizeof(opts)); 223 opts.device = NULL; 224 opts.path = "/"; 225 226 while ((c = getopt_long(argc, argv, sopt, lopt, NULL)) != -1) { 227 switch (c) { 228 case 1: 229 if (!opts.device) 230 opts.device = optarg; 231 else 232 err++; 233 break; 234 case 'p': 235 opts.path = optarg; 236 break; 237 case 'f': 238 opts.force++; 239 break; 240 case 'h': 241 case '?': 242 if (strncmp (argv[optind-1], "--log-", 6) == 0) { 243 if (!ntfs_log_parse_option (argv[optind-1])) 244 err++; 245 break; 246 } 247 help++; 248 break; 249 case 'q': 250 opts.quiet++; 251 ntfs_log_clear_levels(NTFS_LOG_LEVEL_QUIET); 252 break; 253 case 'v': 254 opts.verbose++; 255 ntfs_log_set_levels(NTFS_LOG_LEVEL_VERBOSE); 256 break; 257 case 'V': 258 ver++; 259 break; 260 case 'x': 261 opts.dos = 1; 262 break; 263 case 'l': 264 opts.lng++; 265 break; 266 case 'i': 267 opts.inode++; 268 break; 269 case 'F': 270 opts.classify++; 271 break; 272 case 'a': 273 opts.all++; 274 break; 275 case 's': 276 opts.system++; 277 break; 278 case 'R': 279 opts.recursive++; 280 break; 281 default: 282 ntfs_log_error("Unknown option '%s'.\n", argv[optind - 1]); 283 err++; 284 break; 285 } 286 } 287 288 /* Make sure we're in sync with the log levels */ 289 levels = ntfs_log_get_levels(); 290 if (levels & NTFS_LOG_LEVEL_VERBOSE) 291 opts.verbose++; 292 if (!(levels & NTFS_LOG_LEVEL_QUIET)) 293 opts.quiet++; 294 295 /* defaults to -a if -s is not specified */ 296 if (!opts.system) 297 opts.all++; 298 299 if (help || ver) 300 opts.quiet = 0; 301 else { 302 if (opts.device == NULL) { 303 if (argc > 1) 304 ntfs_log_error("You must specify exactly one " 305 "device.\n"); 306 err++; 307 } 308 309 if (opts.quiet && opts.verbose) { 310 ntfs_log_error("You may not use --quiet and --verbose at the " 311 "same time.\n"); 312 err++; 313 } 314 } 315 316 if (ver) 317 version(); 318 if (help || err) 319 usage(); 320 321 return (!err && !help && !ver); 322 } 323 324 /** 325 * free_dir - free one dir 326 * @tofree: the dir to free 327 * 328 * Close the inode and then free the dir 329 */ 330 static void free_dir(struct dir *tofree) 331 { 332 if (tofree) { 333 if (tofree->ni) { 334 ntfs_inode_close(tofree->ni); 335 tofree->ni = NULL; 336 } 337 free(tofree); 338 } 339 } 340 341 /** 342 * free_dirs - walk the list of dir's and free each of them 343 * @dir_list: the list_head of any entry in the list 344 * 345 * Iterate over @dir_list, calling free_dir on each entry 346 */ 347 static void free_dirs(struct list_head *dir_list) 348 { 349 struct dir *tofree = NULL; 350 struct list_head *walker = NULL; 351 352 if (dir_list) { 353 list_for_each(walker, dir_list) { 354 free_dir(tofree); 355 tofree = list_entry(walker, struct dir, list); 356 } 357 358 free_dir(tofree); 359 } 360 } 361 362 /** 363 * readdir_recursive - list a directory and sub-directories encountered 364 * @ni: ntfs inode of the directory to list 365 * @pos: current position in directory 366 * @dirent: context for filldir callback supplied by the caller 367 * 368 * For each directory, print its path relative to opts.path. List a directory, 369 * then list each of its sub-directories. 370 * 371 * Returns 0 on success or -1 on error. 372 * 373 * NOTE: Assumes recursive option. Currently no limit on the depths of 374 * recursion. 375 */ 376 static int readdir_recursive(ntfs_inode * ni, s64 * pos, ntfsls_dirent * dirent) 377 { 378 /* list of dirs to "ls" recursively */ 379 static struct dir dirs = { 380 .list = LIST_HEAD_INIT(dirs.list), 381 .ni = NULL, 382 .name = {0}, 383 .depth = 0 384 }; 385 386 static struct path_component paths = { 387 .list = LIST_HEAD_INIT(paths.list), 388 .name = NULL 389 }; 390 391 static struct path_component base_comp; 392 393 struct dir *subdir = NULL; 394 struct dir *tofree = NULL; 395 struct path_component comp; 396 struct path_component *tempcomp = NULL; 397 struct list_head *dir_walker = NULL; 398 struct list_head *comp_walker = NULL; 399 s64 pos2 = 0; 400 int ni_depth = depth; 401 int result = 0; 402 403 if (list_empty(&dirs.list)) { 404 base_comp.name = opts.path; 405 list_add(&base_comp.list, &paths.list); 406 dir_list_insert_pos = &dirs.list; 407 printf("%s:\n", opts.path); 408 } 409 410 depth++; 411 412 result = ntfs_readdir(ni, pos, dirent, (ntfs_filldir_t) list_dir_entry); 413 414 if (result == 0) { 415 list_add_tail(&comp.list, &paths.list); 416 417 /* for each of ni's sub-dirs: list in this iteration, then 418 free at the top of the next iteration or outside of loop */ 419 list_for_each(dir_walker, &dirs.list) { 420 if (tofree) { 421 free_dir(tofree); 422 tofree = NULL; 423 } 424 subdir = list_entry(dir_walker, struct dir, list); 425 426 /* subdir is not a subdir of ni */ 427 if (subdir->depth != ni_depth + 1) 428 break; 429 430 pos2 = 0; 431 dir_list_insert_pos = &dirs.list; 432 if (!subdir->ni) { 433 subdir->ni = 434 ntfs_pathname_to_inode(ni->vol, ni, 435 subdir->name); 436 437 if (!subdir->ni) { 438 ntfs_log_error 439 ("ntfsls::readdir_recursive(): cannot get inode from pathname.\n"); 440 result = -1; 441 break; 442 } 443 } 444 puts(""); 445 446 comp.name = subdir->name; 447 448 /* print relative path header */ 449 list_for_each(comp_walker, &paths.list) { 450 tempcomp = 451 list_entry(comp_walker, 452 struct path_component, list); 453 printf("%s", tempcomp->name); 454 if (tempcomp != &comp 455 && *tempcomp->name != PATH_SEP 456 && (!opts.classify 457 || tempcomp == &base_comp)) 458 putchar(PATH_SEP); 459 } 460 puts(":"); 461 462 result = readdir_recursive(subdir->ni, &pos2, dirent); 463 464 if (result) 465 break; 466 467 tofree = subdir; 468 list_del(dir_walker); 469 } 470 471 list_del(&comp.list); 472 } 473 474 if (tofree) 475 free_dir(tofree); 476 477 /* if at the outer-most readdir_recursive, then clean up */ 478 if (ni_depth == 0) { 479 free_dirs(&dirs.list); 480 } 481 482 depth--; 483 484 return result; 485 } 486 487 /** 488 * list_dir_entry 489 * 490 * FIXME: Should we print errors as we go along? (AIA) 491 */ 492 static int list_dir_entry(ntfsls_dirent * dirent, const ntfschar * name, 493 const int name_len, const int name_type, 494 const s64 pos __attribute__((unused)), 495 const MFT_REF mref, const unsigned dt_type) 496 { 497 char *filename = NULL; 498 int result = 0; 499 500 struct dir *dir = NULL; 501 502 filename = calloc(1, MAX_PATH); 503 if (!filename) 504 return -1; 505 506 if (ntfs_ucstombs(name, name_len, &filename, MAX_PATH) < 0) { 507 ntfs_log_error("Cannot represent filename in current locale.\n"); 508 goto free; 509 } 510 511 result = 0; // These are successful 512 if ((MREF(mref) < FILE_first_user) && (!opts.system)) 513 goto free; 514 if (name_type == FILE_NAME_POSIX && !opts.all) 515 goto free; 516 if (((name_type & FILE_NAME_WIN32_AND_DOS) == FILE_NAME_WIN32) && 517 opts.dos) 518 goto free; 519 if (((name_type & FILE_NAME_WIN32_AND_DOS) == FILE_NAME_DOS) && 520 !opts.dos) 521 goto free; 522 if (dt_type == NTFS_DT_DIR && opts.classify) 523 sprintf(filename + strlen(filename), "/"); 524 525 if (dt_type == NTFS_DT_DIR && opts.recursive 526 && strcmp(filename, ".") && strcmp(filename, "./") 527 && strcmp(filename, "..") && strcmp(filename, "../")) 528 { 529 dir = (struct dir *)calloc(1, sizeof(struct dir)); 530 531 if (!dir) { 532 ntfs_log_error("Failed to allocate for subdir.\n"); 533 result = -1; 534 goto free; 535 } 536 537 strcpy(dir->name, filename); 538 dir->ni = NULL; 539 dir->depth = depth; 540 } 541 542 if (!opts.lng) { 543 if (!opts.inode) 544 printf("%s\n", filename); 545 else 546 printf("%7llu %s\n", (unsigned long long)MREF(mref), 547 filename); 548 result = 0; 549 } else { 550 s64 filesize = 0; 551 ntfs_inode *ni; 552 ntfs_attr_search_ctx *ctx = NULL; 553 FILE_NAME_ATTR *file_name_attr; 554 ATTR_RECORD *attr; 555 time_t ntfs_time; 556 char t_buf[26]; 557 558 result = -1; // Everything else is bad 559 560 ni = ntfs_inode_open(dirent->vol, mref); 561 if (!ni) 562 goto release; 563 564 ctx = ntfs_attr_get_search_ctx(ni, NULL); 565 if (!ctx) 566 goto release; 567 568 if (ntfs_attr_lookup(AT_FILE_NAME, AT_UNNAMED, 0, 0, 0, NULL, 569 0, ctx)) 570 goto release; 571 attr = ctx->attr; 572 573 file_name_attr = (FILE_NAME_ATTR *)((char *)attr + 574 le16_to_cpu(attr->u.res.value_offset)); 575 if (!file_name_attr) 576 goto release; 577 578 ntfs_time = ntfs2utc(file_name_attr->last_data_change_time); 579 strcpy(t_buf, ctime(&ntfs_time)); 580 memmove(t_buf+16, t_buf+19, 5); 581 t_buf[21] = '\0'; 582 583 if (dt_type != NTFS_DT_DIR) { 584 if (!ntfs_attr_lookup(AT_DATA, AT_UNNAMED, 0, 0, 0, 585 NULL, 0, ctx)) 586 filesize = ntfs_get_attribute_value_length( 587 ctx->attr); 588 } 589 590 if (opts.inode) 591 printf("%7llu %8lld %s %s\n", 592 (unsigned long long)MREF(mref), 593 (long long)filesize, t_buf + 4, 594 filename); 595 else 596 printf("%8lld %s %s\n", (long long)filesize, t_buf + 4, 597 filename); 598 599 if (dir) { 600 dir->ni = ni; 601 ni = NULL; /* so release does not close inode */ 602 } 603 604 result = 0; 605 release: 606 /* Release attribute search context and close the inode. */ 607 if (ctx) 608 ntfs_attr_put_search_ctx(ctx); 609 if (ni) 610 ntfs_inode_close(ni); 611 } 612 613 if (dir) { 614 if (result == 0) { 615 list_add(&dir->list, dir_list_insert_pos); 616 dir_list_insert_pos = &dir->list; 617 } else { 618 free(dir); 619 dir = NULL; 620 } 621 } 622 623 free: 624 free(filename); 625 return result; 626 } 627 628 /** 629 * main - Begin here 630 * 631 * Start from here. 632 * 633 * Return: 0 Success, the program worked 634 * 1 Error, parsing mount options failed 635 * 2 Error, mount attempt failed 636 * 3 Error, failed to open root directory 637 * 4 Error, failed to open directory in search path 638 */ 639 int main(int argc, char **argv) 640 { 641 s64 pos; 642 ntfs_volume *vol; 643 ntfs_inode *ni; 644 ntfsls_dirent dirent; 645 646 ntfs_log_set_handler(ntfs_log_handler_outerr); 647 648 if (!parse_options(argc, argv)) { 649 // FIXME: Print error... (AIA) 650 return 1; 651 } 652 653 utils_set_locale(); 654 655 vol = utils_mount_volume(opts.device, NTFS_MNT_RDONLY | 656 (opts.force ? NTFS_MNT_FORCE : 0)); 657 if (!vol) { 658 // FIXME: Print error... (AIA) 659 return 2; 660 } 661 662 ni = ntfs_pathname_to_inode(vol, NULL, opts.path); 663 if (!ni) { 664 // FIXME: Print error... (AIA) 665 ntfs_umount(vol, FALSE); 666 return 3; 667 } 668 669 /* 670 * We now are at the final path component. If it is a file just 671 * list it. If it is a directory, list its contents. 672 */ 673 pos = 0; 674 memset(&dirent, 0, sizeof(dirent)); 675 dirent.vol = vol; 676 if (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY) { 677 if (opts.recursive) 678 readdir_recursive(ni, &pos, &dirent); 679 else 680 ntfs_readdir(ni, &pos, &dirent, 681 (ntfs_filldir_t) list_dir_entry); 682 // FIXME: error checking... (AIA) 683 } else { 684 ATTR_RECORD *rec; 685 FILE_NAME_ATTR *attr; 686 ntfs_attr_search_ctx *ctx; 687 int space = 4; 688 ntfschar *name = NULL; 689 int name_len = 0;; 690 691 ctx = ntfs_attr_get_search_ctx(ni, NULL); 692 if (!ctx) 693 return -1; 694 695 while ((rec = find_attribute(AT_FILE_NAME, ctx))) { 696 /* We know this will always be resident. */ 697 attr = (FILE_NAME_ATTR *) ((char *) rec + le16_to_cpu(rec->u.res.value_offset)); 698 699 if (attr->file_name_type < space) { 700 name = attr->file_name; 701 name_len = attr->file_name_length; 702 space = attr->file_name_type; 703 } 704 } 705 706 list_dir_entry(&dirent, name, name_len, space, pos, ni->mft_no, 707 NTFS_DT_REG); 708 // FIXME: error checking... (AIA) 709 710 ntfs_attr_put_search_ctx(ctx); 711 } 712 713 /* Finished with the inode; release it. */ 714 ntfs_inode_close(ni); 715 716 ntfs_umount(vol, FALSE); 717 return 0; 718 } 719