r13255: New CIFS dd client for use in performance testing. The guts of this is
authorJames Peach <jpeach@samba.org>
Tue, 31 Jan 2006 06:09:18 +0000 (06:09 +0000)
committerGerald (Jerry) Carter <jerry@samba.org>
Wed, 10 Oct 2007 18:51:39 +0000 (13:51 -0500)
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 [new file with mode: 0644]
source4/client/cifsdd.h [new file with mode: 0644]
source4/client/cifsddio.c [new file with mode: 0644]
source4/client/config.mk
source4/lib/util_str.c
source4/libcli/cliconnect.c
source4/script/tests/test_cifsdd.sh [new file with mode: 0755]
source4/script/tests/tests_all.sh
source4/script/tests/tests_client.sh [new file with mode: 0755]

diff --git a/source4/client/cifsdd.c b/source4/client/cifsdd.c
new file mode 100644 (file)
index 0000000..cf3ab17
--- /dev/null
@@ -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("<unknown>");
+}
+
+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 (file)
index 0000000..28d3101
--- /dev/null
@@ -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 (file)
index 0000000..5152840
--- /dev/null
@@ -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 : */
index 2cf5dbb80ad83f03bde8b703aed128b5c6451dc7..10f56da13872f7a028d227efef9f24d3d61ad6a7 100644 (file)
@@ -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
+#################################
+
index 311f81eaf3bd277edfc0bde32491e5f52f4a43e6..6da0063c504d19a0b5df5c00bd90d31746d21c63 100644 (file)
@@ -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;
+}
index fe0ad9c9f5e455d806da95866dfe02cef222b63d..9a5236a661c97eae33537307325e93788e8c893c 100644 (file)
@@ -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 (executable)
index 0000000..9462187
--- /dev/null
@@ -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 <<EOF
+Usage: test_cifsdd.sh SERVER USERNAME PASSWORD DOMAIN
+EOF
+exit 1;
+fi
+
+SERVER=$1
+USERNAME=$2
+PASSWORD=$3
+DOMAIN=$4
+
+DD=bin/cifsdd
+
+SHARE=tmp
+DEBUGLEVEL=4
+
+failed=0
+
+failtest() {
+       failed=`expr $failed + 1`
+}
+
+runcopy() {
+       message="$1"
+       shift
+       
+       testit "$message" $DD --debuglevel=$DEBUGLEVEL -W "$DOMAIN" -U "$USERNAME"%"$PASSWORD" \
+           "$@"
+}
+
+compare() {
+       if [ -r $1 -a -r $2 ] ; then
+           sum1=`sum $1`
+           sum2=`sum $2`
+
+           [[ x"$sum1" = x"$sum2" ]]
+       else
+           false
+       fi
+}
+
+incdir=`dirname $0`
+. $incdir/test_functions.sh
+
+sourcepath=tempfile.src.$$
+destpath=tempfile.dst.$$
+
+# Create a source file with arbitrary contents
+cp $DD $sourcepath
+
+for bs in 512 4k 48k ; do
+
+echo "Testing $bs block size ..."
+
+# Check whether we can do local IO
+runcopy "Testing local -> 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
index e940ec16c1efa10e7aa70dba567065232420c31e..d5d20d8f92b2d59fce10d92a8394cecf79228c01 100755 (executable)
@@ -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 (executable)
index 0000000..5a3c5eb
--- /dev/null
@@ -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 + $?`