ntfscp.c revision 9663:ace9a2ac3683
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 %s) - Copy file to an NTFS " 87 "volume.\n\n", EXEC_NAME, VERSION, ntfs_libntfs_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, 0); 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, 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_FORCE; 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 (NVolWasDirty(vol) && !opts.force) 360 goto umount; 361 362 { 363 struct stat fst; 364 if (stat(opts.src_file, &fst) == -1) { 365 ntfs_log_perror("ERROR: Couldn't stat source file"); 366 goto umount; 367 } 368 new_size = fst.st_size; 369 } 370 ntfs_log_verbose("New file size: %lld\n", new_size); 371 372 in = fopen(opts.src_file, "r"); 373 if (!in) { 374 ntfs_log_perror("ERROR: Couldn't open source file"); 375 goto umount; 376 } 377 378 if (opts.inode) { 379 s64 inode_num; 380 char *s; 381 382 inode_num = strtoll(opts.dest_file, &s, 0); 383 if (*s) { 384 ntfs_log_error("ERROR: Couldn't parse inode number.\n"); 385 goto close_src; 386 } 387 out = ntfs_inode_open(vol, inode_num); 388 } else 389 out = ntfs_pathname_to_inode(vol, NULL, opts.dest_file); 390 if (!out) { 391 /* Copy the file if the dest_file's parent dir can be opened. */ 392 char *parent_dirname; 393 char *filename; 394 ntfs_inode *dir_ni; 395 ntfs_inode *ni; 396 int dest_path_len; 397 char *dirname_last_whack; 398 399 filename = basename(opts.dest_file); 400 dest_path_len = strlen(opts.dest_file); 401 parent_dirname = strdup(opts.dest_file); 402 if (!parent_dirname) { 403 ntfs_log_perror("strdup() failed"); 404 goto close_src; 405 } 406 dirname_last_whack = strrchr(parent_dirname, '/'); 407 if (dirname_last_whack) { 408 dirname_last_whack[1] = 0; 409 dir_ni = ntfs_pathname_to_inode(vol, NULL, 410 parent_dirname); 411 } else { 412 ntfs_log_verbose("Target path does not contain '/'. " 413 "Using root directory as parent.\n"); 414 dir_ni = ntfs_inode_open(vol, FILE_root); 415 } 416 if (dir_ni) { 417 if (!(dir_ni->mrec->flags & MFT_RECORD_IS_DIRECTORY)) { 418 /* Remove the last '/' for estetic reasons. */ 419 dirname_last_whack[0] = 0; 420 ntfs_log_error("The file '%s' already exists " 421 "and is not a directory. " 422 "Aborting.\n", parent_dirname); 423 free(parent_dirname); 424 ntfs_inode_close(dir_ni); 425 goto close_src; 426 } 427 ntfs_log_verbose("Creating a new file '%s' under '%s'" 428 "\n", filename, parent_dirname); 429 ni = ntfs_new_file(dir_ni, filename); 430 ntfs_inode_close(dir_ni); 431 if (!ni) { 432 ntfs_log_perror("Failed to create '%s' under " 433 "'%s'", filename, 434 parent_dirname); 435 free(parent_dirname); 436 goto close_src; 437 } 438 out = ni; 439 } else { 440 ntfs_log_perror("ERROR: Couldn't open '%s'", 441 parent_dirname); 442 free(parent_dirname); 443 goto close_src; 444 } 445 free(parent_dirname); 446 } 447 /* The destination is a directory. */ 448 if ((out->mrec->flags & MFT_RECORD_IS_DIRECTORY) && !opts.inode) { 449 char *filename; 450 char *overwrite_filename; 451 int overwrite_filename_len; 452 ntfs_inode *ni; 453 ntfs_inode *dir_ni; 454 int filename_len; 455 int dest_dirname_len; 456 457 filename = basename(opts.src_file); 458 dir_ni = out; 459 filename_len = strlen(filename); 460 dest_dirname_len = strlen(opts.dest_file); 461 overwrite_filename_len = filename_len+dest_dirname_len + 2; 462 overwrite_filename = malloc(overwrite_filename_len); 463 if (!overwrite_filename) { 464 ntfs_log_perror("ERROR: Failed to allocate %i bytes " 465 "memory for the overwrite filename", 466 overwrite_filename_len); 467 ntfs_inode_close(out); 468 goto close_src; 469 } 470 strcpy(overwrite_filename, opts.dest_file); 471 if (opts.dest_file[dest_dirname_len - 1] != '/') { 472 strcat(overwrite_filename, "/"); 473 } 474 strcat(overwrite_filename, filename); 475 ni = ntfs_pathname_to_inode(vol, NULL, overwrite_filename); 476 /* Does a file with the same name exist in the dest dir? */ 477 if (ni) { 478 ntfs_log_verbose("Destination path has a file with " 479 "the same name\nOverwriting the file " 480 "'%s'\n", overwrite_filename); 481 ntfs_inode_close(out); 482 out = ni; 483 } else { 484 ntfs_log_verbose("Creating a new file '%s' under " 485 "'%s'\n", filename, opts.dest_file); 486 ni = ntfs_new_file(dir_ni, filename); 487 ntfs_inode_close(dir_ni); 488 if (!ni) { 489 ntfs_log_perror("ERROR: Failed to create the " 490 "destination file under '%s'", 491 opts.dest_file); 492 free(overwrite_filename); 493 goto close_src; 494 } 495 out = ni; 496 } 497 free(overwrite_filename); 498 } 499 500 attr_name = ntfs_str2ucs(opts.attr_name, &attr_name_len); 501 if (!attr_name) { 502 ntfs_log_perror("ERROR: Failed to parse attribute name '%s'", 503 opts.attr_name); 504 goto close_dst; 505 } 506 507 na = ntfs_attr_open(out, opts.attribute, attr_name, attr_name_len); 508 if (!na) { 509 if (errno != ENOENT) { 510 ntfs_log_perror("ERROR: Couldn't open attribute"); 511 goto close_dst; 512 } 513 /* Requested attribute isn't present, add it. */ 514 if (ntfs_attr_add(out, opts.attribute, attr_name, 515 attr_name_len, NULL, 0)) { 516 ntfs_log_perror("ERROR: Couldn't add attribute"); 517 goto close_dst; 518 } 519 na = ntfs_attr_open(out, opts.attribute, attr_name, 520 attr_name_len); 521 if (!na) { 522 ntfs_log_perror("ERROR: Couldn't open just added " 523 "attribute"); 524 goto close_dst; 525 } 526 } 527 ntfs_ucsfree(attr_name); 528 529 ntfs_log_verbose("Old file size: %lld\n", na->data_size); 530 if (na->data_size != new_size) { 531 if (__ntfs_attr_truncate(na, new_size, FALSE)) { 532 ntfs_log_perror("ERROR: Couldn't resize attribute"); 533 goto close_attr; 534 } 535 } 536 537 buf = malloc(NTFS_BUF_SIZE); 538 if (!buf) { 539 ntfs_log_perror("ERROR: malloc failed"); 540 goto close_attr; 541 } 542 543 ntfs_log_verbose("Starting write.\n"); 544 offset = 0; 545 while (!feof(in)) { 546 if (caught_terminate) { 547 ntfs_log_error("SIGTERM or SIGINT received. " 548 "Aborting write.\n"); 549 break; 550 } 551 br = fread(buf, 1, NTFS_BUF_SIZE, in); 552 if (!br) { 553 if (!feof(in)) ntfs_log_perror("ERROR: fread failed"); 554 break; 555 } 556 bw = ntfs_attr_pwrite(na, offset, br, buf); 557 if (bw != br) { 558 ntfs_log_perror("ERROR: ntfs_attr_pwrite failed"); 559 break; 560 } 561 offset += bw; 562 } 563 ntfs_log_verbose("Syncing.\n"); 564 result = 0; 565 free(buf); 566close_attr: 567 ntfs_attr_close(na); 568close_dst: 569 while (ntfs_inode_close(out)) { 570 if (errno != EBUSY) { 571 ntfs_log_error("Sync failed. Run chkdsk.\n"); 572 break; 573 } 574 ntfs_log_error("Device busy. Will retry sync in 3 seconds.\n"); 575 sleep(3); 576 } 577close_src: 578 fclose(in); 579umount: 580 ntfs_umount(vol, FALSE); 581 ntfs_log_verbose("Done.\n"); 582 return result; 583} 584