PageRenderTime 81ms CodeModel.GetById 13ms app.highlight 53ms RepoModel.GetById 1ms app.codeStats 1ms

/src/pdsh/opt.c

https://code.google.com/
C | 1413 lines | 974 code | 209 blank | 230 comment | 277 complexity | c6d02f5c6dd8d0da47f11488336cdc20 MD5 | raw file
   1/*****************************************************************************\
   2 *  $Id$
   3 *****************************************************************************
   4 *  Copyright (C) 2001-2006 The Regents of the University of California.
   5 *  Produced at Lawrence Livermore National Laboratory (cf, DISCLAIMER).
   6 *  Written by Jim Garlick <garlick@llnl.gov>.
   7 *  UCRL-CODE-2003-005.
   8 *  
   9 *  This file is part of Pdsh, a parallel remote shell program.
  10 *  For details, see <http://www.llnl.gov/linux/pdsh/>.
  11 *  
  12 *  Pdsh is free software; you can redistribute it and/or modify it under
  13 *  the terms of the GNU General Public License as published by the Free
  14 *  Software Foundation; either version 2 of the License, or (at your option)
  15 *  any later version.
  16 *  
  17 *  Pdsh is distributed in the hope that it will be useful, but WITHOUT ANY
  18 *  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  19 *  FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
  20 *  details.
  21 *  
  22 *  You should have received a copy of the GNU General Public License along
  23 *  with Pdsh; if not, write to the Free Software Foundation, Inc.,
  24 *  59 Temple Place, Suite 330, Boston, MA  02111-1307  USA.
  25\*****************************************************************************/
  26
  27#if     HAVE_CONFIG_H
  28#include "config.h"
  29#endif
  30
  31#if HAVE_STRING_H
  32#include <string.h>             /* strcpy */
  33#endif
  34#include <stdlib.h>             /* getenv */
  35#include <pwd.h>                /* getpwuid */
  36#include <sys/param.h>          /* MAXPATHLEN */
  37
  38#include <sys/types.h>
  39#include <sys/stat.h>           /* stat */
  40#if	HAVE_UNISTD_H
  41#include <unistd.h>             /* getopt */
  42#endif
  43
  44#include <errno.h>
  45
  46#include <regex.h>
  47#include <ctype.h>
  48
  49#include "src/common/hostlist.h"
  50#include "src/common/err.h"
  51#include "src/common/list.h"
  52#include "src/common/split.h"
  53#include "src/common/xstring.h"
  54#include "src/common/xmalloc.h"
  55#include "dsh.h"                
  56#include "opt.h"
  57#include "wcoll.h"
  58#include "mod.h"
  59#include "rcmd.h"
  60
  61/*
  62 *  Fallback maximum username length if sysconf(_SC_LOGIN_NAME_MAX) not
  63 *   available or fails. (buffers will be this size + 1 for NUL termination)
  64 */
  65#define DEFAULT_MAX_USERNAME_LENGTH 16
  66
  67#define OPT_USAGE_DSH "\
  68Usage: pdsh [-options] command ...\n\
  69-S                return largest of remote command return values\n"
  70
  71/* -s option only useful on AIX */
  72#if	HAVE_MAGIC_RSHELL_CLEANUP
  73#define OPT_USAGE_STDERR "\
  74-s                separate stderr and stdout\n"
  75#endif
  76
  77
  78#define OPT_USAGE_PCP "\
  79Usage: pdcp [-options] src [src2...] dest\n\
  80-r                recursively copy files\n\
  81-p                preserve modification time and modes\n\
  82-e PATH           specify the path to pdcp on the remote machine\n"
  83/* undocumented "-y"  target must be directory option */
  84/* undocumented "-z"  run pdcp server option */
  85/* undocumented "-Z"  run pdcp client option */
  86
  87#define OPT_USAGE_RPCP "\
  88Usage: rpdcp [-options] src [src2...] dir\n\
  89-r                recursively copy files\n\
  90-p                preserve modification time and modes\n"
  91/* undocumented "-y"  target must be directory option */
  92/* undocumented "-z"  run pdcp server option */
  93/* undocumented "-Z"  run pdcp client option */
  94
  95#define OPT_USAGE_COMMON "\
  96-h                output usage menu and quit\n\
  97-V                output version information and quit\n\
  98-q                list the option settings and quit\n\
  99-b                disable ^C status feature (batch mode)\n\
 100-d                enable extra debug information from ^C status\n\
 101-l user           execute remote commands as user\n\
 102-t seconds        set connect timeout (default is 10 sec)\n\
 103-u seconds        set command timeout (no default)\n\
 104-f n              use fanout of n nodes\n\
 105-w host,host,...  set target node list on command line\n\
 106-x host,host,...  set node exclusion list on command line\n\
 107-R name           set rcmd module to name\n\
 108-M name,...       select one or more misc modules to initialize first\n\
 109-N                disable hostname: labels on output lines\n\
 110-L                list info on all loaded modules and exit\n"
 111/* undocumented "-T testcase" option */
 112/* undocumented "-Q" option */
 113/* undocumented "-K" option -  keep domain name in output */
 114
 115#if	HAVE_MAGIC_RSHELL_CLEANUP
 116#define DSH_ARGS	"sS"
 117#else
 118#define DSH_ARGS    "S"
 119#endif
 120#define PCP_ARGS	"pryzZe:"
 121#define GEN_ARGS	"hLNKR:M:t:cqf:w:x:l:u:bI:dVT:Q"
 122
 123
 124/*
 125 *  Pdsh options string (for getopt) -- initialized
 126 *    in _init_pdsh_options(), and appended to by modules that
 127 *    register new pdsh options.
 128 */    
 129static char *pdsh_options = NULL;
 130
 131/*
 132 *  Pdsh personality
 133 */
 134static pers_t personality = DSH;
 135
 136/*
 137 *  Return the current pdsh "personality"
 138 */
 139pers_t pdsh_personality(void)
 140{
 141    return personality;
 142}
 143
 144/*
 145 *  Remote argv array
 146 */
 147static char **remote_argv;
 148static int    remote_argc;
 149
 150const char **pdsh_remote_argv (void)
 151{
 152    return (const char **) remote_argv;
 153}
 154
 155int pdsh_remote_argc (void)
 156{
 157    return remote_argc;
 158}
 159
 160/*
 161 *  List of explicitly excluded hosts and regex filter options:
 162 */
 163static List exclude_list = NULL;
 164static List regex_list = NULL;
 165
 166static void _usage(opt_t * opt);
 167static void _show_version(void);
 168static int wcoll_args_process (opt_t *opt, char *args);
 169static void wcoll_apply_regex (opt_t *opt, List regexs);
 170static void wcoll_apply_excluded (opt_t *opt, List excludes);
 171static void wcoll_expand (opt_t *opt);
 172
 173static void
 174_init_pdsh_options()
 175{
 176    pdsh_options = Strdup(GEN_ARGS);
 177    if (personality == DSH) {
 178        xstrcat(&pdsh_options, DSH_ARGS);
 179    } else
 180        xstrcat(&pdsh_options, PCP_ARGS);
 181}
 182
 183/*
 184 *  Check whether options in 'opt_table' are available for
 185 *    provisioning.  Register the option if the option's personality
 186 *    allows it.  'opt_table' points to the first module_option listed
 187 *    in the module.
 188 *
 189 *  Returns false if option is already used by pdsh or a pdsh module.
 190 *  Returns true if option was successfully registered.
 191 */
 192bool opt_register(struct pdsh_module_option *opt_table)
 193{
 194    struct pdsh_module_option *p;
 195  
 196    if (opt_table == NULL)
 197        return true;
 198
 199    if (pdsh_options == NULL)
 200        _init_pdsh_options();
 201    
 202    /*  Don't register any options if we can't register all the options
 203     *   in this module 
 204     */
 205    for (p = opt_table; p && (p->opt != 0); p++) {
 206        if (  (personality & p->personality) 
 207           && (strchr(pdsh_options, p->opt) != NULL))
 208            return false;
 209    }
 210
 211    for (p = opt_table; p && (p->opt != 0); p++) {
 212        /* register only if the personality allows this option */
 213        if (p->personality & personality) {
 214            xstrcatchar(&pdsh_options, p->opt);
 215            if (p->arginfo != NULL)
 216                xstrcatchar(&pdsh_options, ':');
 217        }
 218    }
 219
 220    return true;
 221}
 222
 223/*
 224 * Check current path dir, cwd, and argv0 for path to program
 225 */
 226char * _check_path(char *dir, char *cwd, char *argv0)
 227{
 228  char *abspath = NULL;
 229
 230  if (*dir != '/') {
 231    abspath = Strdup(cwd);
 232    xstrcat(&abspath, "/");
 233  }
 234  xstrcat(&abspath, dir);
 235  xstrcat(&abspath, "/");
 236  xstrcat(&abspath, argv0);
 237  
 238  if (access(abspath, R_OK) == 0)
 239      return abspath;
 240
 241  Free((void **) &abspath);
 242
 243  return NULL;
 244}
 245
 246/*
 247 *  Determine absolute path to the program name based on argv0
 248 */
 249char * _find_path(char *argv0) 
 250{
 251    char *abspath = NULL;
 252    char cwd[MAXPATHLEN];
 253
 254    if (*argv0 == '/') {
 255        /* is absolute path */
 256        abspath = Strdup(argv0);
 257        goto done;
 258    }
 259
 260    if (getcwd(cwd, MAXPATHLEN) == NULL) 
 261        errx("%p: getcwd failed: %m\n"); 
 262      
 263    if (*argv0 == '.' || strchr(argv0, '/') != NULL) {
 264        /* path relative */
 265        abspath = Strdup(cwd);
 266        xstrcat(&abspath, "/");
 267        xstrcat(&abspath, argv0);
 268    }
 269    else {
 270      /* argv0 executed in PATH environment var */
 271      char *path, *dir, *p;
 272      
 273      if ((path = Strdup(getenv("PATH"))) == NULL)
 274        errx("%p: getenv PATH failed\n"); 
 275      
 276      dir = path;
 277      while ((p = strchr(dir,':')) != NULL) {
 278        *p = '\0';
 279        
 280        if (strlen(dir) > 0 && (abspath = _check_path(dir,cwd,argv0)) != NULL) {
 281            Free((void **) &path);
 282            goto done;
 283        }
 284
 285        dir = ++p;
 286      }
 287      
 288      if (strlen(dir) > 0)
 289          abspath = _check_path(dir,cwd,argv0);
 290        
 291      Free((void **) &path);
 292    }
 293
 294done:
 295    return abspath;
 296}
 297
 298
 299/*
 300 *   Return a value for the max login name length (username buffer length)
 301 */
 302static int login_name_max_len (void)
 303{
 304    static int maxnamelen = -1;
 305
 306    if (maxnamelen < 0) {
 307#ifdef _SC_LOGIN_NAME_MAX
 308        errno = 0;
 309        if ((maxnamelen = sysconf (_SC_LOGIN_NAME_MAX)) <= 0) {
 310            err ("%p: sysconf(LOGIN_NAME_MAX): %m\n");
 311            maxnamelen = DEFAULT_MAX_USERNAME_LENGTH;
 312        }
 313#else
 314        maxnamelen = DEFAULT_MAX_USERNAME_LENGTH;
 315#endif
 316    }
 317
 318    return (maxnamelen);
 319}
 320
 321static void copy_username (char *dst, const char *src)
 322{
 323    int maxlen = login_name_max_len ();
 324
 325    if (strlen (src) > maxlen)
 326        errx ("%p: Fatal: username '%s' exceeds max username length (%d)\n",
 327                src, maxlen);
 328
 329    strcpy (dst, src);
 330}
 331
 332/*
 333 * Set defaults for various options.
 334 *	opt (IN/OUT)	option struct
 335 */
 336void opt_default(opt_t * opt, char *argv0)
 337{
 338    struct passwd *pw;
 339
 340    opt->progname = xbasename(argv0);
 341    opt->luser = Malloc (login_name_max_len () + 1);
 342    opt->ruser = Malloc (login_name_max_len () + 1);
 343
 344    opt->reverse_copy = false;
 345
 346    if (!strcmp(opt->progname, "pdsh") || !strcmp(opt->progname, "dsh"))
 347        personality = DSH;
 348    else if (!strcmp(opt->progname, "pdcp") 
 349            || !strcmp(opt->progname, "dcp")
 350            || !strcmp(opt->progname, "pcp") )
 351        personality = PCP;
 352    else if (!strcmp(opt->progname, "rpdcp")) {
 353        personality = PCP;
 354        opt->reverse_copy = true;
 355    } else
 356        errx("%p: program must be named pdsh/dsh/pdcp/dcp/pcp/rpdcp\n");
 357
 358    if (pdsh_options == NULL)
 359        _init_pdsh_options();
 360
 361    if ((pw = getpwuid(getuid())) != NULL) {
 362        copy_username (opt->luser, pw->pw_name);
 363        copy_username (opt->ruser, pw->pw_name);
 364        opt->luid = pw->pw_uid;
 365    } else
 366        errx("%p: who are you?\n");
 367
 368    opt->info_only = false;
 369    opt->test_range_expansion = false;
 370    opt->wcoll = NULL;
 371    opt->connect_timeout = CONNECT_TIMEOUT;
 372    opt->command_timeout = 0;
 373    opt->fanout = DFLT_FANOUT;
 374    opt->sigint_terminates = false;
 375    opt->infile_names = NULL;
 376    opt->altnames = false;
 377    opt->debug = false;
 378    opt->labels = true;
 379
 380    opt->rcmd_name = NULL;
 381    opt->misc_modules = NULL;
 382
 383    /*
 384     *  Resolve hostnames by default
 385     */
 386    opt->resolve_hosts = true; 
 387
 388    /*
 389     *  Do not kill all tasks on single failure by default
 390     */
 391    opt->kill_on_fail = false;
 392
 393    /* DSH specific */
 394    opt->dshpath = NULL;
 395    opt->getstat = NULL;
 396    opt->ret_remote_rc = false;
 397    opt->cmd = NULL;
 398    opt->stdin_unavailable = false;
 399#if	HAVE_MAGIC_RSHELL_CLEANUP
 400    opt->separate_stderr = false;    /* save a socket per connection on aix */
 401#else
 402    opt->separate_stderr = true;
 403#endif
 404
 405    opt->local_program_path = _find_path(argv0);
 406
 407    /*
 408     *  By default assume remote path to pdsh/pdcp is the same
 409     *   as local path: (overridden with pdpc -e or PDSH_REMOTE_PDCP_PATH).
 410     */
 411    opt->remote_program_path = Strdup(opt->local_program_path);
 412
 413    /* PCP specific */
 414    opt->outfile_name = NULL;
 415    opt->recursive = false;
 416    opt->preserve = false;
 417    opt->pcp_server = false;
 418    opt->target_is_directory = false;
 419    opt->pcp_client = false;
 420    opt->pcp_client_host = NULL;
 421
 422    return;
 423}
 424
 425static int string_to_int (const char *val, int *p2int)
 426{
 427    char *p;
 428    long n;
 429
 430    errno = 0;
 431    n = strtoul (val, &p, 10);
 432    if (errno || (*p != '\0'))
 433        return (-1);
 434
 435    *p2int = (int) n;
 436
 437    return (0);
 438}
 439
 440/*
 441 * Override default options with environment variables.
 442 *	opt (IN/OUT)	option struct	
 443 */
 444void opt_env(opt_t * opt)
 445{
 446    char *rhs;
 447
 448    if ((rhs = getenv("FANOUT")) != NULL)
 449        if (string_to_int (rhs, &opt->fanout) < 0)
 450            errx ("%p: Invalid environment variable FANOUT=%s\n", rhs);
 451
 452    if ((rhs = getenv("PDSH_RCMD_TYPE")) != NULL)
 453        opt->rcmd_name = Strdup(rhs);
 454
 455    if ((rhs = getenv("PDSH_MISC_MODULES")) != NULL)
 456        opt->misc_modules = Strdup(rhs);
 457
 458    if ((rhs = getenv("DSHPATH")) != NULL) {
 459        struct passwd *pw = getpwnam(opt->luser);
 460        char *shell = "sh";
 461
 462        if (pw && *pw->pw_shell)
 463            shell = xbasename(pw->pw_shell);
 464        /* c shell syntax */
 465        if (!strcmp(shell, "csh") || !strcmp(shell, "tcsh")) {
 466            opt->dshpath = Strdup("setenv PATH ");
 467            xstrcat(&opt->dshpath, rhs);
 468            xstrcat(&opt->dshpath, ";");
 469
 470        } else {                /* bourne shell syntax */
 471            opt->dshpath = Strdup("PATH=");
 472            xstrcat(&opt->dshpath, rhs);
 473            xstrcat(&opt->dshpath, ";");
 474        }
 475    }
 476
 477    if (pdsh_personality() == PCP) {
 478        if ((rhs = getenv ("PDSH_REMOTE_PDCP_PATH")) != NULL) {
 479            Free ((void **) &opt->remote_program_path);
 480            opt->remote_program_path = Strdup (rhs);
 481        }
 482    }
 483}
 484
 485
 486/*
 487 *  Process any options that need to be handled early, i.e.
 488 *   before modules are loaded.
 489 */
 490void opt_args_early (opt_t * opt, int argc, char *argv[])
 491{
 492    int c;
 493    extern int optind;
 494    extern char *optarg;
 495    extern int opterr;
 496
 497    /*
 498     *  Disable error reporting from getopt during early processing,
 499     *   since we won't have access to all the options provided by
 500     *   dlopened modules.
 501     */
 502    opterr = 0;
 503
 504#ifdef __linux
 505    /* Tell glibc getopt to stop eating after the first non-option arg */
 506    putenv("POSIXLY_CORRECT=1");
 507#endif
 508    while ((c = getopt(argc, argv, pdsh_options)) != EOF) {
 509        switch (c) {
 510            case 'M':
 511                if (opt->misc_modules)
 512                    Free ((void **) &opt->misc_modules);
 513                opt->misc_modules = Strdup (optarg);
 514                break;
 515        }
 516    }
 517}
 518
 519static void wcoll_append_excluded (opt_t *opt, char *exclude_args)
 520{
 521    List l = list_split (",", exclude_args);
 522    ListIterator i = list_iterator_create (l);
 523    char *s;
 524
 525    while ((s = list_next (i))) {
 526        char *p = NULL;
 527        xstrcatchar (&p, '-');
 528        xstrcat (&p, s);
 529        wcoll_args_process (opt, p);
 530        Free ((void **) &p);
 531    }
 532
 533    list_iterator_destroy (i);
 534    list_destroy (l);
 535}
 536
 537/*
 538 * Override  default/environment options with command line arguments.
 539 *	opt (IN/OUT)	option struct	
 540 * 	argc (IN)	arg count passed in from main
 541 * 	argv (IN)	arg vector passed in from main
 542 */
 543void opt_args(opt_t * opt, int argc, char *argv[])
 544{
 545    int c;
 546    extern int optind;
 547    extern char *optarg;
 548    extern int opterr;
 549
 550    /*
 551     *  Reset optind after opt_args_early.
 552     */
 553    optind = 1;
 554
 555    /*
 556     * Reinstate getopt error reporting
 557     */
 558    opterr = 1;
 559
 560    while ((c = getopt(argc, argv, pdsh_options)) != EOF) {
 561        switch (c) {
 562
 563        /*
 564         *  The following options were handled in opt_args_early() :
 565         */
 566        case 'M':
 567            break;
 568
 569        /*  Continue processing regular options...
 570         */
 571        case 'N':
 572            opt->labels = false;
 573            break;
 574        case 'L':
 575            mod_list_module_info();
 576            exit(0);
 577            break;
 578        case 'R': 
 579            opt->rcmd_name = Strdup(optarg);
 580            break;
 581        case 'S':              /* get remote command status */
 582            opt->ret_remote_rc = true;
 583            break;
 584        case 'd':              /* debug */
 585            opt->debug = true;
 586            break;
 587        case 'f':              /* fanout */
 588            if (string_to_int (optarg, &opt->fanout) < 0)
 589                errx ("%p: Invalid fanout `%s' passed to -f.\n", optarg);
 590            break;
 591        case 'w':              /* target node list */
 592            if (strcmp(optarg, "-") == 0)
 593                wcoll_args_process (opt, "^-");
 594            else
 595                wcoll_args_process (opt, optarg);
 596            break;
 597        case 'x':              /* exclude node list */
 598            wcoll_append_excluded (opt, optarg);
 599            break;
 600        case 'q':              /* display fanout and wcoll then quit */
 601            opt->info_only = true;
 602            break;
 603/* -s option only useful on AIX */
 604#if	HAVE_MAGIC_RSHELL_CLEANUP
 605        case 's':              /* split stderr and stdout */
 606            opt->separate_stderr = true;
 607            break;
 608#endif
 609        case 't':              /* set connect timeout */
 610            opt->connect_timeout = atoi(optarg);
 611            break;
 612        case 'u':              /* set command timeout */
 613            opt->command_timeout = atoi(optarg);
 614            break;
 615        case 'b':              /* "batch" */
 616            opt->sigint_terminates = true;
 617            break;
 618        case 'l':              /* specify remote username for rshd */
 619            copy_username (opt->ruser, optarg);
 620            break;
 621        case 'r':              /* rcp: copy recursively */
 622            if (pdsh_personality() == PCP)
 623                opt->recursive = true;
 624            else
 625                goto test_module_option;
 626            break;
 627        case 'p':              /* rcp: preserve permissions */
 628            if (pdsh_personality() == PCP)
 629                opt->preserve = true;
 630            else
 631                goto test_module_option;
 632            break;
 633        case 'e':
 634            if (pdsh_personality() == PCP) {
 635                Free ((void **) &opt->remote_program_path);
 636                opt->remote_program_path = Strdup(optarg);
 637            }
 638            else
 639                goto test_module_option;
 640            break;
 641        case 'V':              /* show version */
 642            _show_version();
 643            break;
 644        case 'T':              /* execute testcase */
 645            testcase(atoi(optarg));
 646            break;
 647        case 'Q':              /* info only, expand host ranges */
 648            opt->info_only = true;
 649            opt->test_range_expansion = true;
 650            break;
 651        case 'h':              /* display usage message */
 652            _usage(opt);
 653            break;
 654        case 'K':              /* don't strip host domain in output */
 655            err_no_strip_domain (); 
 656            break;
 657        case 'y':
 658            if (pdsh_personality() == PCP)
 659                opt->target_is_directory = true;  /* is target a dir? */
 660            else
 661                goto test_module_option;
 662            break;
 663        case 'z':
 664            if (pdsh_personality() == PCP)
 665                opt->pcp_server = true;          /* run PDCP server */
 666            else
 667                goto test_module_option;
 668            break;
 669        case 'Z':
 670            if (pdsh_personality() == PCP)
 671                opt->pcp_client = true;          /* run PDCP client */
 672            else
 673                goto test_module_option;
 674            break;
 675        default: test_module_option:
 676            if (mod_process_opt(opt, c, optarg) < 0)
 677               _usage(opt);
 678        }
 679    }
 680
 681    /*
 682     *  Load default module for all hosts (unless overridden)
 683     */
 684    if (opt->rcmd_name == NULL)
 685        opt->rcmd_name = Strdup(rcmd_get_default_module ());
 686    if (opt->rcmd_name != NULL)
 687        if (rcmd_register_default_rcmd(opt->rcmd_name) < 0)
 688            exit(1);
 689
 690
 691    /* 
 692     *  Save beginning of remote argv in case something needs
 693     *   to view the unadulterated version (after shell quoting
 694     *   applied, etc.)
 695     */
 696    remote_argc = argc - optind;
 697    remote_argv = argv + optind;
 698
 699    /* DSH: build command */
 700    if (personality == DSH) {
 701        for (; optind < argc; optind++) {
 702            if (opt->cmd != NULL)
 703                xstrcat(&opt->cmd, " ");
 704            xstrcat(&opt->cmd, argv[optind]);
 705        }
 706    /* PCP: build file list */
 707    } else {
 708        if (!opt->infile_names)
 709            opt->infile_names = list_create(NULL);
 710        
 711        for (; optind < argc - 1; optind++)
 712            list_append(opt->infile_names, argv[optind]);
 713        if (optind < argc) {
 714            /* If this is the initial pdcp call, the last argument is
 715             * the output file/dir.  If this is the pcp_client, the
 716             * last argument is the hostname used for connecting.
 717             */
 718            if (opt->pcp_client)
 719                xstrcat(&opt->pcp_client_host, argv[optind]);
 720            else
 721                xstrcat(&opt->outfile_name, argv[optind]);
 722        }
 723    }
 724
 725    /* ignore wcoll filtering when running pcp server */
 726    if (opt->pcp_server)
 727        return;
 728
 729    /*
 730     *  Give modules a chance to fill in wcoll if it hasn't been already:
 731     */
 732    if (mod_read_wcoll (opt) < 0)
 733        exit (1);
 734
 735    /*
 736     *  If wcoll is still empty, try WCOLL env variable:
 737     */
 738    if (opt->wcoll == NULL) {
 739        char *val = getenv ("WCOLL");
 740        if (val != NULL)
 741            opt->wcoll = read_wcoll (val, NULL);
 742    }
 743
 744    if (opt->wcoll) {
 745        /*
 746         *  Now apply wcoll filtering
 747         */
 748        if (exclude_list) {
 749            wcoll_apply_excluded (opt, exclude_list);
 750            list_destroy (exclude_list);
 751        }
 752        if (regex_list) {
 753            wcoll_apply_regex (opt, regex_list);
 754            list_destroy (regex_list);
 755        }
 756
 757        /*
 758         *  Finally, re-expand wcoll to allow two sets of brackets.
 759         *   (For historical compatibility)
 760         */
 761        wcoll_expand (opt);
 762    }
 763}
 764
 765static void wcoll_expand (opt_t *opt)
 766{
 767    hostlist_t hl = opt->wcoll;
 768    const char *hosts;
 769
 770    /*
 771     *  Create new hostlist for wcoll
 772     */
 773    opt->wcoll = hostlist_create ("");
 774    while ((hosts = hostlist_shift (hl))) {
 775        hostlist_push (opt->wcoll, hosts);
 776        free (hosts);
 777    }
 778
 779    hostlist_destroy (hl);
 780}
 781
 782
 783/* 
 784 * Check if infile_names are legit.
 785 */
 786static int
 787_infile_names_check(opt_t * opt)
 788{
 789    bool verified = true;
 790    ListIterator i;
 791    char *name;
 792
 793    i = list_iterator_create(opt->infile_names);
 794    while ((name = list_next(i))) {
 795        struct stat sb;
 796        if (stat(name, &sb) < 0) {
 797            err("%p: can't stat %s\n", name);
 798            verified = false;
 799            continue;
 800        }
 801        if (!S_ISREG(sb.st_mode) && !S_ISDIR(sb.st_mode)) {
 802            err("%p: not a regular file or directory: %s\n", name);
 803            verified = false;
 804            break;
 805        }
 806        if (S_ISDIR(sb.st_mode) && !opt->recursive) {
 807            err("%p: use -r to copy directories: %s\n", name);
 808            verified = false;
 809            break;
 810        }
 811    }
 812    list_iterator_destroy(i);
 813    return verified;
 814}
 815
 816/*
 817 * Trap a few option inconsitencies.
 818 *	opt (IN)	option struct
 819 */
 820bool opt_verify(opt_t * opt)
 821{
 822    bool verified = true;
 823
 824    /*
 825     *  Call all post option processing functions for modules
 826     */
 827    if (mod_postop(opt) > 0)
 828        verified = false;
 829
 830    /* can't prompt for command if stdin was used for wcoll */
 831    if (personality == DSH && opt->stdin_unavailable && !opt->cmd) {
 832        _usage(opt);
 833        verified = false;
 834    }
 835
 836    if (!opt->pcp_server && !opt->pcp_client) { 
 837        /* wcoll is required */
 838        if (opt->wcoll == NULL || hostlist_count(opt->wcoll) == 0) {
 839            err("%p: no remote hosts specified\n");
 840            verified = false;
 841        }
 842
 843        /* connect and command timeouts must be reasonable */
 844        if (opt->connect_timeout < 0) {
 845            err("%p: connect timeout must be >= 0\n");
 846            verified = false;
 847        }
 848        if (opt->command_timeout < 0) {
 849            err("%p: command timeout must be >= 0\n");
 850            verified = false;
 851        }
 852    }
 853
 854    /* PCP: must have source and destination filename(s) */
 855    if (personality == PCP && !opt->pcp_server && !opt->pcp_client) {
 856        if (!opt->outfile_name || list_is_empty(opt->infile_names)) {
 857            err("%p: pcp requires source and dest filenames\n");
 858            verified = false;
 859        }
 860
 861        if (opt->target_is_directory) {
 862            err("%p: target is directory can only be specified with pcp server\n");
 863            verified = false;
 864        }
 865
 866        /* If reverse copy, the infiles need not exist locally */
 867        if (!opt->reverse_copy) {
 868            if (!_infile_names_check(opt))
 869                verified = false;
 870        }
 871
 872        /* If reverse copy, the destination must be a directory */
 873        if (opt->reverse_copy && opt->outfile_name) {
 874            struct stat statbuf;
 875
 876            if (stat(opt->outfile_name, &statbuf) < 0) {
 877                err("%p: can't stat %s\n", opt->outfile_name);
 878                verified = false;
 879            }
 880
 881            if (!S_ISDIR(statbuf.st_mode)) {
 882                err("%p: reverse copy dest must be a directory\n");
 883                verified = false;
 884            }
 885        }
 886    }
 887
 888    /* PCP: server and client sanity check */
 889    if (personality == PCP && opt->pcp_server && opt->pcp_client) {
 890        err("%p: pcp server and pcp client cannot both be set\n");
 891        verified = false;
 892    }
 893
 894    /* PCP: verify options when -z option specified */
 895    if (personality == PCP  && opt->pcp_server) {
 896        if (opt->infile_names && !list_is_empty(opt->infile_names)) {
 897            err("%p: do not list source files with pcp server\n");
 898            verified = false;
 899        }
 900
 901        if (!opt->outfile_name) {
 902            err("%p: output file must be specified with pcp server\n");
 903            verified = false;
 904        }
 905
 906        if (opt->pcp_client_host) {
 907            err("%p: pcp client host should not be specified with pcp server\n");
 908            verified = false;
 909        }
 910
 911        if (opt->reverse_copy) {
 912            err("%p: reverse copy cannot be specified with pcp server\n");
 913            verified = false;
 914        }
 915    }
 916
 917    /* PCP: verify options when -Z option specified */
 918    if (personality == PCP  && opt->pcp_client) {
 919
 920        opt->reverse_copy = false;
 921
 922        if (!opt->infile_names || list_is_empty(opt->infile_names)) {
 923            err("%p: list source files required for pcp client\n");
 924            verified = false;
 925        }
 926
 927        if (opt->outfile_name) {
 928            err("%p: output file should not be specified with pcp client\n");
 929            verified = false;
 930        }
 931
 932        if (!opt->pcp_client_host) {
 933            err("%p: pcp client host must be specified with pcp client\n");
 934            verified = false;
 935        }
 936
 937        /* If reverse copy the infiles should exist locally */
 938        if (opt->infile_names && !_infile_names_check(opt))
 939            verified = false;
 940    }
 941
 942    return verified;
 943}
 944
 945/* printf args */
 946#define BOOLSTR(x)	((x) ? "Yes" : "No")
 947#define STRORNULL(x)	((x) ? (x) : "none")
 948#define RCMDSTR(x)	(x == RCMD_BSD ? "RCMD_BSD" :  \
 949			  (x == RCMD_K4 ? "RCMD_K4" : \
 950			    (x == RCMD_QSHELL ? "RCMD_QSHELL" : \
 951			      (x == RCMD_SSH ? "RCMD_SSH" : "<Unknown>"))))
 952#define ALLOCSTR(x)	(x == ALLOC_BLOCK ? "ALLOC_BLOCK" : \
 953			  (x == ALLOC_CYCLIC ? "ALLOC_CYCLIC" : "<Unknown>"))
 954
 955/*
 956 * List the current options.
 957 *	opt (IN)	option list
 958 */
 959void opt_list(opt_t * opt)
 960{
 961    char wcoll_str[1024];
 962    int n;
 963
 964    if (personality == DSH) {
 965        out("-- DSH-specific options --\n");
 966        out("Separate stderr/stdout	%s\n",
 967            BOOLSTR(opt->separate_stderr));
 968        out("Path prepended to cmd	%s\n", STRORNULL(opt->dshpath));
 969        out("Appended to cmd         %s\n", STRORNULL(opt->getstat));
 970        out("Command:		%s\n", STRORNULL(opt->cmd));
 971    } else {
 972        char infiles [4096];
 973        out("-- PCP-specific options --\n");
 974        if (list_join (infiles, sizeof (infiles), ", ", opt->infile_names))
 975            out("Infile(s)		%s\n", infiles);
 976        out("Outfile			%s\n", STRORNULL(opt->outfile_name));
 977        out("Recursive		%s\n", BOOLSTR(opt->recursive));
 978        out("Preserve mod time/mode	%s\n", BOOLSTR(opt->preserve));
 979        if (opt->pcp_server) {
 980            out("pcp server         	%s\n", BOOLSTR(opt->pcp_server));
 981            out("target is directory	%s\n", BOOLSTR(opt->target_is_directory));
 982        }
 983    }
 984
 985    if (!opt->pcp_server) {
 986        out("Full program pathname	%s\n", STRORNULL(opt->local_program_path));
 987        out("Remote program path	%s\n",
 988                STRORNULL(opt->remote_program_path));
 989        out("\n-- Generic options --\n");
 990        out("Local username		%s\n", opt->luser);
 991        out("Local uid     		%d\n", opt->luid);
 992        out("Remote username		%s\n", opt->ruser);
 993        out("Rcmd type		%s\n", STRORNULL(opt->rcmd_name));
 994        out("one ^C will kill pdsh   %s\n", BOOLSTR(opt->sigint_terminates));
 995        out("Connect timeout (secs)	%d\n", opt->connect_timeout);
 996        out("Command timeout (secs)	%d\n", opt->command_timeout);
 997        out("Fanout			%d\n", opt->fanout);
 998        out("Display hostname labels	%s\n", BOOLSTR(opt->labels));
 999        out("Debugging       	%s\n", BOOLSTR(opt->debug));
1000
1001        out("\n-- Target nodes --\n");
1002        if (opt->test_range_expansion) {
1003            n = hostlist_deranged_string(opt->wcoll, sizeof(wcoll_str),
1004                                         wcoll_str);
1005        } else {
1006            n = hostlist_ranged_string(opt->wcoll, sizeof(wcoll_str),
1007                                       wcoll_str);
1008        }
1009
1010        if (n < 0)
1011            out("%s[truncated]\n", wcoll_str);
1012        else
1013            out("%s\n", wcoll_str);
1014    }
1015}
1016
1017/*
1018 * Free heap-allocated memory associated with options, etc.
1019 *	opt (IN/OUT)	option struct
1020 */
1021void opt_free(opt_t * opt)
1022{
1023    if (opt->wcoll != NULL)
1024        hostlist_destroy(opt->wcoll);
1025    if (opt->cmd != NULL)
1026        Free((void **) &opt->cmd);
1027    if (opt->rcmd_name != NULL)
1028        Free((void **) &opt->rcmd_name);
1029    if (opt->misc_modules != NULL)
1030        Free((void **) &opt->misc_modules);
1031    if (pdsh_options)
1032        Free((void **) &pdsh_options);
1033    if (opt->dshpath)
1034        Free((void **) &opt->dshpath);
1035    if (opt->local_program_path)
1036        Free((void **) &opt->local_program_path);
1037    if (opt->remote_program_path)
1038        Free((void **) &opt->remote_program_path);
1039    if (opt->infile_names)
1040        list_destroy(opt->infile_names);
1041    if (opt->luser)
1042        Free((void **) &opt->luser);
1043    if (opt->ruser)
1044        Free((void **) &opt->ruser);
1045
1046    rcmd_exit();
1047}
1048
1049/*
1050 *  Returns a string of comma separated module names of type `type'
1051 *  Returns NULL if no modules of this type are loaded.
1052 */
1053static int _module_list_string(char *type, char *buf, int len)
1054{
1055    List l = NULL;
1056    int  n = 0;
1057
1058    if (mod_count(type) == 0)
1059        return (0);
1060
1061    l = mod_get_module_names(type);
1062    n = list_join(buf, len, ",", l);
1063    list_destroy(l);
1064
1065    return (n);
1066}
1067
1068static int _module_list_uninitialized (char *type, char *buf, int len)
1069{
1070    List l = NULL;
1071    int  n = 0;
1072
1073    if (mod_count(type) == 0)
1074        return (0);
1075
1076    l = mod_get_uninitialized_module_names(type);
1077    n = list_join(buf, len, ",", l);
1078    list_destroy(l);
1079
1080    return (n);
1081
1082}
1083
1084static char *_rcmd_module_list(char *buf, int maxlen)
1085{
1086    int len, len2;
1087    char rbuf [1024];
1088    int n;
1089
1090    n = _module_list_string ("rcmd", rbuf, sizeof (rbuf));
1091
1092    len = snprintf(buf, maxlen, "%s", n ? rbuf : "(none)");
1093    if ((len < 0) || (len >= maxlen)) 
1094        goto done;
1095
1096    if (mod_count("rcmd") > 1) {
1097        char *def = rcmd_get_default_module();
1098        len2 = snprintf ( buf+len, maxlen-len, " (default: %s)",
1099                          def ? def : "none" );
1100        if (len2 < 0)
1101            len = -1;
1102        else
1103            len += len2;
1104    }
1105
1106done:
1107    if ((len < 0) || (len > maxlen)) 
1108        snprintf(buf + maxlen - 12, 12, "[truncated]"); 
1109
1110    buf[maxlen - 1] = '\0';
1111    return buf;
1112}
1113
1114/*
1115 * Spit out all the options and their one-line synopsis for the user, 
1116 * then exit.
1117 */
1118static void _usage(opt_t * opt)
1119{
1120    char buf[1024];
1121
1122    if (personality == DSH) {
1123        err(OPT_USAGE_DSH);
1124#if	HAVE_MAGIC_RSHELL_CLEANUP
1125        err(OPT_USAGE_STDERR);
1126#endif
1127    } else if (!opt->reverse_copy) /* PCP */
1128        err(OPT_USAGE_PCP);
1129    else 
1130        err(OPT_USAGE_RPCP);
1131
1132    err(OPT_USAGE_COMMON);
1133
1134    mod_print_all_options(18);
1135
1136    err("available rcmd modules: %s\n", _rcmd_module_list(buf, 1024));
1137
1138    exit(1);
1139}
1140
1141
1142static void _show_version(void)
1143{
1144    char buf[1024];
1145    extern char *pdsh_version;
1146    int n;
1147
1148    printf("%s\n", pdsh_version);
1149    printf("rcmd modules: %s\n", _rcmd_module_list(buf, sizeof (buf)));
1150
1151    n = _module_list_string("misc", buf, sizeof (buf));
1152    printf("misc modules: %s", n ? buf : "(none)");
1153
1154    if ((n = _module_list_uninitialized ("misc", buf, sizeof (buf)))) {
1155        printf (" (*conflicting: %s)\n", buf);
1156	printf ("[* To force-load a conflicting module,"
1157	        " use the -M <name> option]\n");
1158    }
1159    else
1160        printf ("\n");
1161
1162    exit(0);
1163}
1164
1165/*
1166 *  Take a string `hosts' possibly of form "rcmd_type:user@hostlist" and
1167 *    place the hostlist part of the string in *hptr and the rcmd part
1168 *    of the string in rptr. Returns nonzero if an rcmd_type was found,
1169 *    zero otherwise (in which case rptr is not touched).
1170 */
1171static int get_host_rcmd_type (char *hosts, char **rptr, char **hptr, 
1172                               char **uptr)
1173{
1174    char *p = hosts;
1175    char *q;
1176    *hptr = hosts;
1177
1178    p = strchr (hosts, ':');
1179    q = strchr (hosts, '@');
1180
1181    if (p && q && p > q)
1182        errx ("Host spec \"%s\" not of form [rcmd_type:][user@]hosts\n", hosts);
1183
1184    /*
1185     *  If we found a single ':' character, then everything
1186     *   preceeding that is the rcmd type.  Otherwise, we ignore
1187     *   presence of all colons. This can be done b/c even if there
1188     *   were another colon later in the string, the string preceeding
1189     *   it can not be an rcmd type since colon is not extant in rcmd
1190     *   type names.
1191     */
1192    if (p && (*(p+1) != ':')) {
1193        *rptr = *hptr;
1194        *p++ = '\0';
1195        *hptr = p;
1196    }
1197
1198    /*
1199     *  If we found a '@' char then what precedes it is username.
1200     */
1201    if (q) {
1202        *uptr = *hptr;
1203        *q++ = '\0';
1204        *hptr = q;
1205    }
1206
1207    return (1);
1208}
1209
1210void free_f (void *x)
1211{
1212    Free (&x);
1213}
1214
1215struct regex_info {
1216    int     exclude;
1217    int     cflags;
1218    int     eflags;
1219    int     compiled;
1220    char *  pattern;
1221    regex_t reg;
1222};
1223
1224void regex_info_destroy (struct regex_info *re)
1225{
1226    if (re->compiled)
1227        regfree (&re->reg);
1228    if (re->pattern)
1229        Free ((void **) &re->pattern);
1230    Free ((void **) &re);
1231}
1232
1233struct regex_info * regex_info_create (const char *r, int exclude)
1234{
1235    int rc;
1236    struct regex_info *re = Malloc (sizeof (*re));
1237
1238    re->pattern = Strdup (r);
1239    re->exclude = exclude;
1240    re->cflags =  REG_EXTENDED | REG_NOSUB;
1241    re->eflags =  0;
1242    re->compiled = 0;
1243
1244    if ((rc = regcomp (&re->reg, re->pattern, re->cflags)) != 0) {
1245        char msg [4096];
1246        regerror (rc, &re->reg, msg, sizeof (msg));
1247        err ("%p: Error %s pattern \"%s\": %s\n",
1248                re->exclude ? "excluding" : "matching",
1249                re->pattern, msg);
1250        regex_info_destroy (re);
1251        return (NULL);
1252    }
1253
1254    re->compiled = 1;
1255    return (re);
1256}
1257
1258
1259void hostlist_filter_regex (hostlist_t hl, struct regex_info *re)
1260{
1261    char *host;
1262    hostlist_iterator_t i;
1263
1264    i = hostlist_iterator_create (hl);
1265    while ((host = hostlist_next (i))) {
1266        int rc = regexec (&re->reg, host, 0, NULL, re->eflags);
1267        if ((re->exclude && rc == 0) || (!re->exclude && rc == REG_NOMATCH))
1268            hostlist_remove (i);
1269        free (host);
1270    }
1271    hostlist_iterator_destroy (i);
1272}
1273
1274
1275static void list_push_hostlist (List l, hostlist_t hl)
1276{
1277    size_t n = 4096;
1278    char *s = Malloc (n);
1279
1280    while ((hostlist_ranged_string (hl, n-1, s) < 0) && (n*=2 < 0x7fffff)) {
1281        Realloc ((void **) &s, n);
1282    }
1283
1284    list_push (l, s);
1285}
1286
1287
1288static void hostlist_assign (hostlist_t *hlp, hostlist_t hl2)
1289{
1290    if (*hlp == NULL)
1291        *hlp = hostlist_create ("");
1292    hostlist_push_list (*hlp, hl2);
1293}
1294
1295static int wcoll_arg_process (char *arg, opt_t *opt)
1296{
1297    struct regex_info *re;
1298    int excluded = 0;
1299    char *p = arg;
1300
1301    if (exclude_list == NULL)
1302        exclude_list = list_create (free_f);
1303
1304    if (regex_list == NULL)
1305        regex_list = list_create ((ListDelF) regex_info_destroy);
1306
1307    /*
1308     *  Check for excluded arg
1309     */
1310    if (*p == '-') {
1311        excluded = 1;
1312        p++;
1313    }
1314
1315    /*
1316     *  Move past any leading whitespace
1317     */
1318    while (isspace (*p))
1319        p++;
1320
1321    if (*p == '^') {
1322        hostlist_t hl = read_wcoll (p+1, NULL);
1323        if (hl == NULL)
1324            errx ("%p: Error reading wcoll: %s: %m\n", p+1);
1325        if (excluded)
1326            list_push_hostlist (exclude_list, hl);
1327        else
1328            hostlist_assign (&opt->wcoll, hl);
1329        hostlist_destroy (hl);
1330    }
1331    else if (*p == '/') {
1332        int len;
1333
1334        ++p;
1335        len = strlen (p);
1336        if (p [len - 1] == '/')
1337            p [len - 1] = '\0';
1338
1339        if ((re = regex_info_create (p, excluded)) == NULL)
1340            errx ("%p: Fatal error\n");
1341
1342        list_push (regex_list, re);
1343    }
1344    else {
1345        if (excluded) {
1346            list_push (exclude_list, Strdup (p));
1347        }
1348        else {
1349            char *rcmd_type = NULL;
1350            char *hosts, *user = NULL;
1351
1352            if (!opt->wcoll)
1353                opt->wcoll = hostlist_create ("");
1354
1355            get_host_rcmd_type (p, &rcmd_type, &hosts, &user);
1356            hostlist_push (opt->wcoll, hosts);
1357            if (rcmd_type || user) {
1358                if (rcmd_register_defaults (hosts, rcmd_type, user) < 0)
1359                    errx ("%p: Failed to register rcmd \"%s\" for \"%s\"\n",
1360                            rcmd_type, hosts);
1361            }
1362        }
1363    }
1364
1365    return 0;
1366}
1367
1368static int wcoll_args_process (opt_t *opt, char * args)
1369{
1370    int rc;
1371    List l = list_split (",", args);
1372    rc = list_for_each (l, (ListForF) wcoll_arg_process, opt);
1373    list_destroy (l);
1374    return (rc);
1375}
1376
1377static void wcoll_apply_regex (opt_t *opt, List regexs)
1378{
1379    struct regex_info *re;
1380    ListIterator i;
1381
1382    if (!opt->wcoll || !regexs)
1383        return;
1384
1385    /*
1386     *  filter any supplied regular expression args
1387     */
1388    i = list_iterator_create (regexs);
1389    while ((re = list_next (i)))
1390        hostlist_filter_regex (opt->wcoll, re);
1391    list_iterator_destroy (i);
1392}
1393
1394static void wcoll_apply_excluded (opt_t *opt, List excludes)
1395{
1396    ListIterator i;
1397    char *arg;
1398
1399    if (!opt->wcoll || !excludes)
1400        return;
1401
1402    /*
1403     *  filter explicitly excluded hosts:
1404     */
1405    i = list_iterator_create (excludes);
1406    while ((arg = list_next (i)))
1407        hostlist_delete (opt->wcoll, arg);
1408    list_iterator_destroy (i);
1409}
1410
1411/*
1412 * vi:tabstop=4 shiftwidth=4 expandtab
1413 */