mirror of
https://git.code.sf.net/p/ntfs-3g/ntfs-3g.git
synced 2024-12-03 23:13:39 +08:00
7506d8b80b
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.
591 lines
15 KiB
C
591 lines
15 KiB
C
/**
|
|
* ntfscp - Part of the Linux-NTFS project.
|
|
*
|
|
* Copyright (c) 2004-2007 Yura Pakhuchiy
|
|
* Copyright (c) 2005 Anton Altaparmakov
|
|
* Copyright (c) 2006 Hil Liao
|
|
*
|
|
* This utility will copy file to an NTFS volume.
|
|
*
|
|
* 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_GETOPT_H
|
|
#include <getopt.h>
|
|
#endif
|
|
#ifdef HAVE_STDLIB_H
|
|
#include <stdlib.h>
|
|
#endif
|
|
#ifdef HAVE_STRING_H
|
|
#include <string.h>
|
|
#endif
|
|
#include <signal.h>
|
|
#ifdef HAVE_SYS_STAT_H
|
|
#include <sys/stat.h>
|
|
#endif
|
|
#ifdef HAVE_UNISTD_H
|
|
#include <unistd.h>
|
|
#endif
|
|
#ifdef HAVE_LIBGEN_H
|
|
#include <libgen.h>
|
|
#endif
|
|
|
|
#include "types.h"
|
|
#include "attrib.h"
|
|
#include "utils.h"
|
|
#include "volume.h"
|
|
#include "dir.h"
|
|
#include "debug.h"
|
|
/* #include "version.h" */
|
|
#include "logging.h"
|
|
|
|
struct options {
|
|
char *device; /* Device/File to work with */
|
|
char *src_file; /* Source file */
|
|
char *dest_file; /* Destination file */
|
|
char *attr_name; /* Write to attribute with this name. */
|
|
int force; /* Override common sense */
|
|
int quiet; /* Less output */
|
|
int verbose; /* Extra output */
|
|
int noaction; /* Do not write to disk */
|
|
ATTR_TYPES attribute; /* Write to this attribute. */
|
|
int inode; /* Treat dest_file as inode number. */
|
|
};
|
|
|
|
static const char *EXEC_NAME = "ntfscp";
|
|
static struct options opts;
|
|
static volatile sig_atomic_t caught_terminate = 0;
|
|
|
|
/**
|
|
* version - Print version information about the program
|
|
*
|
|
* Print a copyright statement and a brief description of the program.
|
|
*
|
|
* Return: none
|
|
*/
|
|
static void version(void)
|
|
{
|
|
ntfs_log_info("\n%s v%s (libntfs-3g) - Copy file to an NTFS "
|
|
"volume.\n\n", EXEC_NAME, VERSION);
|
|
ntfs_log_info("Copyright (c) 2004-2007 Yura Pakhuchiy\n");
|
|
ntfs_log_info("Copyright (c) 2005 Anton Altaparmakov\n");
|
|
ntfs_log_info("Copyright (c) 2006 Hil Liao\n");
|
|
ntfs_log_info("\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)
|
|
{
|
|
ntfs_log_info("\nUsage: %s [options] device src_file dest_file\n\n"
|
|
" -a, --attribute NUM Write to this attribute\n"
|
|
" -i, --inode Treat dest_file as inode number\n"
|
|
" -f, --force Use less caution\n"
|
|
" -h, --help Print this help\n"
|
|
" -N, --attr-name NAME Write to attribute with this name\n"
|
|
" -n, --no-action Do not write to disk\n"
|
|
" -q, --quiet Less output\n"
|
|
" -V, --version Version information\n"
|
|
" -v, --verbose More output\n\n",
|
|
EXEC_NAME);
|
|
ntfs_log_info("%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 = "-a:ifh?N:nqVv";
|
|
static const struct option lopt[] = {
|
|
{ "attribute", required_argument, NULL, 'a' },
|
|
{ "inode", no_argument, NULL, 'i' },
|
|
{ "force", no_argument, NULL, 'f' },
|
|
{ "help", no_argument, NULL, 'h' },
|
|
{ "attr-name", required_argument, NULL, 'N' },
|
|
{ "no-action", no_argument, NULL, 'n' },
|
|
{ "quiet", no_argument, NULL, 'q' },
|
|
{ "version", no_argument, NULL, 'V' },
|
|
{ "verbose", no_argument, NULL, 'v' },
|
|
{ NULL, 0, NULL, 0 }
|
|
};
|
|
|
|
char *s;
|
|
int c = -1;
|
|
int err = 0;
|
|
int ver = 0;
|
|
int help = 0;
|
|
int levels = 0;
|
|
s64 attr;
|
|
|
|
opts.device = NULL;
|
|
opts.src_file = NULL;
|
|
opts.dest_file = NULL;
|
|
opts.attr_name = NULL;
|
|
opts.inode = 0;
|
|
opts.attribute = AT_DATA;
|
|
|
|
opterr = 0; /* We'll handle the errors, thank you. */
|
|
|
|
while ((c = getopt_long(argc, argv, sopt, lopt, NULL)) != -1) {
|
|
switch (c) {
|
|
case 1: /* A non-option argument */
|
|
if (!opts.device) {
|
|
opts.device = argv[optind - 1];
|
|
} else if (!opts.src_file) {
|
|
opts.src_file = argv[optind - 1];
|
|
} else if (!opts.dest_file) {
|
|
opts.dest_file = argv[optind - 1];
|
|
} else {
|
|
ntfs_log_error("You must specify exactly two "
|
|
"files.\n");
|
|
err++;
|
|
}
|
|
break;
|
|
case 'a':
|
|
if (opts.attribute != AT_DATA) {
|
|
ntfs_log_error("You can specify only one "
|
|
"attribute.\n");
|
|
err++;
|
|
break;
|
|
}
|
|
|
|
attr = strtol(optarg, &s, 0);
|
|
if (*s) {
|
|
ntfs_log_error("Couldn't parse attribute.\n");
|
|
err++;
|
|
} else
|
|
opts.attribute = (ATTR_TYPES)cpu_to_le32(attr);
|
|
break;
|
|
case 'i':
|
|
opts.inode++;
|
|
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 'N':
|
|
if (opts.attr_name) {
|
|
ntfs_log_error("You can specify only one "
|
|
"attribute name.\n");
|
|
err++;
|
|
} else
|
|
opts.attr_name = argv[optind - 1];
|
|
break;
|
|
case 'n':
|
|
opts.noaction++;
|
|
break;
|
|
case 'q':
|
|
opts.quiet++;
|
|
ntfs_log_clear_levels(NTFS_LOG_LEVEL_QUIET);
|
|
break;
|
|
case 'V':
|
|
ver++;
|
|
break;
|
|
case 'v':
|
|
opts.verbose++;
|
|
ntfs_log_set_levels(NTFS_LOG_LEVEL_VERBOSE);
|
|
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++;
|
|
|
|
if (help || ver) {
|
|
opts.quiet = 0;
|
|
} else {
|
|
if (!opts.device) {
|
|
ntfs_log_error("You must specify a device.\n");
|
|
err++;
|
|
} else if (!opts.src_file) {
|
|
ntfs_log_error("You must specify a source file.\n");
|
|
err++;
|
|
} else if (!opts.dest_file) {
|
|
ntfs_log_error("You must specify a destination "
|
|
"file.\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);
|
|
}
|
|
|
|
/**
|
|
* signal_handler - Handle SIGINT and SIGTERM: abort write, sync and exit.
|
|
*/
|
|
static void signal_handler(int arg __attribute__((unused)))
|
|
{
|
|
caught_terminate++;
|
|
}
|
|
|
|
/**
|
|
* Create a regular file under the given directory inode
|
|
*
|
|
* It is a wrapper function to ntfs_create(...)
|
|
*
|
|
* Return: the created file inode
|
|
*/
|
|
static ntfs_inode *ntfs_new_file(ntfs_inode *dir_ni,
|
|
const char *filename)
|
|
{
|
|
ntfschar *ufilename;
|
|
/* inode to the file that is being created */
|
|
ntfs_inode *ni;
|
|
int ufilename_len;
|
|
|
|
/* ntfs_mbstoucs(...) will allocate memory for ufilename if it's NULL */
|
|
ufilename = NULL;
|
|
ufilename_len = ntfs_mbstoucs(filename, &ufilename);
|
|
if (ufilename_len == -1) {
|
|
ntfs_log_perror("ERROR: Failed to convert '%s' to unicode",
|
|
filename);
|
|
return NULL;
|
|
}
|
|
ni = ntfs_create(dir_ni, 0, ufilename, ufilename_len, S_IFREG);
|
|
free(ufilename);
|
|
return ni;
|
|
}
|
|
|
|
/**
|
|
* main - Begin here
|
|
*
|
|
* Start from here.
|
|
*
|
|
* Return: 0 Success, the program worked
|
|
* 1 Error, something went wrong
|
|
*/
|
|
int main(int argc, char *argv[])
|
|
{
|
|
FILE *in;
|
|
ntfs_volume *vol;
|
|
ntfs_inode *out;
|
|
ntfs_attr *na;
|
|
int flags = 0;
|
|
int result = 1;
|
|
s64 new_size;
|
|
u64 offset;
|
|
char *buf;
|
|
s64 br, bw;
|
|
ntfschar *attr_name;
|
|
int attr_name_len = 0;
|
|
|
|
ntfs_log_set_handler(ntfs_log_handler_stderr);
|
|
|
|
if (!parse_options(argc, argv))
|
|
return 1;
|
|
|
|
utils_set_locale();
|
|
|
|
/* Set SIGINT handler. */
|
|
if (signal(SIGINT, signal_handler) == SIG_ERR) {
|
|
ntfs_log_perror("Failed to set SIGINT handler");
|
|
return 1;
|
|
}
|
|
/* Set SIGTERM handler. */
|
|
if (signal(SIGTERM, signal_handler) == SIG_ERR) {
|
|
ntfs_log_perror("Failed to set SIGTERM handler");
|
|
return 1;
|
|
}
|
|
|
|
if (opts.noaction)
|
|
flags = NTFS_MNT_RDONLY;
|
|
if (opts.force)
|
|
flags |= NTFS_MNT_RECOVER;
|
|
|
|
vol = utils_mount_volume(opts.device, flags);
|
|
if (!vol) {
|
|
ntfs_log_perror("ERROR: couldn't mount volume");
|
|
return 1;
|
|
}
|
|
|
|
if ((vol->flags & VOLUME_IS_DIRTY) && !opts.force)
|
|
goto umount;
|
|
|
|
NVolSetCompression(vol); /* allow compression */
|
|
if (ntfs_volume_get_free_space(vol)) {
|
|
ntfs_log_perror("ERROR: couldn't get free space");
|
|
goto umount;
|
|
}
|
|
|
|
{
|
|
struct stat fst;
|
|
if (stat(opts.src_file, &fst) == -1) {
|
|
ntfs_log_perror("ERROR: Couldn't stat source file");
|
|
goto umount;
|
|
}
|
|
new_size = fst.st_size;
|
|
}
|
|
ntfs_log_verbose("New file size: %lld\n", (long long)new_size);
|
|
|
|
in = fopen(opts.src_file, "r");
|
|
if (!in) {
|
|
ntfs_log_perror("ERROR: Couldn't open source file");
|
|
goto umount;
|
|
}
|
|
|
|
if (opts.inode) {
|
|
s64 inode_num;
|
|
char *s;
|
|
|
|
inode_num = strtoll(opts.dest_file, &s, 0);
|
|
if (*s) {
|
|
ntfs_log_error("ERROR: Couldn't parse inode number.\n");
|
|
goto close_src;
|
|
}
|
|
out = ntfs_inode_open(vol, inode_num);
|
|
} else
|
|
out = ntfs_pathname_to_inode(vol, NULL, opts.dest_file);
|
|
if (!out) {
|
|
/* Copy the file if the dest_file's parent dir can be opened. */
|
|
char *parent_dirname;
|
|
char *filename;
|
|
ntfs_inode *dir_ni;
|
|
ntfs_inode *ni;
|
|
char *dirname_last_whack;
|
|
|
|
filename = basename(opts.dest_file);
|
|
parent_dirname = strdup(opts.dest_file);
|
|
if (!parent_dirname) {
|
|
ntfs_log_perror("strdup() failed");
|
|
goto close_src;
|
|
}
|
|
dirname_last_whack = strrchr(parent_dirname, '/');
|
|
if (dirname_last_whack) {
|
|
dirname_last_whack[1] = 0;
|
|
dir_ni = ntfs_pathname_to_inode(vol, NULL,
|
|
parent_dirname);
|
|
} else {
|
|
ntfs_log_verbose("Target path does not contain '/'. "
|
|
"Using root directory as parent.\n");
|
|
dir_ni = ntfs_inode_open(vol, FILE_root);
|
|
}
|
|
if (dir_ni) {
|
|
if (!(dir_ni->mrec->flags & MFT_RECORD_IS_DIRECTORY)) {
|
|
/* Remove the last '/' for estetic reasons. */
|
|
dirname_last_whack[0] = 0;
|
|
ntfs_log_error("The file '%s' already exists "
|
|
"and is not a directory. "
|
|
"Aborting.\n", parent_dirname);
|
|
free(parent_dirname);
|
|
ntfs_inode_close(dir_ni);
|
|
goto close_src;
|
|
}
|
|
ntfs_log_verbose("Creating a new file '%s' under '%s'"
|
|
"\n", filename, parent_dirname);
|
|
ni = ntfs_new_file(dir_ni, filename);
|
|
ntfs_inode_close(dir_ni);
|
|
if (!ni) {
|
|
ntfs_log_perror("Failed to create '%s' under "
|
|
"'%s'", filename,
|
|
parent_dirname);
|
|
free(parent_dirname);
|
|
goto close_src;
|
|
}
|
|
out = ni;
|
|
} else {
|
|
ntfs_log_perror("ERROR: Couldn't open '%s'",
|
|
parent_dirname);
|
|
free(parent_dirname);
|
|
goto close_src;
|
|
}
|
|
free(parent_dirname);
|
|
}
|
|
/* The destination is a directory. */
|
|
if ((out->mrec->flags & MFT_RECORD_IS_DIRECTORY) && !opts.inode) {
|
|
char *filename;
|
|
char *overwrite_filename;
|
|
int overwrite_filename_len;
|
|
ntfs_inode *ni;
|
|
ntfs_inode *dir_ni;
|
|
int filename_len;
|
|
int dest_dirname_len;
|
|
|
|
filename = basename(opts.src_file);
|
|
dir_ni = out;
|
|
filename_len = strlen(filename);
|
|
dest_dirname_len = strlen(opts.dest_file);
|
|
overwrite_filename_len = filename_len+dest_dirname_len + 2;
|
|
overwrite_filename = malloc(overwrite_filename_len);
|
|
if (!overwrite_filename) {
|
|
ntfs_log_perror("ERROR: Failed to allocate %i bytes "
|
|
"memory for the overwrite filename",
|
|
overwrite_filename_len);
|
|
ntfs_inode_close(out);
|
|
goto close_src;
|
|
}
|
|
strcpy(overwrite_filename, opts.dest_file);
|
|
if (opts.dest_file[dest_dirname_len - 1] != '/') {
|
|
strcat(overwrite_filename, "/");
|
|
}
|
|
strcat(overwrite_filename, filename);
|
|
ni = ntfs_pathname_to_inode(vol, NULL, overwrite_filename);
|
|
/* Does a file with the same name exist in the dest dir? */
|
|
if (ni) {
|
|
ntfs_log_verbose("Destination path has a file with "
|
|
"the same name\nOverwriting the file "
|
|
"'%s'\n", overwrite_filename);
|
|
ntfs_inode_close(out);
|
|
out = ni;
|
|
} else {
|
|
ntfs_log_verbose("Creating a new file '%s' under "
|
|
"'%s'\n", filename, opts.dest_file);
|
|
ni = ntfs_new_file(dir_ni, filename);
|
|
ntfs_inode_close(dir_ni);
|
|
if (!ni) {
|
|
ntfs_log_perror("ERROR: Failed to create the "
|
|
"destination file under '%s'",
|
|
opts.dest_file);
|
|
free(overwrite_filename);
|
|
goto close_src;
|
|
}
|
|
out = ni;
|
|
}
|
|
free(overwrite_filename);
|
|
}
|
|
|
|
attr_name = ntfs_str2ucs(opts.attr_name, &attr_name_len);
|
|
if (!attr_name) {
|
|
ntfs_log_perror("ERROR: Failed to parse attribute name '%s'",
|
|
opts.attr_name);
|
|
goto close_dst;
|
|
}
|
|
|
|
na = ntfs_attr_open(out, opts.attribute, attr_name, attr_name_len);
|
|
if (!na) {
|
|
if (errno != ENOENT) {
|
|
ntfs_log_perror("ERROR: Couldn't open attribute");
|
|
goto close_dst;
|
|
}
|
|
/* Requested attribute isn't present, add it. */
|
|
if (ntfs_attr_add(out, opts.attribute, attr_name,
|
|
attr_name_len, NULL, 0)) {
|
|
ntfs_log_perror("ERROR: Couldn't add attribute");
|
|
goto close_dst;
|
|
}
|
|
na = ntfs_attr_open(out, opts.attribute, attr_name,
|
|
attr_name_len);
|
|
if (!na) {
|
|
ntfs_log_perror("ERROR: Couldn't open just added "
|
|
"attribute");
|
|
goto close_dst;
|
|
}
|
|
}
|
|
ntfs_ucsfree(attr_name);
|
|
|
|
ntfs_log_verbose("Old file size: %lld\n", (long long)na->data_size);
|
|
if (na->data_size != new_size) {
|
|
if (ntfs_attr_truncate_solid(na, new_size)) {
|
|
ntfs_log_perror("ERROR: Couldn't resize attribute");
|
|
goto close_attr;
|
|
}
|
|
}
|
|
|
|
buf = malloc(NTFS_BUF_SIZE);
|
|
if (!buf) {
|
|
ntfs_log_perror("ERROR: malloc failed");
|
|
goto close_attr;
|
|
}
|
|
|
|
ntfs_log_verbose("Starting write.\n");
|
|
offset = 0;
|
|
while (!feof(in)) {
|
|
if (caught_terminate) {
|
|
ntfs_log_error("SIGTERM or SIGINT received. "
|
|
"Aborting write.\n");
|
|
break;
|
|
}
|
|
br = fread(buf, 1, NTFS_BUF_SIZE, in);
|
|
if (!br) {
|
|
if (!feof(in)) ntfs_log_perror("ERROR: fread failed");
|
|
break;
|
|
}
|
|
bw = ntfs_attr_pwrite(na, offset, br, buf);
|
|
if (bw != br) {
|
|
ntfs_log_perror("ERROR: ntfs_attr_pwrite failed");
|
|
break;
|
|
}
|
|
offset += bw;
|
|
}
|
|
if ((na->data_flags & ATTR_COMPRESSION_MASK)
|
|
&& ntfs_attr_pclose(na))
|
|
ntfs_log_perror("ERROR: ntfs_attr_pclose failed");
|
|
ntfs_log_verbose("Syncing.\n");
|
|
result = 0;
|
|
free(buf);
|
|
close_attr:
|
|
ntfs_attr_close(na);
|
|
close_dst:
|
|
while (ntfs_inode_close(out)) {
|
|
if (errno != EBUSY) {
|
|
ntfs_log_error("Sync failed. Run chkdsk.\n");
|
|
break;
|
|
}
|
|
ntfs_log_error("Device busy. Will retry sync in 3 seconds.\n");
|
|
sleep(3);
|
|
}
|
|
close_src:
|
|
fclose(in);
|
|
umount:
|
|
ntfs_umount(vol, FALSE);
|
|
ntfs_log_verbose("Done.\n");
|
|
return result;
|
|
}
|