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 }