1 /*
2    CIFSDD - dd for SMB.
3    Main program, argument handling and block copying.
4 
5    Copyright (C) James Peach 2005-2006
6 
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11 
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16 
17    You should have received a copy of the GNU General Public License
18    along with this program; if not, write to the Free Software
19    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20 */
21 
22 #include "includes.h"
23 #include "system/filesys.h"
24 #include "auth/gensec/gensec.h"
25 #include "lib/cmdline/popt_common.h"
26 
27 #include "cifsdd.h"
28 
29 const char * const PROGNAME = "cifsdd";
30 
31 #define SYNTAX_EXIT_CODE	 1	/* Invokation syntax or logic error. */
32 #define EOM_EXIT_CODE		 9	/* Out of memory error. */
33 #define FILESYS_EXIT_CODE	10	/* File manipulation error. */
34 #define IOERROR_EXIT_CODE	11	/* Error during IO phase. */
35 
36 struct	dd_stats_record dd_stats;
37 
38 static int dd_sigint;
39 static int dd_sigusr1;
40 
dd_handle_signal(int sig)41 static void dd_handle_signal(int sig)
42 {
43 	switch (sig)
44 	{
45 		case SIGINT:
46 			++dd_sigint;
47 			break;
48 		case SIGUSR1:
49 			++dd_sigusr1;
50 			break;
51 		default:
52 			break;
53 	}
54 }
55 
56 /* ------------------------------------------------------------------------- */
57 /* Argument handling.							     */
58 /* ------------------------------------------------------------------------- */
59 
argtype_str(enum argtype arg_type)60 static const char * argtype_str(enum argtype arg_type)
61 {
62 	static const struct {
63 		enum argtype arg_type;
64 		const char * arg_name;
65 	} names [] =
66 	{
67 		{ ARG_NUMERIC, "COUNT" },
68 		{ ARG_SIZE, "SIZE" },
69 		{ ARG_PATHNAME, "FILE" },
70 		{ ARG_BOOL, "BOOLEAN" },
71 	};
72 
73 	int i;
74 
75 	for (i = 0; i < ARRAY_SIZE(names); ++i) {
76 		if (arg_type == names[i].arg_type) {
77 			return(names[i].arg_name);
78 		}
79 	}
80 
81 	return("<unknown>");
82 }
83 
84 static struct argdef args[] =
85 {
86 	{ "bs",	ARG_SIZE,	"force ibs and obs to SIZE bytes" },
87 	{ "ibs", ARG_SIZE,	"read SIZE bytes at a time" },
88 	{ "obs", ARG_SIZE,	"write SIZE bytes at a time" },
89 
90 	{ "count", ARG_NUMERIC,	"copy COUNT input blocks" },
91 	{ "seek",ARG_NUMERIC,	"skip COUNT blocks at start of output" },
92 	{ "skip",ARG_NUMERIC,	"skip COUNT blocks at start of input" },
93 
94 	{ "if",	ARG_PATHNAME,	"read input from FILE" },
95 	{ "of",	ARG_PATHNAME,	"write output to FILE" },
96 
97 	{ "direct", ARG_BOOL,	"use direct I/O if non-zero" },
98 	{ "sync", ARG_BOOL,	"use synchronous writes if non-zero" },
99 	{ "oplock", ARG_BOOL,	"take oplocks on the input and output files" },
100 
101 /* FIXME: We should support using iflags and oflags for setting oplock and I/O
102  * options. This would make us compatible with GNU dd.
103  */
104 };
105 
find_named_arg(const char * arg)106 struct argdef * find_named_arg(const char * arg)
107 {
108 	int i;
109 
110 	for (i = 0; i < ARRAY_SIZE(args); ++i) {
111 		if (strwicmp(arg, args[i].arg_name) == 0) {
112 			return(&args[i]);
113 		}
114 	}
115 
116 	return(NULL);
117 }
118 
set_arg_argv(const char * argv)119 int set_arg_argv(const char * argv)
120 {
121 	struct argdef *	arg;
122 
123 	char *	name;
124 	char *	val;
125 
126 	if ((name = strdup(argv)) == NULL) {
127 		return(0);
128 	}
129 
130 	if ((val = strchr(name, '=')) == NULL) {
131 		fprintf(stderr, "%s: malformed argument \"%s\"\n",
132 				PROGNAME, argv);
133 		goto fail;
134 	}
135 
136 	*val = '\0';
137 	val++;
138 
139 	if ((arg = find_named_arg(name)) == NULL) {
140 		fprintf(stderr, "%s: ignoring unknown argument \"%s\"\n",
141 				PROGNAME, name);
142 		goto fail;
143 	}
144 
145 	/* Found a matching name; convert the variable argument. */
146 	switch (arg->arg_type) {
147 		case ARG_NUMERIC:
148 			if (!conv_str_u64(val, &arg->arg_val.nval)) {
149 				goto fail;
150 			}
151 			break;
152 		case ARG_SIZE:
153 			if (!conv_str_size(val, &arg->arg_val.nval)) {
154 				goto fail;
155 			}
156 			break;
157 		case ARG_BOOL:
158 			if (!conv_str_bool(val, &arg->arg_val.bval)) {
159 				goto fail;
160 			}
161 			break;
162 		case ARG_PATHNAME:
163 			if (!(arg->arg_val.pval = strdup(val))) {
164 				goto fail;
165 			}
166 			break;
167 		default:
168 			fprintf(stderr, "%s: argument \"%s\" is of "
169 				"unknown type\n", PROGNAME, name);
170 			goto fail;
171 	}
172 
173 	free(name);
174 	return(1);
175 
176 fail:
177 	free(name);
178 	return(0);
179 }
180 
set_arg_val(const char * name,...)181 void set_arg_val(const char * name, ...)
182 {
183 	va_list		ap;
184 	struct argdef * arg;
185 
186 	va_start(ap, name);
187 	if ((arg = find_named_arg(name)) == NULL) {
188 		goto fail;
189 	}
190 
191 	/* Found a matching name; convert the variable argument. */
192 	switch (arg->arg_type) {
193 		case ARG_NUMERIC:
194 		case ARG_SIZE:
195 			arg->arg_val.nval = va_arg(ap, uint64_t);
196 			break;
197 		case ARG_BOOL:
198 			arg->arg_val.bval = va_arg(ap, int);
199 			break;
200 		case ARG_PATHNAME:
201 			arg->arg_val.pval = va_arg(ap, char *);
202 			if (arg->arg_val.pval) {
203 				arg->arg_val.pval = strdup(arg->arg_val.pval);
204 			}
205 			break;
206 		default:
207 			fprintf(stderr, "%s: argument \"%s\" is of "
208 				"unknown type\n", PROGNAME, name);
209 			goto fail;
210 	}
211 
212 	va_end(ap);
213 	return;
214 
215 fail:
216 	fprintf(stderr, "%s: ignoring unknown argument \"%s\"\n",
217 			PROGNAME, name);
218 	va_end(ap);
219 	return;
220 }
221 
check_arg_bool(const char * name)222 BOOL check_arg_bool(const char * name)
223 {
224 	struct argdef * arg;
225 
226 	if ((arg = find_named_arg(name)) &&
227 	    (arg->arg_type == ARG_BOOL)) {
228 		return(arg->arg_val.bval);
229 	}
230 
231 	DEBUG(0, ("invalid argument name: %s", name));
232 	SMB_ASSERT(0);
233 	return(False);
234 }
235 
check_arg_numeric(const char * name)236 uint64_t check_arg_numeric(const char * name)
237 {
238 	struct argdef * arg;
239 
240 	if ((arg = find_named_arg(name)) &&
241 	    (arg->arg_type == ARG_NUMERIC || arg->arg_type == ARG_SIZE)) {
242 		return(arg->arg_val.nval);
243 	}
244 
245 	DEBUG(0, ("invalid argument name: %s", name));
246 	SMB_ASSERT(0);
247 	return(-1);
248 }
249 
check_arg_pathname(const char * name)250 const char * check_arg_pathname(const char * name)
251 {
252 	struct argdef * arg;
253 
254 	if ((arg = find_named_arg(name)) &&
255 	    (arg->arg_type == ARG_PATHNAME)) {
256 		return(arg->arg_val.pval);
257 	}
258 
259 	DEBUG(0, ("invalid argument name: %s", name));
260 	SMB_ASSERT(0);
261 	return(NULL);
262 }
263 
dump_args(void)264 static void dump_args(void)
265 {
266 	int i;
267 
268 	DEBUG(10, ("dumping argument values:\n"));
269 	for (i = 0; i < ARRAY_SIZE(args); ++i) {
270 		switch (args[i].arg_type) {
271 			case ARG_NUMERIC:
272 			case ARG_SIZE:
273 				DEBUG(10, ("\t%s=%llu\n", args[i].arg_name,
274 					(unsigned long long)args[i].arg_val.nval));
275 				break;
276 			case ARG_BOOL:
277 				DEBUG(10, ("\t%s=%s\n", args[i].arg_name,
278 					args[i].arg_val.bval ? "yes" : "no"));
279 				break;
280 			case ARG_PATHNAME:
281 				DEBUG(10, ("\t%s=%s\n", args[i].arg_name,
282 					args[i].arg_val.pval ?
283 						args[i].arg_val.pval :
284 						"(NULL)"));
285 				break;
286 			default:
287 				SMB_ASSERT(0);
288 		}
289 	}
290 }
291 
cifsdd_help_message(poptContext pctx,enum poptCallbackReason preason,struct poptOption * poption,const char * parg,void * pdata)292 static void cifsdd_help_message(poptContext pctx,
293 		enum poptCallbackReason preason,
294 		struct poptOption * poption,
295 		const char * parg,
296 		void * pdata)
297 {
298 	static const char notes[] =
299 "FILE can be a local filename or a UNC path of the form //server/share/path.\n";
300 
301 	char prefix[24];
302 	int i;
303 
304 	if (poption->shortName != '?') {
305 		poptPrintUsage(pctx, stdout, 0);
306 		fprintf(stdout, "        [dd options]\n");
307 		exit(0);
308 	}
309 
310 	poptPrintHelp(pctx, stdout, 0);
311 	fprintf(stdout, "\nCIFS dd options:\n");
312 
313 	for (i = 0; i < ARRAY_SIZE(args); ++i) {
314 		if (args[i].arg_name == NULL) {
315 			break;
316 		}
317 
318 		snprintf(prefix, sizeof(prefix), "%s=%-*s",
319 			args[i].arg_name,
320 			(int)(sizeof(prefix) - strlen(args[i].arg_name) - 2),
321 			argtype_str(args[i].arg_type));
322 		prefix[sizeof(prefix) - 1] = '\0';
323 		fprintf(stdout, "  %s%s\n", prefix, args[i].arg_help);
324 	}
325 
326 	fprintf(stdout, "\n%s\n", notes);
327 	exit(0);
328 }
329 
330 /* ------------------------------------------------------------------------- */
331 /* Main block copying routine.						     */
332 /* ------------------------------------------------------------------------- */
333 
print_transfer_stats(void)334 static void print_transfer_stats(void)
335 {
336 	if (DEBUGLEVEL > 0) {
337 		printf("%llu+%llu records in (%llu bytes)\n"
338 			"%llu+%llu records out (%llu bytes)\n",
339 			(unsigned long long)dd_stats.in.fblocks,
340 			(unsigned long long)dd_stats.in.pblocks,
341 			(unsigned long long)dd_stats.in.bytes,
342 			(unsigned long long)dd_stats.out.fblocks,
343 			(unsigned long long)dd_stats.out.pblocks,
344 			(unsigned long long)dd_stats.out.bytes);
345 	} else {
346 		printf("%llu+%llu records in\n%llu+%llu records out\n",
347 				(unsigned long long)dd_stats.in.fblocks,
348 				(unsigned long long)dd_stats.in.pblocks,
349 				(unsigned long long)dd_stats.out.fblocks,
350 				(unsigned long long)dd_stats.out.pblocks);
351 	}
352 }
353 
open_file(const char * which)354 static struct dd_iohandle * open_file(const char * which)
355 {
356 	int			options = 0;
357 	const char *		path = NULL;
358 	struct dd_iohandle *	handle = NULL;
359 
360 	if (check_arg_bool("direct")) {
361 		options |= DD_DIRECT_IO;
362 	}
363 
364 	if (check_arg_bool("sync")) {
365 		options |= DD_SYNC_IO;
366 	}
367 
368 	if (check_arg_bool("oplock")) {
369 		options |= DD_OPLOCK;
370 	}
371 
372 	if (strcmp(which, "if") == 0) {
373 		path = check_arg_pathname("if");
374 		handle = dd_open_path(path, check_arg_numeric("ibs"),
375 					options);
376 	} else if (strcmp(which, "of") == 0) {
377 		options |= DD_WRITE;
378 		path = check_arg_pathname("of");
379 		handle = dd_open_path(path, check_arg_numeric("obs"),
380 					options);
381 	} else {
382 		SMB_ASSERT(0);
383 		return(NULL);
384 	}
385 
386 	if (!handle) {
387 		fprintf(stderr, "%s: failed to open %s\n", PROGNAME, path);
388 	}
389 
390 	return(handle);
391 }
392 
set_max_xmit(uint64_t iomax)393 static void set_max_xmit(uint64_t iomax)
394 {
395 	char buf[64];
396 
397 	snprintf(buf, sizeof(buf), "%llu", (unsigned long long)iomax);
398 	lp_set_cmdline("max xmit", buf);
399 }
400 
copy_files(void)401 static int copy_files(void)
402 {
403 	uint8_t *	iobuf;	/* IO buffer. */
404 	uint64_t	iomax;	/* Size of the IO buffer. */
405 	uint64_t	data_size; /* Amount of data in the IO buffer. */
406 
407 	uint64_t	ibs;
408 	uint64_t	obs;
409 	uint64_t	count;
410 
411 	struct dd_iohandle *	ifile;
412 	struct dd_iohandle *	ofile;
413 
414 	ibs = check_arg_numeric("ibs");
415 	obs = check_arg_numeric("obs");
416 	count = check_arg_numeric("count");
417 
418 	/* Allocate IO buffer. We need more than the max IO size because we
419 	 * could accumulate a remainder if ibs and obs don't match.
420 	 */
421 	iomax = 2 * MAX(ibs, obs);
422 	if ((iobuf = malloc(iomax)) == NULL) {
423 		fprintf(stderr,
424 			"%s: failed to allocate IO buffer of %llu bytes\n",
425 			PROGNAME, (unsigned long long)iomax);
426 		return(EOM_EXIT_CODE);
427 	}
428 
429 	set_max_xmit(MAX(ibs, obs));
430 
431 	DEBUG(4, ("IO buffer size is %llu, max xmit is %d\n",
432 			(unsigned long long)iomax, lp_max_xmit()));
433 
434 	if (!(ifile = open_file("if"))) {
435 		return(FILESYS_EXIT_CODE);
436 	}
437 
438 	if (!(ofile = open_file("of"))) {
439 		return(FILESYS_EXIT_CODE);
440 	}
441 
442 	/* Seek the files to their respective starting points. */
443 	ifile->io_seek(ifile, check_arg_numeric("skip") * ibs);
444 	ofile->io_seek(ofile, check_arg_numeric("seek") * obs);
445 
446 	DEBUG(4, ("max xmit was negotiated to be %d\n", lp_max_xmit()));
447 
448 	for (data_size = 0;;) {
449 
450 		/* Handle signals. We are somewhat compatible with GNU dd.
451 		 * SIGINT makes us stop, but still print transfer statistics.
452 		 * SIGUSR1 makes us print transfer statistics but we continue
453 		 * copying.
454 		 */
455 		if (dd_sigint) {
456 			break;
457 		}
458 
459 		if (dd_sigusr1) {
460 			print_transfer_stats();
461 			dd_sigusr1 = 0;
462 		}
463 
464 		if (ifile->io_flags & DD_END_OF_FILE) {
465 			DEBUG(4, ("flushing %llu bytes at EOF\n",
466 					(unsigned long long)data_size));
467 			while (data_size > 0) {
468 				if (!dd_flush_block(ofile, iobuf,
469 							&data_size, obs)) {
470 					return(IOERROR_EXIT_CODE);
471 				}
472 			}
473 			goto done;
474 		}
475 
476 		/* Try and read enough blocks of ibs bytes to be able write
477 		 * out one of obs bytes.
478 		 */
479 		if (!dd_fill_block(ifile, iobuf, &data_size, obs, ibs)) {
480 			return(IOERROR_EXIT_CODE);
481 		}
482 
483 		if (data_size == 0) {
484 			/* Done. */
485 			SMB_ASSERT(ifile->io_flags & DD_END_OF_FILE);
486 		}
487 
488 		/* Stop reading when we hit the block count. */
489 		if (dd_stats.in.bytes >= (ibs * count)) {
490 			ifile->io_flags |= DD_END_OF_FILE;
491 		}
492 
493 		/* If we wanted to be a legitimate dd, we would do character
494 		 * conversions and other shenanigans here.
495 		 */
496 
497 		/* Flush what we read in units of obs bytes. We want to have
498 		 * at least obs bytes in the IO buffer but might not if the
499 		 * file is too small.
500 		 */
501 		if (data_size &&
502 		    !dd_flush_block(ofile, iobuf, &data_size, obs)) {
503 			return(IOERROR_EXIT_CODE);
504 		}
505 	}
506 
507 done:
508 	print_transfer_stats();
509 	return(0);
510 }
511 
512 /* ------------------------------------------------------------------------- */
513 /* Main.								     */
514 /* ------------------------------------------------------------------------- */
515 
516 struct poptOption cifsddHelpOptions[] = {
517   { NULL, '\0', POPT_ARG_CALLBACK, (void *)&cifsdd_help_message, '\0', NULL, NULL },
518   { "help", '?', 0, NULL, '?', "Show this help message", NULL },
519   { "usage", '\0', 0, NULL, 'u', "Display brief usage message", NULL },
520   { NULL }
521 } ;
522 
main(int argc,const char ** argv)523 int main(int argc, const char ** argv)
524 {
525 	int i;
526 	const char ** dd_args;
527 
528 	poptContext pctx;
529 	struct poptOption poptions[] = {
530 		/* POPT_AUTOHELP */
531 		{ NULL, '\0', POPT_ARG_INCLUDE_TABLE, cifsddHelpOptions,
532 			0, "Help options:", NULL },
533 		POPT_COMMON_SAMBA
534 		POPT_COMMON_CONNECTION
535 		POPT_COMMON_CREDENTIALS
536 		POPT_COMMON_VERSION
537 		{ NULL }
538 	};
539 
540 	/* Block sizes. */
541 	set_arg_val("bs", (uint64_t)4096);
542 	set_arg_val("ibs", (uint64_t)4096);
543 	set_arg_val("obs", (uint64_t)4096);
544 	/* Block counts. */
545 	set_arg_val("count", (uint64_t)-1);
546 	set_arg_val("seek", (uint64_t)0);
547 	set_arg_val("seek", (uint64_t)0);
548 	/* Files. */
549 	set_arg_val("if", NULL);
550 	set_arg_val("of", NULL);
551 	/* Options. */
552 	set_arg_val("direct", False);
553 	set_arg_val("sync", False);
554 	set_arg_val("oplock", False);
555 
556 	pctx = poptGetContext(PROGNAME, argc, argv, poptions, 0);
557 	while ((i = poptGetNextOpt(pctx)) != -1) {
558 		;
559 	}
560 
561 	for (dd_args = poptGetArgs(pctx); dd_args && *dd_args; ++dd_args) {
562 
563 		if (!set_arg_argv(*dd_args)) {
564 			fprintf(stderr, "%s: invalid option: %s\n",
565 					PROGNAME, *dd_args);
566 			exit(SYNTAX_EXIT_CODE);
567 		}
568 
569 		/* "bs" has the side-effect of setting "ibs" and "obs". */
570 		if (strncmp(*dd_args, "bs=", 3) == 0) {
571 			uint64_t bs = check_arg_numeric("bs");
572 			set_arg_val("ibs", bs);
573 			set_arg_val("obs", bs);
574 		}
575 	}
576 
577 	gensec_init();
578 	dump_args();
579 
580 	if (check_arg_numeric("ibs") == 0 || check_arg_numeric("ibs") == 0) {
581 		fprintf(stderr, "%s: block sizes must be greater that zero\n",
582 				PROGNAME);
583 		exit(SYNTAX_EXIT_CODE);
584 	}
585 
586 	if (check_arg_pathname("if") == NULL) {
587 		fprintf(stderr, "%s: missing input filename\n", PROGNAME);
588 		exit(SYNTAX_EXIT_CODE);
589 	}
590 
591 	if (check_arg_pathname("of") == NULL) {
592 		fprintf(stderr, "%s: missing output filename\n", PROGNAME);
593 		exit(SYNTAX_EXIT_CODE);
594 	}
595 
596 	CatchSignal(SIGINT, dd_handle_signal);
597 	CatchSignal(SIGUSR1, dd_handle_signal);
598 	return(copy_files());
599 }
600 
601 /* vim: set sw=8 sts=8 ts=8 tw=79 : */
602