From 60f8666ae88c5a03b0da58acb94015337442e18b Mon Sep 17 00:00:00 2001 From: James Peach Date: Tue, 31 Jan 2006 06:09:18 +0000 Subject: [PATCH] r13255: New CIFS dd client for use in performance testing. The guts of this is in client/cifsdd*, which implements a minimal implementation of dd. The IO path is careful to always perform IO at the requested block size. There is a very basic test suite in script/tests/test_cifsdd.sh which covers local and remote IO at a variety of block sizes. Added to lib/util_str.c is a small set of conv_str_*() functions to convert strings to the corresponding type. smbcli_parse_unc is modified to insert NULL terminators after its hostname and sharename parameters. This allows it to correctly parse a path of the form //foo/share/path/file. (This used to be commit cd2f94a65817bfae20ac21b730a2c42d8e581ab3) --- source4/client/cifsdd.c | 599 +++++++++++++++++++++++++++ source4/client/cifsdd.h | 99 +++++ source4/client/cifsddio.c | 456 ++++++++++++++++++++ source4/client/config.mk | 17 + source4/lib/util_str.c | 71 ++++ source4/libcli/cliconnect.c | 34 +- source4/script/tests/test_cifsdd.sh | 84 ++++ source4/script/tests/tests_all.sh | 1 + source4/script/tests/tests_client.sh | 2 + 9 files changed, 1356 insertions(+), 7 deletions(-) create mode 100644 source4/client/cifsdd.c create mode 100644 source4/client/cifsdd.h create mode 100644 source4/client/cifsddio.c create mode 100755 source4/script/tests/test_cifsdd.sh create mode 100755 source4/script/tests/tests_client.sh diff --git a/source4/client/cifsdd.c b/source4/client/cifsdd.c new file mode 100644 index 00000000000..cf3ab17308b --- /dev/null +++ b/source4/client/cifsdd.c @@ -0,0 +1,599 @@ +/* + 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 2 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, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "includes.h" +#include "system/filesys.h" +#include "auth/gensec/gensec.h" +#include "lib/cmdline/popt_common.h" + +#include "cifsdd.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 char * argtype_str(enum argtype arg_type) +{ + static const struct { + enum argtype arg_type; + const char * arg_name; + } names [] = + { + { ARG_NUMERIC, "COUNT" }, + { ARG_SIZE, "SIZE" }, + { ARG_PATHNAME, "FILE" }, + { ARG_BOOL, "BOOLEAN" }, + }; + + int i; + + for (i = 0; i < ARRAY_SIZE(names); ++i) { + if (arg_type == names[i].arg_type) { + return(names[i].arg_name); + } + } + + return(""); +} + +static struct argdef args[] = +{ + { "bs", ARG_SIZE, "force ibs and obs to SIZE bytes" }, + { "ibs", ARG_SIZE, "read SIZE bytes at a time" }, + { "obs", ARG_SIZE, "write SIZE bytes at a time" }, + + { "count", ARG_NUMERIC, "copy COUNT input blocks" }, + { "seek",ARG_NUMERIC, "skip COUNT blocks at start of output" }, + { "skip",ARG_NUMERIC, "skip COUNT blocks at start of input" }, + + { "if", ARG_PATHNAME, "read input from FILE" }, + { "of", ARG_PATHNAME, "write output to FILE" }, + + { "direct", ARG_BOOL, "use direct I/O if non-zero" }, + { "sync", ARG_BOOL, "use synchronous writes if non-zero" }, + { "oplock", ARG_BOOL, "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. + */ +}; + +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(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, BOOL); + 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 const 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(const char * which) +{ + 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(path, check_arg_numeric("ibs"), + options); + } else if (strcmp(which, "of") == 0) { + options |= DD_WRITE; + path = check_arg_pathname("of"); + handle = dd_open_path(path, check_arg_numeric("obs"), + options); + } else { + SMB_ASSERT(0); + return(NULL); + } + + if (!handle) { + fprintf(stderr, "%s: failed to open %s\n", PROGNAME, path); + } + + return(handle); +} + +static void set_max_xmit(uint64_t iomax) +{ + char buf[64]; + + snprintf(buf, sizeof(buf), "%llu", (unsigned long long)iomax); + lp_set_cmdline("max xmit", buf); +} + +static int copy_files(void) +{ + uint8_t * iobuf; /* IO buffer. */ + uint64_t iomax; /* Size of the IO buffer. */ + uint64_t iosz; /* Amount of data in the IO buffer. */ + + uint64_t ibs; + uint64_t obs; + uint64_t count; + + struct dd_iohandle * ifile; + struct dd_iohandle * ofile; + + ibs = check_arg_numeric("ibs"); + obs = check_arg_numeric("obs"); + count = check_arg_numeric("count"); + + /* 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(iomax)) == NULL) { + fprintf(stderr, + "%s: failed to allocate IO buffer of %llu bytes\n", + PROGNAME, (unsigned long long)iomax); + return(EOM_EXIT_CODE); + } + + set_max_xmit(MAX(ibs, obs)); + + DEBUG(4, ("IO buffer size is %llu, max xmit is %d\n", + (unsigned long long)iomax, lp_max_xmit())); + + if (!(ifile = open_file("if"))) { + return(FILESYS_EXIT_CODE); + } + + if (!(ofile = open_file("of"))) { + 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", lp_max_xmit())); + + for (iosz = 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)iosz)); + while (iosz > 0) { + if (!dd_flush_block(ofile, iobuf, + &iosz, obs)) { + 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, &iosz, obs, ibs)) { + return(IOERROR_EXIT_CODE); + } + + if (iosz == 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 (!dd_flush_block(ofile, iobuf, &iosz, obs)) { + return(IOERROR_EXIT_CODE); + } + } + +done: + print_transfer_stats(); + return(0); +} + +/* ------------------------------------------------------------------------- */ +/* Main. */ +/* ------------------------------------------------------------------------- */ + +struct poptOption cifsddHelpOptions[] = { + { NULL, '\0', POPT_ARG_CALLBACK, (void *)&cifsdd_help_message, '\0', NULL, NULL }, + { "help", '?', 0, NULL, '?', "Show this help message", NULL }, + { "usage", '\0', 0, NULL, 'u', "Display brief usage message", NULL }, + POPT_TABLEEND +} ; + +int main(int argc, const char ** argv) +{ + int i; + const char ** dd_args; + + 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_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("seek", (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); + + pctx = poptGetContext(PROGNAME, argc, argv, poptions, 0); + while ((i = poptGetNextOpt(pctx)) != -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); + } + } + + gensec_init(); + dump_args(); + + if (check_arg_numeric("ibs") == 0 || check_arg_numeric("ibs") == 0) { + fprintf(stderr, "%s: block sizes must be greater that zero\n", + PROGNAME); + exit(SYNTAX_EXIT_CODE); + } + + if (check_arg_pathname("if") == NULL) { + fprintf(stderr, "%s: missing input filename\n", PROGNAME); + exit(SYNTAX_EXIT_CODE); + } + + if (check_arg_pathname("of") == NULL) { + fprintf(stderr, "%s: missing output filename\n", PROGNAME); + exit(SYNTAX_EXIT_CODE); + } + + CatchSignal(SIGINT, dd_handle_signal); + CatchSignal(SIGUSR1, dd_handle_signal); + return(copy_files()); +} + +/* vim: set sw=8 sts=8 ts=8 tw=79 : */ diff --git a/source4/client/cifsdd.h b/source4/client/cifsdd.h new file mode 100644 index 00000000000..28d31010ba0 --- /dev/null +++ b/source4/client/cifsdd.h @@ -0,0 +1,99 @@ +/* + CIFSDD - dd for SMB. + Declarations and administrivia. + + 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 2 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, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +extern const char * const PROGNAME; + +enum argtype +{ + ARG_NUMERIC, + ARG_SIZE, + ARG_PATHNAME, + ARG_BOOL, +}; + +struct argdef +{ + const char * arg_name; + enum argtype arg_type; + const char * arg_help; + + union + { + BOOL bval; + uint64_t nval; + const char * pval; + } arg_val; +}; + +int set_arg_argv(const char * argv); +void set_arg_val(const char * name, ...); + +BOOL check_arg_bool(const char * name); +uint64_t check_arg_numeric(const char * name); +const char * check_arg_pathname(const char * name); + +typedef BOOL (*dd_seek_func)(void * handle, uint64_t offset); +typedef BOOL (*dd_read_func)(void * handle, uint8_t * buf, + uint64_t wanted, uint64_t * actual); +typedef BOOL (*dd_write_func)(void * handle, uint8_t * buf, + uint64_t wanted, uint64_t * actual); + +struct dd_stats_record +{ + struct + { + uint64_t fblocks; /* Full blocks. */ + uint64_t pblocks; /* Partial blocks. */ + uint64_t bytes; /* Total bytes read. */ + } in; + struct + { + uint64_t fblocks; /* Full blocks. */ + uint64_t pblocks; /* Partial blocks. */ + uint64_t bytes; /* Total bytes written. */ + } out; +}; + +extern struct dd_stats_record dd_stats; + +struct dd_iohandle +{ + dd_seek_func io_seek; + dd_read_func io_read; + dd_write_func io_write; + int io_flags; +}; + +#define DD_END_OF_FILE 0x10000000 + +#define DD_DIRECT_IO 0x00000001 +#define DD_SYNC_IO 0x00000002 +#define DD_WRITE 0x00000004 +#define DD_OPLOCK 0x00000008 + +struct dd_iohandle * dd_open_path(const char * path, + uint64_t iosz, int options); +BOOL dd_fill_block(struct dd_iohandle * h, uint8_t * buf, + uint64_t * bufsz, uint64_t needsz, uint64_t blocksz); +BOOL dd_flush_block(struct dd_iohandle * h, uint8_t * buf, + uint64_t * bufsz, uint64_t blocksz); + +/* vim: set sw=8 sts=8 ts=8 tw=79 : */ diff --git a/source4/client/cifsddio.c b/source4/client/cifsddio.c new file mode 100644 index 00000000000..51528406bf4 --- /dev/null +++ b/source4/client/cifsddio.c @@ -0,0 +1,456 @@ +/* + CIFSDD - dd for SMB. + IO routines, generic and specific. + + 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 2 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, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "includes.h" +#include "system/filesys.h" +#include "libcli/raw/libcliraw.h" +#include "libcli/libcli.h" +#include "lib/cmdline/popt_common.h" + +#include "cifsdd.h" + +/* ------------------------------------------------------------------------- */ +/* UNIX file descriptor IO. */ +/* ------------------------------------------------------------------------- */ + +struct fd_handle +{ + struct dd_iohandle h; + int fd; +}; + +#define IO_HANDLE_TO_FD(h) (((struct fd_handle *)(h))->fd) + +static BOOL fd_seek_func(void * handle, uint64_t offset) +{ + ssize_t ret; + + ret = lseek(IO_HANDLE_TO_FD(handle), offset, SEEK_SET); + if (ret < 0) { + fprintf(stderr, "%s: seek failed: %s\n", + PROGNAME, strerror(errno)); + return(False); + } + + return(True); +} + +static BOOL fd_read_func(void * handle, uint8_t * buf, uint64_t wanted, uint64_t * actual) +{ + ssize_t ret; + + ret = read(IO_HANDLE_TO_FD(handle), buf, wanted); + if (ret < 0) { + fprintf(stderr, "%s: %llu byte read failed: %s\n", + PROGNAME, (unsigned long long)wanted, strerror(errno)); + return(False); + } + + *actual = (uint64_t)ret; + return(True); +} + +static BOOL +fd_write_func(void * handle, uint8_t * buf, uint64_t wanted, uint64_t * actual) +{ + ssize_t ret; + + ret = write(IO_HANDLE_TO_FD(handle), buf, wanted); + if (ret < 0) { + fprintf(stderr, "%s: %llu byte write failed: %s\n", + PROGNAME, (unsigned long long)wanted, strerror(errno)); + return(False); + } + + *actual = (uint64_t)ret; + return(True); +} + +static struct dd_iohandle * +open_fd_handle(const char * path, uint64_t iosz, int options) +{ + struct fd_handle * fdh; + int oflags = 0; + + DEBUG(4, ("opening fd stream for %s\n", path)); + if ((fdh = talloc_zero(NULL, struct fd_handle)) == NULL) { + return(NULL); + } + + fdh->h.io_read = fd_read_func; + fdh->h.io_write = fd_write_func; + fdh->h.io_seek = fd_seek_func; + + if (options & DD_DIRECT_IO) + oflags |= O_DIRECT; + + if (options & DD_SYNC_IO) + oflags |= O_SYNC; + + oflags |= (options & DD_WRITE) ? (O_WRONLY | O_CREAT) : (O_RDONLY); + + fdh->fd = open(path, oflags, 0644); + if (fdh->fd < 0) { + fprintf(stderr, "%s: %s: %s\n", + PROGNAME, path, strerror(errno)); + talloc_free(fdh); + return(NULL); + } + + if (options & DD_OPLOCK) { + DEBUG(2, ("FIXME: take local oplock on %s\n", path)); + } + + SMB_ASSERT((void *)fdh == (void *)&fdh->h); + return(&fdh->h); +} + +/* ------------------------------------------------------------------------- */ +/* CIFS client IO. */ +/* ------------------------------------------------------------------------- */ + +struct smb_handle +{ + struct dd_iohandle h; + struct smbcli_state * cli; + int fnum; + uint64_t offset; +}; + +#define IO_HANDLE_TO_SMB(h) ((struct smb_handle *)(h)) + +BOOL smb_seek_func(void * handle, uint64_t offset) +{ + IO_HANDLE_TO_SMB(handle)->offset = offset; + return(True); +} + +BOOL smb_read_func(void * handle, uint8_t * buf, + uint64_t wanted, uint64_t * actual) +{ + NTSTATUS ret; + union smb_read r; + struct smb_handle * smbh; + + ZERO_STRUCT(r); + smbh = IO_HANDLE_TO_SMB(handle); + + r.generic.level = RAW_READ_READX; + r.readx.in.fnum = smbh->fnum; + r.readx.in.offset = smbh->offset; + r.readx.in.mincnt = wanted; + r.readx.in.maxcnt = wanted; + r.readx.out.data = buf; + + /* FIXME: Should I really set readx.in.remaining? That just seems + * redundant. + */ + ret = smb_raw_read(smbh->cli->tree, &r); + if (!NT_STATUS_IS_OK(ret)) { + fprintf(stderr, "%s: %llu byte read failed: %s\n", + PROGNAME, (unsigned long long)wanted, nt_errstr(ret)); + return(False); + } + + /* Trap integer wrap. */ + SMB_ASSERT((smbh->offset + r.readx.out.nread) >= smbh->offset); + + *actual = r.readx.out.nread; + smbh->offset += r.readx.out.nread; + return(True); +} + +BOOL smb_write_func(void * handle, uint8_t * buf, + uint64_t wanted, uint64_t * actual) +{ + NTSTATUS ret; + union smb_write w; + struct smb_handle * smbh; + + ZERO_STRUCT(w); + smbh = IO_HANDLE_TO_SMB(handle); + + w.generic.level = RAW_WRITE_WRITEX; + w.writex.in.fnum = smbh->fnum; + w.writex.in.offset = smbh->offset; + w.writex.in.count = wanted; + w.writex.in.data = buf; + + ret = smb_raw_write(smbh->cli->tree, &w); + if (!NT_STATUS_IS_OK(ret)) { + fprintf(stderr, "%s: %llu byte write failed: %s\n", + PROGNAME, (unsigned long long)wanted, nt_errstr(ret)); + return(False); + } + + *actual = w.writex.out.nwritten; + smbh->offset += w.writex.out.nwritten; + return(True); +} + +static struct smbcli_state * init_smb_session(const char * host, const char * share) +{ + NTSTATUS ret; + struct smbcli_state * cli = NULL; + + /* When we support SMB URLs, we can get different user credentials for + * each connection, but for now, we just use the same one for both. + */ + ret = smbcli_full_connection(NULL, &cli, host, share, + NULL /* devtype */, cmdline_credentials, NULL /* events */); + + if (!NT_STATUS_IS_OK(ret)) { + fprintf(stderr, "%s: connecting to //%s/%s: %s\n", + PROGNAME, host, share, nt_errstr(ret)); + return(NULL); + } + + return(cli); +} + +static int open_smb_file(struct smbcli_state * cli, const char * path, int options) +{ + NTSTATUS ret; + union smb_open o; + + ZERO_STRUCT(o); + + o.ntcreatex.level = RAW_OPEN_NTCREATEX; + o.ntcreatex.in.fname = path; + + /* TODO: It's not clear whether to use these flags or to use the + * similarly named NTCREATEX flags in the create_options field. + */ + if (options & DD_DIRECT_IO) + o.ntcreatex.in.flags |= FILE_FLAG_NO_BUFFERING; + + if (options & DD_SYNC_IO) + o.ntcreatex.in.flags |= FILE_FLAG_WRITE_THROUGH; + + o.ntcreatex.in.access_mask |= + (options & DD_WRITE) ? SEC_FILE_WRITE_DATA + : SEC_FILE_READ_DATA; + + /* Try to create the file only if we will be writing to it. */ + o.ntcreatex.in.open_disposition = + (options & DD_WRITE) ? NTCREATEX_DISP_OPEN_IF + : NTCREATEX_DISP_OPEN; + + o.ntcreatex.in.share_access = + NTCREATEX_SHARE_ACCESS_READ | NTCREATEX_SHARE_ACCESS_WRITE; + + if (options & DD_OPLOCK) { + o.ntcreatex.in.flags |= NTCREATEX_FLAGS_REQUEST_OPLOCK; + } + + ret = smb_raw_open(cli->tree, NULL, &o); + if (!NT_STATUS_IS_OK(ret)) { + fprintf(stderr, "%s: opening %s: %s\n", + PROGNAME, path, nt_errstr(ret)); + return(-1); + } + + return(o.ntcreatex.out.fnum); +} + +static struct dd_iohandle * open_smb_handle(const char * host, const char * share, + const char * path, uint64_t iosz, int options) +{ + struct smb_handle * smbh; + + DEBUG(4, ("opening SMB stream to //%s/%s for %s\n", + host, share, path)); + + if ((smbh = talloc_zero(NULL, struct smb_handle)) == NULL) { + return(NULL); + } + + smbh->h.io_read = smb_read_func; + smbh->h.io_write = smb_write_func; + smbh->h.io_seek = smb_seek_func; + + if ((smbh->cli = init_smb_session(host, share)) == NULL) { + return(NULL); + } + + DEBUG(4, ("connected to //%s/%s with xmit size of %u bytes\n", + host, share, smbh->cli->transport->negotiate.max_xmit)); + + smbh->fnum = open_smb_file(smbh->cli, path, options); + return(&smbh->h); +} + +/* ------------------------------------------------------------------------- */ +/* Abstract IO interface. */ +/* ------------------------------------------------------------------------- */ + +struct dd_iohandle * dd_open_path(const char * path, uint64_t iosz, int options) +{ + if (file_exist(path)) { + return(open_fd_handle(path, iosz, options)); + } else { + char * host; + char * share; + + if (smbcli_parse_unc(path, NULL, &host, &share)) { + const char * remain; + remain = strstr(path, share) + strlen(share); + + /* Skip over leading directory separators. */ + while (*remain == '/' || *remain == '\\') { remain++; } + + return(open_smb_handle(host, share, remain, + iosz, options)); + } + + return(open_fd_handle(path, iosz, options)); + } +} + +/* Fill the buffer till it has at least needsz bytes. Use read operations of + * blocksz bytes. Return the number of bytes read and fill bufsz with the new + * buffer size. + * + * NOTE: The IO buffer is guaranteed to be big enough to fit needsz + blocksz + * bytes into it. + */ +BOOL dd_fill_block(struct dd_iohandle * h, uint8_t * buf, + uint64_t * bufsz, uint64_t needsz, uint64_t blocksz) +{ + uint64_t readsz; + + SMB_ASSERT(blocksz > 0); + SMB_ASSERT(needsz > 0); + + while (*bufsz < needsz) { + + if (!h->io_read(h, buf + (*bufsz), blocksz, &readsz)) { + return(False); + } + + if (readsz == 0) { + h->io_flags |= DD_END_OF_FILE; + break; + } + + DEBUG(6, ("added %llu bytes to IO buffer (need %llu bytes)\n", + (unsigned long long)readsz, (unsigned long long)needsz)); + + *bufsz += readsz; + dd_stats.in.bytes += readsz; + + if (readsz == blocksz) { + dd_stats.in.fblocks++; + } else { + DEBUG(3, ("partial read of %llu bytes (expected %llu)\n", + (unsigned long long)readsz, (unsigned long long)blocksz)); + dd_stats.in.pblocks++; + } + } + + return(True); +} + +/* Flush a buffer that contains bufsz bytes. Use writes of blocksz to do it, + * and shift any remaining bytes back to the head of the buffer when there are + * no more blocksz sized IOs left. + */ +BOOL dd_flush_block(struct dd_iohandle * h, uint8_t * buf, + uint64_t * bufsz, uint64_t blocksz) +{ + uint64_t writesz; + uint64_t totalsz = 0; + + SMB_ASSERT(blocksz > 0); + + /* We have explicitly been asked to write a partial block. */ + if ((*bufsz) < blocksz) { + + if (!h->io_write(h, buf, *bufsz, &writesz)) { + return(False); + } + + if (writesz == 0) { + fprintf(stderr, "%s: unexpectedly wrote 0 bytes\n", + PROGNAME); + return(False); + } + + totalsz += writesz; + dd_stats.out.bytes += writesz; + dd_stats.out.pblocks++; + } + + /* Write as many full blocks as there are in the buffer. */ + while (((*bufsz) - totalsz) >= blocksz) { + + if (!h->io_write(h, buf + totalsz, blocksz, &writesz)) { + return(False); + } + + if (writesz == 0) { + fprintf(stderr, "%s: unexpectedly wrote 0 bytes\n", + PROGNAME); + return(False); + } + + if (writesz == blocksz) { + dd_stats.out.fblocks++; + } else { + dd_stats.out.pblocks++; + } + + totalsz += writesz; + dd_stats.out.bytes += writesz; + + DEBUG(6, ("flushed %llu bytes from IO buffer of %llu bytes (%llu remain)\n", + (unsigned long long)blocksz, (unsigned long long)blocksz, + (unsigned long long)(blocksz - totalsz))); + } + + SMB_ASSERT(totalsz > 0); + + /* We have flushed as much of the IO buffer as we can while + * still doing blocksz-sized operations. Shift any remaining data + * to the front of the IO buffer. + */ + if ((*bufsz) > totalsz) { + uint64_t remain = (*bufsz) - totalsz; + + DEBUG(3, ("shifting %llu remainder bytes to IO buffer head\n", + (unsigned long long)remain)); + + memmove(buf, buf + totalsz, remain); + (*bufsz) = remain; + } else if ((*bufsz) == totalsz) { + (*bufsz) = 0; + } else { + /* Else buffer contains bufsz bytes that we will append + * to next time round. + */ + DEBUG(3, ("%llu unflushed bytes left in IO buffer\n", + (unsigned long long)(*bufsz))); + } + + return(True); +} + +/* vim: set sw=8 sts=8 ts=8 tw=79 : */ diff --git a/source4/client/config.mk b/source4/client/config.mk index 2cf5dbb80ad..10f56da1387 100644 --- a/source4/client/config.mk +++ b/source4/client/config.mk @@ -18,3 +18,20 @@ REQUIRED_SUBSYSTEMS = \ POPT_CREDENTIALS # End BINARY smbclient ################################# + +################################# +# Start BINARY cifsdd +[BINARY::cifsdd] +INSTALLDIR = BINDIR +OBJ_FILES = \ + cifsdd.o \ + cifsddio.o +REQUIRED_SUBSYSTEMS = \ + CONFIG \ + LIBSMB \ + LIBPOPT \ + POPT_SAMBA \ + POPT_CREDENTIALS +# End BINARY sdd +################################# + diff --git a/source4/lib/util_str.c b/source4/lib/util_str.c index 311f81eaf3b..6da0063c504 100644 --- a/source4/lib/util_str.c +++ b/source4/lib/util_str.c @@ -5,6 +5,7 @@ Copyright (C) Andrew Tridgell 1992-2001 Copyright (C) Simo Sorce 2001-2002 Copyright (C) Martin Pool 2003 + Copyright (C) James Peach 2005 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 @@ -1136,3 +1137,73 @@ BOOL set_boolean(const char *boolean_string, BOOL *boolean) return False; } +BOOL conv_str_bool(const char * str, BOOL * val) +{ + char * end = NULL; + long lval; + + if (str == NULL || *str == '\0') { + return False; + } + + lval = strtol(str, &end, 10 /* base */); + if (end == NULL || *end != '\0' || end == str) { + return set_boolean(str, val); + } + + *val = (lval) ? True : False; + return True; +} + +/* Convert a size specification like 16K into an integral number of bytes. */ +BOOL conv_str_size(const char * str, uint64_t * val) +{ + char * end = NULL; + unsigned long long lval; + + if (str == NULL || *str == '\0') { + return False; + } + + lval = strtoull(str, &end, 10 /* base */); + if (end == NULL || end == str) { + return False; + } + + if (*end) { + if (strwicmp(end, "K") == 0) { + lval *= 1024ULL; + } else if (strwicmp(end, "M") == 0) { + lval *= (1024ULL * 1024ULL); + } else if (strwicmp(end, "G") == 0) { + lval *= (1024ULL * 1024ULL * 1024ULL); + } else if (strwicmp(end, "T") == 0) { + lval *= (1024ULL * 1024ULL * 1024ULL * 1024ULL); + } else if (strwicmp(end, "P") == 0) { + lval *= (1024ULL * 1024ULL * 1024ULL * 1024ULL * 1024ULL); + } else { + return False; + } + } + + *val = (uint64_t)lval; + return True; +} + +BOOL conv_str_u64(const char * str, uint64_t * val) +{ + char * end = NULL; + unsigned long long lval; + + if (str == NULL || *str == '\0') { + return False; + } + + lval = strtoull(str, &end, 10 /* base */); + if (end == NULL || *end != '\0' || end == str) { + return False; + } + + *val = (uint64_t)lval; + return True; +} diff --git a/source4/libcli/cliconnect.c b/source4/libcli/cliconnect.c index fe0ad9c9f5e..9a5236a661c 100644 --- a/source4/libcli/cliconnect.c +++ b/source4/libcli/cliconnect.c @@ -4,6 +4,7 @@ client connect/disconnect routines Copyright (C) Andrew Tridgell 2003-2005 + Copyright (C) James Peach 2005 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 @@ -175,11 +176,33 @@ struct smbcli_state *smbcli_state_init(TALLOC_CTX *mem_ctx) return talloc_zero(mem_ctx, struct smbcli_state); } +/* Insert a NULL at the first separator of the given path and return a pointer + * to the location it was inserted at. + */ +static char * +terminate_path_at_separator(char * path) +{ + char * p; + + if ((p = strchr_m(path, '/'))) { + *p = '\0'; + return(p); + } + + if ((p = strchr_m(path, '\\'))) { + *p = '\0'; + return(p); + } + + /* No terminator. Return pointer to the last byte. */ + return(p + strlen(path)); +} + /* parse a //server/share type UNC name */ BOOL smbcli_parse_unc(const char *unc_name, TALLOC_CTX *mem_ctx, - const char **hostname, const char **sharename) + char **hostname, char **sharename) { char *p; @@ -189,13 +212,10 @@ BOOL smbcli_parse_unc(const char *unc_name, TALLOC_CTX *mem_ctx, } *hostname = talloc_strdup(mem_ctx, &unc_name[2]); - p = strchr_m(&(*hostname)[2],'/'); - if (!p) { - p = strchr_m(&(*hostname)[2],'\\'); - if (!p) return False; - } - *p = 0; + p = terminate_path_at_separator(*hostname); + *sharename = talloc_strdup(mem_ctx, p+1); + p = terminate_path_at_separator(*sharename); return True; } diff --git a/source4/script/tests/test_cifsdd.sh b/source4/script/tests/test_cifsdd.sh new file mode 100755 index 00000000000..9462187f9cd --- /dev/null +++ b/source4/script/tests/test_cifsdd.sh @@ -0,0 +1,84 @@ +#!/bin/sh + +# Basic script to make sure that cifsdd can do both local and remote I/O. + +if [ $# -lt 4 ]; then +cat < local copy" if=$sourcepath of=$destpath bs=$bs || failtest +compare $sourcepath $destpath || failtest + +# Check whether we can do a round trip +runcopy "Testing local -> remote copy" \ + if=$sourcepath of=//$SERVER/$SHARE/$sourcepath bs=$bs || failtest +runcopy "Testing remote ->local copy" \ + if=//$SERVER/$SHARE/$sourcepath of=$destpath bs=$bs || failtest +compare $sourcepath $destpath || failtest + +# Check that copying within the remote server works +runcopy "Testing local -> remote copy" \ + if=//$SERVER/$SHARE/$sourcepath of=//$SERVER/$SHARE/$sourcepath bs=$bs || failtest +runcopy "Testing remote -> remote copy" \ + if=//$SERVER/$SHARE/$sourcepath of=//$SERVER/$SHARE/$destpath bs=$bs || failtest +runcopy "Testing remote ->local copy" \ + if=//$SERVER/$SHARE/$destpath of=$destpath bs=$bs || failtest +compare $sourcepath $destpath || failtest + +done + +rm -f $sourcepath $destpath + +testok $0 $failed diff --git a/source4/script/tests/tests_all.sh b/source4/script/tests/tests_all.sh index e940ec16c1e..d5d20d8f92b 100755 --- a/source4/script/tests/tests_all.sh +++ b/source4/script/tests/tests_all.sh @@ -10,3 +10,4 @@ $SRCDIR/script/tests/test_local.sh || failed=`expr $failed + $?` $SRCDIR/script/tests/test_pidl.sh || failed=`expr $failed + $?` $SRCDIR/script/tests/test_smbclient.sh $SERVER $USERNAME $PASSWORD $DOMAIN $PREFIX || failed=`expr $failed + $?` + $SRCDIR/script/tests/test_cifsdd.sh $SERVER $USERNAME $PASSWORD $DOMAIN || failed=`expr $failed + $?` diff --git a/source4/script/tests/tests_client.sh b/source4/script/tests/tests_client.sh new file mode 100755 index 00000000000..5a3c5eb79af --- /dev/null +++ b/source4/script/tests/tests_client.sh @@ -0,0 +1,2 @@ + $SRCDIR/script/tests/test_smbclient.sh $SERVER $USERNAME $PASSWORD $DOMAIN $PREFIX || failed=`expr $failed + $?` + $SRCDIR/script/tests/test_cifsdd.sh $SERVER $USERNAME $PASSWORD $DOMAIN || failed=`expr $failed + $?` -- 2.34.1