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 | |
60 | struct 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 | |
73 | static const char *EXEC_NAME = "ntfscp"; |
74 | static struct options opts; |
75 | static 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 | */ |
84 | static 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 | */ |
101 | static 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 | */ |
126 | static 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 | */ |
274 | static 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 | */ |
286 | static 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 | */ |
315 | int 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); |
573 | close_attr: |
574 | ntfs_attr_close(na); |
575 | close_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 | } |
584 | close_src: |
585 | fclose(in); |
586 | umount: |
587 | ntfs_umount(vol, FALSE); |
588 | ntfs_log_verbose("Done.\n"); |
589 | return result; |
590 | } |
591 |