xref: /onnv-gate/usr/src/cmd/ntfsprogs/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 
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 %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  */
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, 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  */
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_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);
566 close_attr:
567 	ntfs_attr_close(na);
568 close_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 	}
577 close_src:
578 	fclose(in);
579 umount:
580 	ntfs_umount(vol, FALSE);
581 	ntfs_log_verbose("Done.\n");
582 	return result;
583 }
584