mirror of
https://github.com/samba-team/samba.git
synced 2024-12-23 17:34:34 +03:00
c50125f6ef
(This used to be commit bccfddcafa
)
601 lines
14 KiB
C
601 lines
14 KiB
C
/*
|
|
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, 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(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 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;
|
|
|
|
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 (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)) {
|
|
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)) {
|
|
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 (!dd_flush_block(ofile, iobuf, &data_size, 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 : */
|