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