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