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