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