summaryrefslogtreecommitdiff
path: root/ntfsprogs/ntfscp.c (plain)
blob: 76f2eb42be3c54f318b6d45d136118f04a9d8da5
1/**
2 * ntfscp - Part of the Linux-NTFS project.
3 *
4 * Copyright (c) 2004-2007 Yura Pakhuchiy
5 * Copyright (c) 2005 Anton Altaparmakov
6 * Copyright (c) 2006 Hil Liao
7 *
8 * This utility will copy file to an NTFS volume.
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program (in the main directory of the Linux-NTFS
22 * distribution in the file COPYING); if not, write to the Free Software
23 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 */
25
26#include "config.h"
27
28#ifdef HAVE_STDIO_H
29#include <stdio.h>
30#endif
31#ifdef HAVE_GETOPT_H
32#include <getopt.h>
33#endif
34#ifdef HAVE_STDLIB_H
35#include <stdlib.h>
36#endif
37#ifdef HAVE_STRING_H
38#include <string.h>
39#endif
40#include <signal.h>
41#ifdef HAVE_SYS_STAT_H
42#include <sys/stat.h>
43#endif
44#ifdef HAVE_UNISTD_H
45#include <unistd.h>
46#endif
47#ifdef HAVE_LIBGEN_H
48#include <libgen.h>
49#endif
50
51#include "types.h"
52#include "attrib.h"
53#include "utils.h"
54#include "volume.h"
55#include "dir.h"
56#include "debug.h"
57/* #include "version.h" */
58#include "logging.h"
59
60struct options {
61 char *device; /* Device/File to work with */
62 char *src_file; /* Source file */
63 char *dest_file; /* Destination file */
64 char *attr_name; /* Write to attribute with this name. */
65 int force; /* Override common sense */
66 int quiet; /* Less output */
67 int verbose; /* Extra output */
68 int noaction; /* Do not write to disk */
69 ATTR_TYPES attribute; /* Write to this attribute. */
70 int inode; /* Treat dest_file as inode number. */
71};
72
73static const char *EXEC_NAME = "ntfscp";
74static struct options opts;
75static volatile sig_atomic_t caught_terminate = 0;
76
77/**
78 * version - Print version information about the program
79 *
80 * Print a copyright statement and a brief description of the program.
81 *
82 * Return: none
83 */
84static void version(void)
85{
86 ntfs_log_info("\n%s v%s (libntfs-3g) - Copy file to an NTFS "
87 "volume.\n\n", EXEC_NAME, VERSION);
88 ntfs_log_info("Copyright (c) 2004-2007 Yura Pakhuchiy\n");
89 ntfs_log_info("Copyright (c) 2005 Anton Altaparmakov\n");
90 ntfs_log_info("Copyright (c) 2006 Hil Liao\n");
91 ntfs_log_info("\n%s\n%s%s\n", ntfs_gpl, ntfs_bugs, ntfs_home);
92}
93
94/**
95 * usage - Print a list of the parameters to the program
96 *
97 * Print a list of the parameters and options for the program.
98 *
99 * Return: none
100 */
101static void usage(void)
102{
103 ntfs_log_info("\nUsage: %s [options] device src_file dest_file\n\n"
104 " -a, --attribute NUM Write to this attribute\n"
105 " -i, --inode Treat dest_file as inode number\n"
106 " -f, --force Use less caution\n"
107 " -h, --help Print this help\n"
108 " -N, --attr-name NAME Write to attribute with this name\n"
109 " -n, --no-action Do not write to disk\n"
110 " -q, --quiet Less output\n"
111 " -V, --version Version information\n"
112 " -v, --verbose More output\n\n",
113 EXEC_NAME);
114 ntfs_log_info("%s%s\n", ntfs_bugs, ntfs_home);
115}
116
117/**
118 * parse_options - Read and validate the programs command line
119 *
120 * Read the command line, verify the syntax and parse the options.
121 * This function is very long, but quite simple.
122 *
123 * Return: 1 Success
124 * 0 Error, one or more problems
125 */
126static int parse_options(int argc, char **argv)
127{
128 static const char *sopt = "-a:ifh?N:nqVv";
129 static const struct option lopt[] = {
130 { "attribute", required_argument, NULL, 'a' },
131 { "inode", no_argument, NULL, 'i' },
132 { "force", no_argument, NULL, 'f' },
133 { "help", no_argument, NULL, 'h' },
134 { "attr-name", required_argument, NULL, 'N' },
135 { "no-action", no_argument, NULL, 'n' },
136 { "quiet", no_argument, NULL, 'q' },
137 { "version", no_argument, NULL, 'V' },
138 { "verbose", no_argument, NULL, 'v' },
139 { NULL, 0, NULL, 0 }
140 };
141
142 char *s;
143 int c = -1;
144 int err = 0;
145 int ver = 0;
146 int help = 0;
147 int levels = 0;
148 s64 attr;
149
150 opts.device = NULL;
151 opts.src_file = NULL;
152 opts.dest_file = NULL;
153 opts.attr_name = NULL;
154 opts.inode = 0;
155 opts.attribute = AT_DATA;
156
157 opterr = 0; /* We'll handle the errors, thank you. */
158
159 while ((c = getopt_long(argc, argv, sopt, lopt, NULL)) != -1) {
160 switch (c) {
161 case 1: /* A non-option argument */
162 if (!opts.device) {
163 opts.device = argv[optind - 1];
164 } else if (!opts.src_file) {
165 opts.src_file = argv[optind - 1];
166 } else if (!opts.dest_file) {
167 opts.dest_file = argv[optind - 1];
168 } else {
169 ntfs_log_error("You must specify exactly two "
170 "files.\n");
171 err++;
172 }
173 break;
174 case 'a':
175 if (opts.attribute != AT_DATA) {
176 ntfs_log_error("You can specify only one "
177 "attribute.\n");
178 err++;
179 break;
180 }
181
182 attr = strtol(optarg, &s, 0);
183 if (*s) {
184 ntfs_log_error("Couldn't parse attribute.\n");
185 err++;
186 } else
187 opts.attribute = (ATTR_TYPES)cpu_to_le32(attr);
188 break;
189 case 'i':
190 opts.inode++;
191 break;
192 case 'f':
193 opts.force++;
194 break;
195 case 'h':
196 case '?':
197 if (strncmp(argv[optind - 1], "--log-", 6) == 0) {
198 if (!ntfs_log_parse_option(argv[optind - 1]))
199 err++;
200 break;
201 }
202 help++;
203 break;
204 case 'N':
205 if (opts.attr_name) {
206 ntfs_log_error("You can specify only one "
207 "attribute name.\n");
208 err++;
209 } else
210 opts.attr_name = argv[optind - 1];
211 break;
212 case 'n':
213 opts.noaction++;
214 break;
215 case 'q':
216 opts.quiet++;
217 ntfs_log_clear_levels(NTFS_LOG_LEVEL_QUIET);
218 break;
219 case 'V':
220 ver++;
221 break;
222 case 'v':
223 opts.verbose++;
224 ntfs_log_set_levels(NTFS_LOG_LEVEL_VERBOSE);
225 break;
226 default:
227 ntfs_log_error("Unknown option '%s'.\n",
228 argv[optind - 1]);
229 err++;
230 break;
231 }
232 }
233
234 /* Make sure we're in sync with the log levels */
235 levels = ntfs_log_get_levels();
236 if (levels & NTFS_LOG_LEVEL_VERBOSE)
237 opts.verbose++;
238 if (!(levels & NTFS_LOG_LEVEL_QUIET))
239 opts.quiet++;
240
241 if (help || ver) {
242 opts.quiet = 0;
243 } else {
244 if (!opts.device) {
245 ntfs_log_error("You must specify a device.\n");
246 err++;
247 } else if (!opts.src_file) {
248 ntfs_log_error("You must specify a source file.\n");
249 err++;
250 } else if (!opts.dest_file) {
251 ntfs_log_error("You must specify a destination "
252 "file.\n");
253 err++;
254 }
255
256 if (opts.quiet && opts.verbose) {
257 ntfs_log_error("You may not use --quiet and --verbose "
258 "at the same time.\n");
259 err++;
260 }
261 }
262
263 if (ver)
264 version();
265 if (help || err)
266 usage();
267
268 return (!err && !help && !ver);
269}
270
271/**
272 * signal_handler - Handle SIGINT and SIGTERM: abort write, sync and exit.
273 */
274static void signal_handler(int arg __attribute__((unused)))
275{
276 caught_terminate++;
277}
278
279/**
280 * Create a regular file under the given directory inode
281 *
282 * It is a wrapper function to ntfs_create(...)
283 *
284 * Return: the created file inode
285 */
286static ntfs_inode *ntfs_new_file(ntfs_inode *dir_ni,
287 const char *filename)
288{
289 ntfschar *ufilename;
290 /* inode to the file that is being created */
291 ntfs_inode *ni;
292 int ufilename_len;
293
294 /* ntfs_mbstoucs(...) will allocate memory for ufilename if it's NULL */
295 ufilename = NULL;
296 ufilename_len = ntfs_mbstoucs(filename, &ufilename);
297 if (ufilename_len == -1) {
298 ntfs_log_perror("ERROR: Failed to convert '%s' to unicode",
299 filename);
300 return NULL;
301 }
302 ni = ntfs_create(dir_ni, 0, ufilename, ufilename_len, S_IFREG);
303 free(ufilename);
304 return ni;
305}
306
307/**
308 * main - Begin here
309 *
310 * Start from here.
311 *
312 * Return: 0 Success, the program worked
313 * 1 Error, something went wrong
314 */
315int main(int argc, char *argv[])
316{
317 FILE *in;
318 ntfs_volume *vol;
319 ntfs_inode *out;
320 ntfs_attr *na;
321 int flags = 0;
322 int result = 1;
323 s64 new_size;
324 u64 offset;
325 char *buf;
326 s64 br, bw;
327 ntfschar *attr_name;
328 int attr_name_len = 0;
329
330 ntfs_log_set_handler(ntfs_log_handler_stderr);
331
332 if (!parse_options(argc, argv))
333 return 1;
334
335 utils_set_locale();
336
337 /* Set SIGINT handler. */
338 if (signal(SIGINT, signal_handler) == SIG_ERR) {
339 ntfs_log_perror("Failed to set SIGINT handler");
340 return 1;
341 }
342 /* Set SIGTERM handler. */
343 if (signal(SIGTERM, signal_handler) == SIG_ERR) {
344 ntfs_log_perror("Failed to set SIGTERM handler");
345 return 1;
346 }
347
348 if (opts.noaction)
349 flags = NTFS_MNT_RDONLY;
350 if (opts.force)
351 flags |= NTFS_MNT_RECOVER;
352
353 vol = utils_mount_volume(opts.device, flags);
354 if (!vol) {
355 ntfs_log_perror("ERROR: couldn't mount volume");
356 return 1;
357 }
358
359 if ((vol->flags & VOLUME_IS_DIRTY) && !opts.force)
360 goto umount;
361
362 NVolSetCompression(vol); /* allow compression */
363 if (ntfs_volume_get_free_space(vol)) {
364 ntfs_log_perror("ERROR: couldn't get free space");
365 goto umount;
366 }
367
368 {
369 struct stat fst;
370 if (stat(opts.src_file, &fst) == -1) {
371 ntfs_log_perror("ERROR: Couldn't stat source file");
372 goto umount;
373 }
374 new_size = fst.st_size;
375 }
376 ntfs_log_verbose("New file size: %lld\n", (long long)new_size);
377
378 in = fopen(opts.src_file, "r");
379 if (!in) {
380 ntfs_log_perror("ERROR: Couldn't open source file");
381 goto umount;
382 }
383
384 if (opts.inode) {
385 s64 inode_num;
386 char *s;
387
388 inode_num = strtoll(opts.dest_file, &s, 0);
389 if (*s) {
390 ntfs_log_error("ERROR: Couldn't parse inode number.\n");
391 goto close_src;
392 }
393 out = ntfs_inode_open(vol, inode_num);
394 } else
395 out = ntfs_pathname_to_inode(vol, NULL, opts.dest_file);
396 if (!out) {
397 /* Copy the file if the dest_file's parent dir can be opened. */
398 char *parent_dirname;
399 char *filename;
400 ntfs_inode *dir_ni;
401 ntfs_inode *ni;
402 char *dirname_last_whack;
403
404 filename = basename(opts.dest_file);
405 parent_dirname = strdup(opts.dest_file);
406 if (!parent_dirname) {
407 ntfs_log_perror("strdup() failed");
408 goto close_src;
409 }
410 dirname_last_whack = strrchr(parent_dirname, '/');
411 if (dirname_last_whack) {
412 dirname_last_whack[1] = 0;
413 dir_ni = ntfs_pathname_to_inode(vol, NULL,
414 parent_dirname);
415 } else {
416 ntfs_log_verbose("Target path does not contain '/'. "
417 "Using root directory as parent.\n");
418 dir_ni = ntfs_inode_open(vol, FILE_root);
419 }
420 if (dir_ni) {
421 if (!(dir_ni->mrec->flags & MFT_RECORD_IS_DIRECTORY)) {
422 /* Remove the last '/' for estetic reasons. */
423 dirname_last_whack[0] = 0;
424 ntfs_log_error("The file '%s' already exists "
425 "and is not a directory. "
426 "Aborting.\n", parent_dirname);
427 free(parent_dirname);
428 ntfs_inode_close(dir_ni);
429 goto close_src;
430 }
431 ntfs_log_verbose("Creating a new file '%s' under '%s'"
432 "\n", filename, parent_dirname);
433 ni = ntfs_new_file(dir_ni, filename);
434 ntfs_inode_close(dir_ni);
435 if (!ni) {
436 ntfs_log_perror("Failed to create '%s' under "
437 "'%s'", filename,
438 parent_dirname);
439 free(parent_dirname);
440 goto close_src;
441 }
442 out = ni;
443 } else {
444 ntfs_log_perror("ERROR: Couldn't open '%s'",
445 parent_dirname);
446 free(parent_dirname);
447 goto close_src;
448 }
449 free(parent_dirname);
450 }
451 /* The destination is a directory. */
452 if ((out->mrec->flags & MFT_RECORD_IS_DIRECTORY) && !opts.inode) {
453 char *filename;
454 char *overwrite_filename;
455 int overwrite_filename_len;
456 ntfs_inode *ni;
457 ntfs_inode *dir_ni;
458 int filename_len;
459 int dest_dirname_len;
460
461 filename = basename(opts.src_file);
462 dir_ni = out;
463 filename_len = strlen(filename);
464 dest_dirname_len = strlen(opts.dest_file);
465 overwrite_filename_len = filename_len+dest_dirname_len + 2;
466 overwrite_filename = malloc(overwrite_filename_len);
467 if (!overwrite_filename) {
468 ntfs_log_perror("ERROR: Failed to allocate %i bytes "
469 "memory for the overwrite filename",
470 overwrite_filename_len);
471 ntfs_inode_close(out);
472 goto close_src;
473 }
474 strcpy(overwrite_filename, opts.dest_file);
475 if (opts.dest_file[dest_dirname_len - 1] != '/') {
476 strcat(overwrite_filename, "/");
477 }
478 strcat(overwrite_filename, filename);
479 ni = ntfs_pathname_to_inode(vol, NULL, overwrite_filename);
480 /* Does a file with the same name exist in the dest dir? */
481 if (ni) {
482 ntfs_log_verbose("Destination path has a file with "
483 "the same name\nOverwriting the file "
484 "'%s'\n", overwrite_filename);
485 ntfs_inode_close(out);
486 out = ni;
487 } else {
488 ntfs_log_verbose("Creating a new file '%s' under "
489 "'%s'\n", filename, opts.dest_file);
490 ni = ntfs_new_file(dir_ni, filename);
491 ntfs_inode_close(dir_ni);
492 if (!ni) {
493 ntfs_log_perror("ERROR: Failed to create the "
494 "destination file under '%s'",
495 opts.dest_file);
496 free(overwrite_filename);
497 goto close_src;
498 }
499 out = ni;
500 }
501 free(overwrite_filename);
502 }
503
504 attr_name = ntfs_str2ucs(opts.attr_name, &attr_name_len);
505 if (!attr_name) {
506 ntfs_log_perror("ERROR: Failed to parse attribute name '%s'",
507 opts.attr_name);
508 goto close_dst;
509 }
510
511 na = ntfs_attr_open(out, opts.attribute, attr_name, attr_name_len);
512 if (!na) {
513 if (errno != ENOENT) {
514 ntfs_log_perror("ERROR: Couldn't open attribute");
515 goto close_dst;
516 }
517 /* Requested attribute isn't present, add it. */
518 if (ntfs_attr_add(out, opts.attribute, attr_name,
519 attr_name_len, NULL, 0)) {
520 ntfs_log_perror("ERROR: Couldn't add attribute");
521 goto close_dst;
522 }
523 na = ntfs_attr_open(out, opts.attribute, attr_name,
524 attr_name_len);
525 if (!na) {
526 ntfs_log_perror("ERROR: Couldn't open just added "
527 "attribute");
528 goto close_dst;
529 }
530 }
531 ntfs_ucsfree(attr_name);
532
533 ntfs_log_verbose("Old file size: %lld\n", (long long)na->data_size);
534 if (na->data_size != new_size) {
535 if (ntfs_attr_truncate_solid(na, new_size)) {
536 ntfs_log_perror("ERROR: Couldn't resize attribute");
537 goto close_attr;
538 }
539 }
540
541 buf = malloc(NTFS_BUF_SIZE);
542 if (!buf) {
543 ntfs_log_perror("ERROR: malloc failed");
544 goto close_attr;
545 }
546
547 ntfs_log_verbose("Starting write.\n");
548 offset = 0;
549 while (!feof(in)) {
550 if (caught_terminate) {
551 ntfs_log_error("SIGTERM or SIGINT received. "
552 "Aborting write.\n");
553 break;
554 }
555 br = fread(buf, 1, NTFS_BUF_SIZE, in);
556 if (!br) {
557 if (!feof(in)) ntfs_log_perror("ERROR: fread failed");
558 break;
559 }
560 bw = ntfs_attr_pwrite(na, offset, br, buf);
561 if (bw != br) {
562 ntfs_log_perror("ERROR: ntfs_attr_pwrite failed");
563 break;
564 }
565 offset += bw;
566 }
567 if ((na->data_flags & ATTR_COMPRESSION_MASK)
568 && ntfs_attr_pclose(na))
569 ntfs_log_perror("ERROR: ntfs_attr_pclose failed");
570 ntfs_log_verbose("Syncing.\n");
571 result = 0;
572 free(buf);
573close_attr:
574 ntfs_attr_close(na);
575close_dst:
576 while (ntfs_inode_close(out)) {
577 if (errno != EBUSY) {
578 ntfs_log_error("Sync failed. Run chkdsk.\n");
579 break;
580 }
581 ntfs_log_error("Device busy. Will retry sync in 3 seconds.\n");
582 sleep(3);
583 }
584close_src:
585 fclose(in);
586umount:
587 ntfs_umount(vol, FALSE);
588 ntfs_log_verbose("Done.\n");
589 return result;
590}
591