ntfs-3g/ntfsprogs/ntfsls.c
Erik Larsson 7506d8b80b Rename legacy MS_* flags for ntfs_mount with NTFS_MNT_* flags.
The MS_* flags originated from system constants. However the flags
passed to ntfs_mount were really unrelated to the system constants and
many new MS_* flags had to be introduced as different features were
added to the library. Those flags had no counterparts in any system
APIs, so using the same naming scheme is inappropriate.

Instead, let's namespace these flags similarly to what has already been
done in ntfsprogs/libntfs earlier. This avoids any possible conflicts
with system constants.
The values of the flags themselves are kept the same as earlier, so
backward compatibility is retained.
2012-11-07 16:29:48 +01:00

718 lines
17 KiB
C

/**
* ntfsls - Part of the Linux-NTFS project.
*
* Copyright (c) 2003 Lode Leroy
* Copyright (c) 2003-2005 Anton Altaparmakov
* Copyright (c) 2003 Richard Russon
* Copyright (c) 2004 Carmelo Kintana
* Copyright (c) 2004 Giang Nguyen
*
* This utility will list a directory's files.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program (in the main directory of the Linux-NTFS
* distribution in the file COPYING); if not, write to the Free Software
* Foundation,Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "config.h"
#ifdef HAVE_STDIO_H
#include <stdio.h>
#endif
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#ifdef HAVE_TIME_H
#include <time.h>
#endif
#ifdef HAVE_GETOPT_H
#include <getopt.h>
#endif
#ifdef HAVE_STRING_H
#include <string.h>
#endif
#include "types.h"
#include "mft.h"
#include "attrib.h"
#include "layout.h"
#include "inode.h"
#include "utils.h"
#include "dir.h"
#include "list.h"
#include "ntfstime.h"
/* #include "version.h" */
#include "logging.h"
static const char *EXEC_NAME = "ntfsls";
/**
* To hold sub-directory information for recursive listing.
* @depth: the level of this dir relative to opts.path
*/
struct dir {
struct ntfs_list_head list;
ntfs_inode *ni;
char name[MAX_PATH];
int depth;
};
/**
* path_component - to store path component strings
*
* @name: string pointer
*
* NOTE: @name is not directly allocated memory. It simply points to the
* character array name in struct dir.
*/
struct path_component {
struct ntfs_list_head list;
const char *name;
};
/* The list of sub-dirs is like a "horizontal" tree. The root of
* the tree is opts.path, but it is not part of the list because
* that's not necessary. The rules of the list are (in order of
* precedence):
* 1. directories immediately follow their parent.
* 2. siblings are next to one another.
*
* For example, if:
* 1. opts.path is /
* 2. / has 2 sub-dirs: dir1 and dir2
* 3. dir1 has 2 sub-dirs: dir11 and dir12
* 4. dir2 has 0 sub-dirs
* then the list will be:
* dummy head -> dir1 -> dir11 -> dir12 -> dir2
*
* dir_list_insert_pos keeps track of where to insert a sub-dir
* into the list.
*/
static struct ntfs_list_head *dir_list_insert_pos = NULL;
/* The global depth relative to opts.path.
* ie: opts.path has depth 0, a sub-dir of opts.path has depth 1
*/
static int depth = 0;
static struct options {
char *device; /* Device/File to work with */
int quiet; /* Less output */
int verbose; /* Extra output */
int force; /* Override common sense */
int all;
int system;
int dos;
int lng;
int inode;
int classify;
int recursive;
const char *path;
} opts;
typedef struct {
ntfs_volume *vol;
} ntfsls_dirent;
static int list_dir_entry(ntfsls_dirent * dirent, const ntfschar * name,
const int name_len, const int name_type,
const s64 pos, const MFT_REF mref,
const unsigned dt_type);
/**
* version - Print version information about the program
*
* Print a copyright statement and a brief description of the program.
*
* Return: none
*/
static void version(void)
{
printf("\n%s v%s (libntfs-3g) - Display information about an NTFS "
"Volume.\n\n", EXEC_NAME, VERSION);
printf("Copyright (c) 2003 Lode Leroy\n");
printf("Copyright (c) 2003-2005 Anton Altaparmakov\n");
printf("Copyright (c) 2003 Richard Russon\n");
printf("Copyright (c) 2004 Carmelo Kintana\n");
printf("Copyright (c) 2004 Giang Nguyen\n");
printf("\n%s\n%s%s\n", ntfs_gpl, ntfs_bugs, ntfs_home);
}
/**
* usage - Print a list of the parameters to the program
*
* Print a list of the parameters and options for the program.
*
* Return: none
*/
static void usage(void)
{
printf("\nUsage: %s [options] device\n"
"\n"
" -a, --all Display all files\n"
" -F, --classify Display classification\n"
" -f, --force Use less caution\n"
" -h, --help Display this help\n"
" -i, --inode Display inode numbers\n"
" -l, --long Display long info\n"
" -p, --path PATH Directory whose contents to list\n"
" -q, --quiet Less output\n"
" -R, --recursive Recursively list subdirectories\n"
" -s, --system Display system files\n"
" -V, --version Display version information\n"
" -v, --verbose More output\n"
" -x, --dos Use short (DOS 8.3) names\n"
"\n",
EXEC_NAME);
printf("NOTE: If neither -a nor -s is specified, the program defaults to -a.\n\n");
printf("%s%s\n", ntfs_bugs, ntfs_home);
}
/**
* parse_options - Read and validate the programs command line
*
* Read the command line, verify the syntax and parse the options.
* This function is very long, but quite simple.
*
* Return: 1 Success
* 0 Error, one or more problems
*/
static int parse_options(int argc, char *argv[])
{
static const char *sopt = "-aFfh?ilp:qRsVvx";
static const struct option lopt[] = {
{ "all", no_argument, NULL, 'a' },
{ "classify", no_argument, NULL, 'F' },
{ "force", no_argument, NULL, 'f' },
{ "help", no_argument, NULL, 'h' },
{ "inode", no_argument, NULL, 'i' },
{ "long", no_argument, NULL, 'l' },
{ "path", required_argument, NULL, 'p' },
{ "recursive", no_argument, NULL, 'R' },
{ "quiet", no_argument, NULL, 'q' },
{ "system", no_argument, NULL, 's' },
{ "version", no_argument, NULL, 'V' },
{ "verbose", no_argument, NULL, 'v' },
{ "dos", no_argument, NULL, 'x' },
{ NULL, 0, NULL, 0 },
};
int c = -1;
int err = 0;
int ver = 0;
int help = 0;
int levels = 0;
opterr = 0; /* We'll handle the errors, thank you. */
memset(&opts, 0, sizeof(opts));
opts.device = NULL;
opts.path = "/";
while ((c = getopt_long(argc, argv, sopt, lopt, NULL)) != -1) {
switch (c) {
case 1:
if (!opts.device)
opts.device = optarg;
else
err++;
break;
case 'p':
opts.path = optarg;
break;
case 'f':
opts.force++;
break;
case 'h':
case '?':
if (strncmp (argv[optind-1], "--log-", 6) == 0) {
if (!ntfs_log_parse_option (argv[optind-1]))
err++;
break;
}
help++;
break;
case 'q':
opts.quiet++;
ntfs_log_clear_levels(NTFS_LOG_LEVEL_QUIET);
break;
case 'v':
opts.verbose++;
ntfs_log_set_levels(NTFS_LOG_LEVEL_VERBOSE);
break;
case 'V':
ver++;
break;
case 'x':
opts.dos = 1;
break;
case 'l':
opts.lng++;
break;
case 'i':
opts.inode++;
break;
case 'F':
opts.classify++;
break;
case 'a':
opts.all++;
break;
case 's':
opts.system++;
break;
case 'R':
opts.recursive++;
break;
default:
ntfs_log_error("Unknown option '%s'.\n", argv[optind - 1]);
err++;
break;
}
}
/* Make sure we're in sync with the log levels */
levels = ntfs_log_get_levels();
if (levels & NTFS_LOG_LEVEL_VERBOSE)
opts.verbose++;
if (!(levels & NTFS_LOG_LEVEL_QUIET))
opts.quiet++;
/* defaults to -a if -s is not specified */
if (!opts.system)
opts.all++;
if (help || ver)
opts.quiet = 0;
else {
if (opts.device == NULL) {
if (argc > 1)
ntfs_log_error("You must specify exactly one "
"device.\n");
err++;
}
if (opts.quiet && opts.verbose) {
ntfs_log_error("You may not use --quiet and --verbose at the "
"same time.\n");
err++;
}
}
if (ver)
version();
if (help || err)
usage();
return (!err && !help && !ver);
}
/**
* free_dir - free one dir
* @tofree: the dir to free
*
* Close the inode and then free the dir
*/
static void free_dir(struct dir *tofree)
{
if (tofree) {
if (tofree->ni) {
ntfs_inode_close(tofree->ni);
tofree->ni = NULL;
}
free(tofree);
}
}
/**
* free_dirs - walk the list of dir's and free each of them
* @dir_list: the ntfs_list_head of any entry in the list
*
* Iterate over @dir_list, calling free_dir on each entry
*/
static void free_dirs(struct ntfs_list_head *dir_list)
{
struct dir *tofree = NULL;
struct ntfs_list_head *walker = NULL;
if (dir_list) {
ntfs_list_for_each(walker, dir_list) {
free_dir(tofree);
tofree = ntfs_list_entry(walker, struct dir, list);
}
free_dir(tofree);
}
}
/**
* readdir_recursive - list a directory and sub-directories encountered
* @ni: ntfs inode of the directory to list
* @pos: current position in directory
* @dirent: context for filldir callback supplied by the caller
*
* For each directory, print its path relative to opts.path. List a directory,
* then list each of its sub-directories.
*
* Returns 0 on success or -1 on error.
*
* NOTE: Assumes recursive option. Currently no limit on the depths of
* recursion.
*/
static int readdir_recursive(ntfs_inode * ni, s64 * pos, ntfsls_dirent * dirent)
{
/* list of dirs to "ls" recursively */
static struct dir dirs = {
.list = NTFS_LIST_HEAD_INIT(dirs.list),
.ni = NULL,
.name = {0},
.depth = 0
};
static struct path_component paths = {
.list = NTFS_LIST_HEAD_INIT(paths.list),
.name = NULL
};
static struct path_component base_comp;
struct dir *subdir = NULL;
struct dir *tofree = NULL;
struct path_component comp;
struct path_component *tempcomp = NULL;
struct ntfs_list_head *dir_walker = NULL;
struct ntfs_list_head *comp_walker = NULL;
s64 pos2 = 0;
int ni_depth = depth;
int result = 0;
if (ntfs_list_empty(&dirs.list)) {
base_comp.name = opts.path;
ntfs_list_add(&base_comp.list, &paths.list);
dir_list_insert_pos = &dirs.list;
printf("%s:\n", opts.path);
}
depth++;
result = ntfs_readdir(ni, pos, dirent, (ntfs_filldir_t) list_dir_entry);
if (result == 0) {
ntfs_list_add_tail(&comp.list, &paths.list);
/* for each of ni's sub-dirs: list in this iteration, then
free at the top of the next iteration or outside of loop */
ntfs_list_for_each(dir_walker, &dirs.list) {
if (tofree) {
free_dir(tofree);
tofree = NULL;
}
subdir = ntfs_list_entry(dir_walker, struct dir, list);
/* subdir is not a subdir of ni */
if (subdir->depth != ni_depth + 1)
break;
pos2 = 0;
dir_list_insert_pos = &dirs.list;
if (!subdir->ni) {
subdir->ni =
ntfs_pathname_to_inode(ni->vol, ni,
subdir->name);
if (!subdir->ni) {
ntfs_log_error
("ntfsls::readdir_recursive(): cannot get inode from pathname.\n");
result = -1;
break;
}
}
puts("");
comp.name = subdir->name;
/* print relative path header */
ntfs_list_for_each(comp_walker, &paths.list) {
tempcomp =
ntfs_list_entry(comp_walker,
struct path_component, list);
printf("%s", tempcomp->name);
if (tempcomp != &comp
&& *tempcomp->name != PATH_SEP
&& (!opts.classify
|| tempcomp == &base_comp))
putchar(PATH_SEP);
}
puts(":");
result = readdir_recursive(subdir->ni, &pos2, dirent);
if (result)
break;
tofree = subdir;
ntfs_list_del(dir_walker);
}
ntfs_list_del(&comp.list);
}
if (tofree)
free_dir(tofree);
/* if at the outer-most readdir_recursive, then clean up */
if (ni_depth == 0) {
free_dirs(&dirs.list);
}
depth--;
return result;
}
/**
* list_dir_entry
*
* FIXME: Should we print errors as we go along? (AIA)
*/
static int list_dir_entry(ntfsls_dirent * dirent, const ntfschar * name,
const int name_len, const int name_type,
const s64 pos __attribute__((unused)),
const MFT_REF mref, const unsigned dt_type)
{
char *filename = NULL;
int result = 0;
struct dir *dir = NULL;
filename = calloc(1, MAX_PATH);
if (!filename)
return -1;
if (ntfs_ucstombs(name, name_len, &filename, MAX_PATH) < 0) {
ntfs_log_error("Cannot represent filename in current locale.\n");
goto free;
}
result = 0; // These are successful
if ((MREF(mref) < FILE_first_user) && (!opts.system))
goto free;
if (name_type == FILE_NAME_POSIX && !opts.all)
goto free;
if (((name_type & FILE_NAME_WIN32_AND_DOS) == FILE_NAME_WIN32) &&
opts.dos)
goto free;
if (((name_type & FILE_NAME_WIN32_AND_DOS) == FILE_NAME_DOS) &&
!opts.dos)
goto free;
if (dt_type == NTFS_DT_DIR && opts.classify)
sprintf(filename + strlen(filename), "/");
if (dt_type == NTFS_DT_DIR && opts.recursive
&& strcmp(filename, ".") && strcmp(filename, "./")
&& strcmp(filename, "..") && strcmp(filename, "../"))
{
dir = (struct dir *)calloc(1, sizeof(struct dir));
if (!dir) {
ntfs_log_error("Failed to allocate for subdir.\n");
result = -1;
goto free;
}
strcpy(dir->name, filename);
dir->ni = NULL;
dir->depth = depth;
}
if (!opts.lng) {
if (!opts.inode)
printf("%s\n", filename);
else
printf("%7llu %s\n", (unsigned long long)MREF(mref),
filename);
result = 0;
} else {
s64 filesize = 0;
ntfs_inode *ni;
ntfs_attr_search_ctx *ctx = NULL;
FILE_NAME_ATTR *file_name_attr;
ATTR_RECORD *attr;
struct timespec change_time;
char t_buf[26];
result = -1; // Everything else is bad
ni = ntfs_inode_open(dirent->vol, mref);
if (!ni)
goto release;
ctx = ntfs_attr_get_search_ctx(ni, NULL);
if (!ctx)
goto release;
if (ntfs_attr_lookup(AT_FILE_NAME, AT_UNNAMED, 0, 0, 0, NULL,
0, ctx))
goto release;
attr = ctx->attr;
file_name_attr = (FILE_NAME_ATTR *)((char *)attr +
le16_to_cpu(attr->value_offset));
if (!file_name_attr)
goto release;
change_time = ntfs2timespec(file_name_attr->last_data_change_time);
strcpy(t_buf, ctime(&change_time.tv_sec));
memmove(t_buf+16, t_buf+19, 5);
t_buf[21] = '\0';
if (dt_type != NTFS_DT_DIR) {
if (!ntfs_attr_lookup(AT_DATA, AT_UNNAMED, 0, 0, 0,
NULL, 0, ctx))
filesize = ntfs_get_attribute_value_length(
ctx->attr);
}
if (opts.inode)
printf("%7llu %8lld %s %s\n",
(unsigned long long)MREF(mref),
(long long)filesize, t_buf + 4,
filename);
else
printf("%8lld %s %s\n", (long long)filesize, t_buf + 4,
filename);
if (dir) {
dir->ni = ni;
ni = NULL; /* so release does not close inode */
}
result = 0;
release:
/* Release attribute search context and close the inode. */
if (ctx)
ntfs_attr_put_search_ctx(ctx);
if (ni)
ntfs_inode_close(ni);
}
if (dir) {
if (result == 0) {
ntfs_list_add(&dir->list, dir_list_insert_pos);
dir_list_insert_pos = &dir->list;
} else {
free(dir);
dir = NULL;
}
}
free:
free(filename);
return result;
}
/**
* main - Begin here
*
* Start from here.
*
* Return: 0 Success, the program worked
* 1 Error, parsing mount options failed
* 2 Error, mount attempt failed
* 3 Error, failed to open root directory
* 4 Error, failed to open directory in search path
*/
int main(int argc, char **argv)
{
s64 pos;
ntfs_volume *vol;
ntfs_inode *ni;
ntfsls_dirent dirent;
ntfs_log_set_handler(ntfs_log_handler_outerr);
if (!parse_options(argc, argv)) {
// FIXME: Print error... (AIA)
return 1;
}
utils_set_locale();
vol = utils_mount_volume(opts.device, NTFS_MNT_RDONLY |
(opts.force ? NTFS_MNT_RECOVER : 0));
if (!vol) {
// FIXME: Print error... (AIA)
return 2;
}
ni = ntfs_pathname_to_inode(vol, NULL, opts.path);
if (!ni) {
// FIXME: Print error... (AIA)
ntfs_umount(vol, FALSE);
return 3;
}
/*
* We now are at the final path component. If it is a file just
* list it. If it is a directory, list its contents.
*/
pos = 0;
memset(&dirent, 0, sizeof(dirent));
dirent.vol = vol;
if (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY) {
if (opts.recursive)
readdir_recursive(ni, &pos, &dirent);
else
ntfs_readdir(ni, &pos, &dirent,
(ntfs_filldir_t) list_dir_entry);
// FIXME: error checking... (AIA)
} else {
ATTR_RECORD *rec;
FILE_NAME_ATTR *attr;
ntfs_attr_search_ctx *ctx;
int space = 4;
ntfschar *name = NULL;
int name_len = 0;;
ctx = ntfs_attr_get_search_ctx(ni, NULL);
if (!ctx)
return -1;
while ((rec = find_attribute(AT_FILE_NAME, ctx))) {
/* We know this will always be resident. */
attr = (FILE_NAME_ATTR *) ((char *) rec + le16_to_cpu(rec->value_offset));
if (attr->file_name_type < space) {
name = attr->file_name;
name_len = attr->file_name_length;
space = attr->file_name_type;
}
}
list_dir_entry(&dirent, name, name_len, space, pos, ni->mft_no,
NTFS_DT_REG);
// FIXME: error checking... (AIA)
ntfs_attr_put_search_ctx(ctx);
}
/* Finished with the inode; release it. */
ntfs_inode_close(ni);
ntfs_umount(vol, FALSE);
return 0;
}