summaryrefslogtreecommitdiffstats
path: root/source4/client/cifsdd.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--source4/client/cifsdd.c733
1 files changed, 733 insertions, 0 deletions
diff --git a/source4/client/cifsdd.c b/source4/client/cifsdd.c
new file mode 100644
index 0000000..812698e
--- /dev/null
+++ b/source4/client/cifsdd.c
@@ -0,0 +1,733 @@
+/*
+ CIFSDD - dd for SMB.
+ Main program, argument handling and block copying.
+
+ Copyright (C) James Peach 2005-2006
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "auth/gensec/gensec.h"
+#include "lib/cmdline/cmdline.h"
+#include "libcli/resolve/resolve.h"
+#include "libcli/raw/libcliraw.h"
+#include "lib/events/events.h"
+
+#include "cifsdd.h"
+#include "param/param.h"
+
+const char * const PROGNAME = "cifsdd";
+
+#define SYNTAX_EXIT_CODE 1 /* Invokation syntax or logic error. */
+#define EOM_EXIT_CODE 9 /* Out of memory error. */
+#define FILESYS_EXIT_CODE 10 /* File manipulation error. */
+#define IOERROR_EXIT_CODE 11 /* Error during IO phase. */
+
+struct dd_stats_record dd_stats;
+
+static int dd_sigint;
+static int dd_sigusr1;
+
+static void dd_handle_signal(int sig)
+{
+ switch (sig)
+ {
+ case SIGINT:
+ ++dd_sigint;
+ break;
+ case SIGUSR1:
+ ++dd_sigusr1;
+ break;
+ default:
+ break;
+ }
+}
+
+/* ------------------------------------------------------------------------- */
+/* Argument handling. */
+/* ------------------------------------------------------------------------- */
+
+static const struct {
+ enum argtype arg_type;
+ const char * arg_name;
+} names [] = {
+ { ARG_NUMERIC, "COUNT" },
+ { ARG_SIZE, "SIZE" },
+ { ARG_PATHNAME, "FILE" },
+ { ARG_BOOL, "BOOLEAN" },
+};
+
+static const char * argtype_str(enum argtype arg_type)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(names); ++i) {
+ if (arg_type == names[i].arg_type) {
+ return(names[i].arg_name);
+ }
+ }
+
+ return("<unknown>");
+}
+
+static struct argdef args[] =
+{
+ {
+ .arg_name = "bs",
+ .arg_type = ARG_SIZE,
+ .arg_help = "force ibs and obs to SIZE bytes",
+ },
+ {
+ .arg_name = "ibs",
+ .arg_type = ARG_SIZE,
+ .arg_help = "read SIZE bytes at a time",
+ },
+ {
+ .arg_name = "obs",
+ .arg_type = ARG_SIZE,
+ .arg_help = "write SIZE bytes at a time",
+ },
+
+ {
+ .arg_name = "count",
+ .arg_type = ARG_NUMERIC,
+ .arg_help = "copy COUNT input blocks",
+ },
+ {
+ .arg_name = "seek",
+ .arg_type = ARG_NUMERIC,
+ .arg_help = "skip COUNT blocks at start of output",
+ },
+ {
+ .arg_name = "skip",
+ .arg_type = ARG_NUMERIC,
+ .arg_help = "skip COUNT blocks at start of input",
+ },
+
+ {
+ .arg_name = "if",
+ .arg_type = ARG_PATHNAME,
+ .arg_help = "read input from FILE",
+ },
+ {
+ .arg_name = "of",
+ .arg_type = ARG_PATHNAME,
+ .arg_help = "write output to FILE",
+ },
+
+ {
+ .arg_name = "direct",
+ .arg_type = ARG_BOOL,
+ .arg_help = "use direct I/O if non-zero",
+ },
+ {
+ .arg_name = "sync",
+ .arg_type = ARG_BOOL,
+ .arg_help = "use synchronous writes if non-zero",
+ },
+ {
+ .arg_name = "oplock",
+ .arg_type = ARG_BOOL,
+ .arg_help = "take oplocks on the input and output files",
+ },
+
+/* FIXME: We should support using iflags and oflags for setting oplock and I/O
+ * options. This would make us compatible with GNU dd.
+ */
+};
+
+static struct argdef * find_named_arg(const char * arg)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(args); ++i) {
+ if (strwicmp(arg, args[i].arg_name) == 0) {
+ return(&args[i]);
+ }
+ }
+
+ return(NULL);
+}
+
+int set_arg_argv(const char * argv)
+{
+ struct argdef * arg;
+
+ char * name;
+ char * val;
+
+ if ((name = strdup(argv)) == NULL) {
+ return(0);
+ }
+
+ if ((val = strchr(name, '=')) == NULL) {
+ fprintf(stderr, "%s: malformed argument \"%s\"\n",
+ PROGNAME, argv);
+ goto fail;
+ }
+
+ *val = '\0';
+ val++;
+
+ if ((arg = find_named_arg(name)) == NULL) {
+ fprintf(stderr, "%s: ignoring unknown argument \"%s\"\n",
+ PROGNAME, name);
+ goto fail;
+ }
+
+ /* Found a matching name; convert the variable argument. */
+ switch (arg->arg_type) {
+ case ARG_NUMERIC:
+ if (!conv_str_u64(val, &arg->arg_val.nval)) {
+ goto fail;
+ }
+ break;
+ case ARG_SIZE:
+ if (!conv_str_size_error(val, &arg->arg_val.nval)) {
+ goto fail;
+ }
+ break;
+ case ARG_BOOL:
+ if (!conv_str_bool(val, &arg->arg_val.bval)) {
+ goto fail;
+ }
+ break;
+ case ARG_PATHNAME:
+ if (!(arg->arg_val.pval = strdup(val))) {
+ goto fail;
+ }
+ break;
+ default:
+ fprintf(stderr, "%s: argument \"%s\" is of "
+ "unknown type\n", PROGNAME, name);
+ goto fail;
+ }
+
+ free(name);
+ return(1);
+
+fail:
+ free(name);
+ return(0);
+}
+
+void set_arg_val(const char * name, ...)
+{
+ va_list ap;
+ struct argdef * arg;
+
+ va_start(ap, name);
+ if ((arg = find_named_arg(name)) == NULL) {
+ goto fail;
+ }
+
+ /* Found a matching name; convert the variable argument. */
+ switch (arg->arg_type) {
+ case ARG_NUMERIC:
+ case ARG_SIZE:
+ arg->arg_val.nval = va_arg(ap, uint64_t);
+ break;
+ case ARG_BOOL:
+ arg->arg_val.bval = va_arg(ap, int);
+ break;
+ case ARG_PATHNAME:
+ arg->arg_val.pval = va_arg(ap, char *);
+ if (arg->arg_val.pval) {
+ arg->arg_val.pval = strdup(arg->arg_val.pval);
+ }
+ break;
+ default:
+ fprintf(stderr, "%s: argument \"%s\" is of "
+ "unknown type\n", PROGNAME, name);
+ goto fail;
+ }
+
+ va_end(ap);
+ return;
+
+fail:
+ fprintf(stderr, "%s: ignoring unknown argument \"%s\"\n",
+ PROGNAME, name);
+ va_end(ap);
+ return;
+}
+
+bool check_arg_bool(const char * name)
+{
+ struct argdef * arg;
+
+ if ((arg = find_named_arg(name)) &&
+ (arg->arg_type == ARG_BOOL)) {
+ return(arg->arg_val.bval);
+ }
+
+ DEBUG(0, ("invalid argument name: %s", name));
+ SMB_ASSERT(0);
+ return(false);
+}
+
+uint64_t check_arg_numeric(const char * name)
+{
+ struct argdef * arg;
+
+ if ((arg = find_named_arg(name)) &&
+ (arg->arg_type == ARG_NUMERIC || arg->arg_type == ARG_SIZE)) {
+ return(arg->arg_val.nval);
+ }
+
+ DEBUG(0, ("invalid argument name: %s", name));
+ SMB_ASSERT(0);
+ return(-1);
+}
+
+const char * check_arg_pathname(const char * name)
+{
+ struct argdef * arg;
+
+ if ((arg = find_named_arg(name)) &&
+ (arg->arg_type == ARG_PATHNAME)) {
+ return(arg->arg_val.pval);
+ }
+
+ DEBUG(0, ("invalid argument name: %s", name));
+ SMB_ASSERT(0);
+ return(NULL);
+}
+
+static void dump_args(void)
+{
+ int i;
+
+ DEBUG(10, ("dumping argument values:\n"));
+ for (i = 0; i < ARRAY_SIZE(args); ++i) {
+ switch (args[i].arg_type) {
+ case ARG_NUMERIC:
+ case ARG_SIZE:
+ DEBUG(10, ("\t%s=%llu\n", args[i].arg_name,
+ (unsigned long long)args[i].arg_val.nval));
+ break;
+ case ARG_BOOL:
+ DEBUG(10, ("\t%s=%s\n", args[i].arg_name,
+ args[i].arg_val.bval ? "yes" : "no"));
+ break;
+ case ARG_PATHNAME:
+ DEBUG(10, ("\t%s=%s\n", args[i].arg_name,
+ args[i].arg_val.pval ?
+ args[i].arg_val.pval :
+ "(NULL)"));
+ break;
+ default:
+ SMB_ASSERT(0);
+ }
+ }
+}
+
+static void cifsdd_help_message(poptContext pctx,
+ enum poptCallbackReason preason,
+ struct poptOption * poption,
+ const char * parg,
+ void * pdata)
+{
+ static const char notes[] =
+"FILE can be a local filename or a UNC path of the form //server/share/path.\n";
+
+ char prefix[24];
+ int i;
+
+ if (poption->shortName != '?') {
+ poptPrintUsage(pctx, stdout, 0);
+ fprintf(stdout, " [dd options]\n");
+ exit(0);
+ }
+
+ poptPrintHelp(pctx, stdout, 0);
+ fprintf(stdout, "\nCIFS dd options:\n");
+
+ for (i = 0; i < ARRAY_SIZE(args); ++i) {
+ if (args[i].arg_name == NULL) {
+ break;
+ }
+
+ snprintf(prefix, sizeof(prefix), "%s=%-*s",
+ args[i].arg_name,
+ (int)(sizeof(prefix) - strlen(args[i].arg_name) - 2),
+ argtype_str(args[i].arg_type));
+ prefix[sizeof(prefix) - 1] = '\0';
+ fprintf(stdout, " %s%s\n", prefix, args[i].arg_help);
+ }
+
+ fprintf(stdout, "\n%s\n", notes);
+ exit(0);
+}
+
+/* ------------------------------------------------------------------------- */
+/* Main block copying routine. */
+/* ------------------------------------------------------------------------- */
+
+static void print_transfer_stats(void)
+{
+ if (DEBUGLEVEL > 0) {
+ printf("%llu+%llu records in (%llu bytes)\n"
+ "%llu+%llu records out (%llu bytes)\n",
+ (unsigned long long)dd_stats.in.fblocks,
+ (unsigned long long)dd_stats.in.pblocks,
+ (unsigned long long)dd_stats.in.bytes,
+ (unsigned long long)dd_stats.out.fblocks,
+ (unsigned long long)dd_stats.out.pblocks,
+ (unsigned long long)dd_stats.out.bytes);
+ } else {
+ printf("%llu+%llu records in\n%llu+%llu records out\n",
+ (unsigned long long)dd_stats.in.fblocks,
+ (unsigned long long)dd_stats.in.pblocks,
+ (unsigned long long)dd_stats.out.fblocks,
+ (unsigned long long)dd_stats.out.pblocks);
+ }
+}
+
+static struct dd_iohandle * open_file(struct resolve_context *resolve_ctx,
+ struct tevent_context *ev,
+ const char * which, const char **ports,
+ struct smbcli_options *smb_options,
+ const char *socket_options,
+ struct smbcli_session_options *smb_session_options,
+ struct gensec_settings *gensec_settings)
+{
+ int options = 0;
+ const char * path = NULL;
+ struct dd_iohandle * handle = NULL;
+
+ if (check_arg_bool("direct")) {
+ options |= DD_DIRECT_IO;
+ }
+
+ if (check_arg_bool("sync")) {
+ options |= DD_SYNC_IO;
+ }
+
+ if (check_arg_bool("oplock")) {
+ options |= DD_OPLOCK;
+ }
+
+ if (strcmp(which, "if") == 0) {
+ path = check_arg_pathname("if");
+ handle = dd_open_path(resolve_ctx, ev, path, ports,
+ check_arg_numeric("ibs"), options,
+ socket_options,
+ smb_options, smb_session_options,
+ gensec_settings);
+ } else if (strcmp(which, "of") == 0) {
+ options |= DD_WRITE;
+ path = check_arg_pathname("of");
+ handle = dd_open_path(resolve_ctx, ev, path, ports,
+ check_arg_numeric("obs"), options,
+ socket_options,
+ smb_options, smb_session_options,
+ gensec_settings);
+ } else {
+ SMB_ASSERT(0);
+ return(NULL);
+ }
+
+ if (!handle) {
+ fprintf(stderr, "%s: failed to open %s\n", PROGNAME, path);
+ }
+
+ return(handle);
+}
+
+static int copy_files(struct tevent_context *ev, struct loadparm_context *lp_ctx)
+{
+ uint8_t * iobuf; /* IO buffer. */
+ uint64_t iomax; /* Size of the IO buffer. */
+ uint64_t data_size; /* Amount of data in the IO buffer. */
+
+ uint64_t ibs;
+ uint64_t obs;
+ uint64_t count;
+
+ struct dd_iohandle * ifile;
+ struct dd_iohandle * ofile;
+
+ struct smbcli_options options;
+ struct smbcli_session_options session_options;
+
+ ibs = check_arg_numeric("ibs");
+ obs = check_arg_numeric("obs");
+ count = check_arg_numeric("count");
+
+ lpcfg_smbcli_options(lp_ctx, &options);
+ lpcfg_smbcli_session_options(lp_ctx, &session_options);
+
+ /* Allocate IO buffer. We need more than the max IO size because we
+ * could accumulate a remainder if ibs and obs don't match.
+ */
+ iomax = 2 * MAX(ibs, obs);
+ if ((iobuf = malloc_array_p(uint8_t, iomax)) == NULL) {
+ fprintf(stderr,
+ "%s: failed to allocate IO buffer of %llu bytes\n",
+ PROGNAME, (unsigned long long)iomax);
+ return(EOM_EXIT_CODE);
+ }
+
+ options.max_xmit = MAX(ibs, obs);
+
+ DEBUG(4, ("IO buffer size is %llu, max xmit is %d\n",
+ (unsigned long long)iomax, options.max_xmit));
+
+ if (!(ifile = open_file(lpcfg_resolve_context(lp_ctx), ev, "if",
+ lpcfg_smb_ports(lp_ctx), &options,
+ lpcfg_socket_options(lp_ctx),
+ &session_options,
+ lpcfg_gensec_settings(lp_ctx, lp_ctx)))) {
+ SAFE_FREE(iobuf);
+ return(FILESYS_EXIT_CODE);
+ }
+
+ if (!(ofile = open_file(lpcfg_resolve_context(lp_ctx), ev, "of",
+ lpcfg_smb_ports(lp_ctx), &options,
+ lpcfg_socket_options(lp_ctx),
+ &session_options,
+ lpcfg_gensec_settings(lp_ctx, lp_ctx)))) {
+ SAFE_FREE(iobuf);
+ return(FILESYS_EXIT_CODE);
+ }
+
+ /* Seek the files to their respective starting points. */
+ ifile->io_seek(ifile, check_arg_numeric("skip") * ibs);
+ ofile->io_seek(ofile, check_arg_numeric("seek") * obs);
+
+ DEBUG(4, ("max xmit was negotiated to be %d\n", options.max_xmit));
+
+ for (data_size = 0;;) {
+
+ /* Handle signals. We are somewhat compatible with GNU dd.
+ * SIGINT makes us stop, but still print transfer statistics.
+ * SIGUSR1 makes us print transfer statistics but we continue
+ * copying.
+ */
+ if (dd_sigint) {
+ break;
+ }
+
+ if (dd_sigusr1) {
+ print_transfer_stats();
+ dd_sigusr1 = 0;
+ }
+
+ if (ifile->io_flags & DD_END_OF_FILE) {
+ DEBUG(4, ("flushing %llu bytes at EOF\n",
+ (unsigned long long)data_size));
+ while (data_size > 0) {
+ if (!dd_flush_block(ofile, iobuf,
+ &data_size, obs)) {
+ SAFE_FREE(iobuf);
+ return(IOERROR_EXIT_CODE);
+ }
+ }
+ goto done;
+ }
+
+ /* Try and read enough blocks of ibs bytes to be able write
+ * out one of obs bytes.
+ */
+ if (!dd_fill_block(ifile, iobuf, &data_size, obs, ibs)) {
+ SAFE_FREE(iobuf);
+ return(IOERROR_EXIT_CODE);
+ }
+
+ if (data_size == 0) {
+ /* Done. */
+ SMB_ASSERT(ifile->io_flags & DD_END_OF_FILE);
+ }
+
+ /* Stop reading when we hit the block count. */
+ if (dd_stats.in.bytes >= (ibs * count)) {
+ ifile->io_flags |= DD_END_OF_FILE;
+ }
+
+ /* If we wanted to be a legitimate dd, we would do character
+ * conversions and other shenanigans here.
+ */
+
+ /* Flush what we read in units of obs bytes. We want to have
+ * at least obs bytes in the IO buffer but might not if the
+ * file is too small.
+ */
+ if (data_size &&
+ !dd_flush_block(ofile, iobuf, &data_size, obs)) {
+ SAFE_FREE(iobuf);
+ return(IOERROR_EXIT_CODE);
+ }
+ }
+
+done:
+ SAFE_FREE(iobuf);
+ print_transfer_stats();
+ return(0);
+}
+
+/* ------------------------------------------------------------------------- */
+/* Main. */
+/* ------------------------------------------------------------------------- */
+
+struct poptOption cifsddHelpOptions[] = {
+ {
+ .longName = NULL,
+ .shortName = '\0',
+ .argInfo = POPT_ARG_CALLBACK,
+ .arg = (void *)&cifsdd_help_message,
+ .val = '\0',
+ },
+ {
+ .longName = "help",
+ .shortName = '?',
+ .val = '?',
+ .descrip = "Show this help message",
+ },
+ {
+ .longName = "usage",
+ .shortName = '\0',
+ .val = 'u',
+ .descrip = "Display brief usage message",
+ },
+ POPT_TABLEEND
+};
+
+int main(int argc, char *argv[])
+{
+ const char **const_argv = discard_const_p(const char *, argv);
+ int i;
+ const char ** dd_args;
+ TALLOC_CTX *mem_ctx = NULL;
+ struct loadparm_context *lp_ctx = NULL;
+ struct tevent_context *ev;
+ bool ok;
+ int rc;
+
+ poptContext pctx;
+ struct poptOption poptions[] = {
+ /* POPT_AUTOHELP */
+ { NULL, '\0', POPT_ARG_INCLUDE_TABLE, cifsddHelpOptions,
+ 0, "Help options:", NULL },
+ POPT_COMMON_SAMBA
+ POPT_COMMON_CONNECTION
+ POPT_COMMON_CREDENTIALS
+ POPT_LEGACY_S4
+ POPT_COMMON_VERSION
+ POPT_TABLEEND
+ };
+
+ /* Block sizes. */
+ set_arg_val("bs", (uint64_t)4096);
+ set_arg_val("ibs", (uint64_t)4096);
+ set_arg_val("obs", (uint64_t)4096);
+ /* Block counts. */
+ set_arg_val("count", (uint64_t)-1);
+ set_arg_val("seek", (uint64_t)0);
+ set_arg_val("skip", (uint64_t)0);
+ /* Files. */
+ set_arg_val("if", NULL);
+ set_arg_val("of", NULL);
+ /* Options. */
+ set_arg_val("direct", false);
+ set_arg_val("sync", false);
+ set_arg_val("oplock", false);
+
+ mem_ctx = talloc_init("cifsdd.c/main");
+ if (mem_ctx == NULL) {
+ d_printf("Not enough memory\n");
+ exit(1);
+ }
+
+ ok = samba_cmdline_init(mem_ctx,
+ SAMBA_CMDLINE_CONFIG_CLIENT,
+ false /* require_smbconf */);
+ if (!ok) {
+ DBG_ERR("Failed to init cmdline parser!\n");
+ TALLOC_FREE(mem_ctx);
+ exit(1);
+ }
+
+ pctx = samba_popt_get_context(getprogname(), argc, const_argv, poptions, 0);
+ if (pctx == NULL) {
+ DBG_ERR("Failed to setup popt context!\n");
+ TALLOC_FREE(mem_ctx);
+ exit(1);
+ }
+
+ while ((i = poptGetNextOpt(pctx)) != -1) {
+ switch (i) {
+ case POPT_ERROR_BADOPT:
+ fprintf(stderr, "\nInvalid option %s: %s\n\n",
+ poptBadOption(pctx, 0), poptStrerror(i));
+ poptPrintUsage(pctx, stderr, 0);
+ exit(1);
+ }
+ }
+
+ for (dd_args = poptGetArgs(pctx); dd_args && *dd_args; ++dd_args) {
+
+ if (!set_arg_argv(*dd_args)) {
+ fprintf(stderr, "%s: invalid option: %s\n",
+ PROGNAME, *dd_args);
+ exit(SYNTAX_EXIT_CODE);
+ }
+
+ /* "bs" has the side-effect of setting "ibs" and "obs". */
+ if (strncmp(*dd_args, "bs=", 3) == 0) {
+ uint64_t bs = check_arg_numeric("bs");
+ set_arg_val("ibs", bs);
+ set_arg_val("obs", bs);
+ }
+ }
+
+ poptFreeContext(pctx);
+ samba_cmdline_burn(argc, argv);
+
+ ev = s4_event_context_init(mem_ctx);
+ lp_ctx = samba_cmdline_get_lp_ctx();
+
+ gensec_init();
+ dump_args();
+
+ if (check_arg_numeric("ibs") == 0 || check_arg_numeric("obs") == 0) {
+ fprintf(stderr, "%s: block sizes must be greater that zero\n",
+ PROGNAME);
+ talloc_free(mem_ctx);
+ exit(SYNTAX_EXIT_CODE);
+ }
+
+ if (check_arg_pathname("if") == NULL) {
+ fprintf(stderr, "%s: missing input filename\n", PROGNAME);
+ talloc_free(mem_ctx);
+ exit(SYNTAX_EXIT_CODE);
+ }
+
+ if (check_arg_pathname("of") == NULL) {
+ fprintf(stderr, "%s: missing output filename\n", PROGNAME);
+ talloc_free(mem_ctx);
+ exit(SYNTAX_EXIT_CODE);
+ }
+
+ CatchSignal(SIGINT, dd_handle_signal);
+ CatchSignal(SIGUSR1, dd_handle_signal);
+ rc = copy_files(ev, lp_ctx);
+
+ talloc_free(mem_ctx);
+ return rc;
+}
+
+/* vim: set sw=8 sts=8 ts=8 tw=79 : */