/* Copyright Red Hat, Inc. 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, 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; see the file COPYING. If not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* Local includes */ #include "xvm.h" #include "simple_auth.h" #include "mcast.h" #include "tcp_listener.h" #include "options.h" #define SCHEMA_COMPAT '\xfe' /* Assignment functions */ static inline void assign_debug(fence_virt_args_t *args, struct arg_info *arg, char *value) { if (!value) { /* GNU getopt sets optarg to NULL for options w/o a param We rely on this here... */ args->debug++; return; } args->debug = atoi(value); if (args->debug < 0) { args->debug = 1; } } static inline void assign_family(fence_virt_args_t *args, struct arg_info *arg, char *value) { if (!value) return; if (!strcasecmp(value, "ipv4")) { args->net.family = PF_INET; } else if (!strcasecmp(value, "ipv6")) { args->net.family = PF_INET6; } else if (!strcasecmp(value, "auto")) { args->net.family = 0; } else { printf("Unsupported family: '%s'\n", value); args->flags |= F_ERR; } } static inline void assign_address(fence_virt_args_t *args, struct arg_info *arg, char *value) { if (!value) return; if (args->net.addr) free(args->net.addr); args->net.addr = strdup(value); } static inline void assign_ip_address(fence_virt_args_t *args, struct arg_info *arg, char *value) { if (!value) return; if (args->net.ipaddr) free(args->net.ipaddr); args->net.ipaddr = strdup(value); } static inline void assign_channel_address(fence_virt_args_t *args, struct arg_info *arg, char *value) { if (!value) return; if (args->serial.address) free(args->serial.address); args->serial.address = strdup(value); } static inline void assign_port(fence_virt_args_t *args, struct arg_info *arg, char *value) { char *p; int ret; if (!value) return; ret = strtol(value, &p, 0); if (ret <= 0 || ret >= 65536 || *p != '\0') { printf("Invalid port: '%s'\n", value); args->flags |= F_ERR; } else args->net.port = ret; } static inline void assign_cid(fence_virt_args_t *args, struct arg_info *arg, char *value) { char *p; unsigned long ret; if (!value) { args->net.cid = 2; return; } ret = strtoul(value, &p, 0); if (!p || *p != '\0' || ret < 2 || ret >= 0xffffffff) { printf("Invalid CID: '%s'\n", value); args->flags |= F_ERR; } else args->net.cid = ret; } static inline void assign_interface(fence_virt_args_t *args, struct arg_info *arg, char *value) { int ret; if (!value) return; ret = if_nametoindex(value); if (ret <= 0) { printf("Invalid interface: %s: %s\n", value, strerror(errno)); args->net.ifindex = 0; } args->net.ifindex = ret; } static inline void assign_retrans(fence_virt_args_t *args, struct arg_info *arg, char *value) { char *p; int ret; if (!value) return; ret = strtol(value, &p, 0); if (ret <= 0 || *p != '\0') { printf("Invalid retransmit time: '%s'\n", value); args->flags |= F_ERR; } else args->retr_time = ret; } static inline void assign_hash(fence_virt_args_t *args, struct arg_info *arg, char *value) { if (!value) return; if (!strcasecmp(value, "none")) { args->net.hash = HASH_NONE; } else if (!strcasecmp(value, "sha1")) { args->net.hash = HASH_SHA1; } else if (!strcasecmp(value, "sha256")) { args->net.hash = HASH_SHA256; } else if (!strcasecmp(value, "sha512")) { args->net.hash = HASH_SHA512; } else { printf("Unsupported hash: %s\n", value); args->flags |= F_ERR; } } static inline void assign_auth(fence_virt_args_t *args, struct arg_info *arg, char *value) { if (!value) return; if (!strcasecmp(value, "none")) { args->net.auth = AUTH_NONE; } else if (!strcasecmp(value, "sha1")) { args->net.auth = AUTH_SHA1; } else if (!strcasecmp(value, "sha256")) { args->net.auth = AUTH_SHA256; } else if (!strcasecmp(value, "sha512")) { args->net.auth = AUTH_SHA512; } else { printf("Unsupported auth type: %s\n", value); args->flags |= F_ERR; } } static inline void assign_key(fence_virt_args_t *args, struct arg_info *arg, char *value) { struct stat st; if (!value) return; if (args->net.key_file) free(args->net.key_file); args->net.key_file = strdup(value); if (stat(value, &st) == -1) { printf("Invalid key file: '%s' (%s)\n", value, strerror(errno)); args->flags |= F_ERR; } } static inline void assign_op(fence_virt_args_t *args, struct arg_info *arg, char *value) { if (!value) return; if (!strcasecmp(value, "null")) { args->op = FENCE_NULL; } else if (!strcasecmp(value, "on")) { args->op = FENCE_ON; } else if (!strcasecmp(value, "off")) { args->op = FENCE_OFF; } else if (!strcasecmp(value, "reboot")) { args->op = FENCE_REBOOT; } else if (!strcasecmp(value, "status")) { args->op = FENCE_STATUS; } else if (!strcasecmp(value, "monitor")) { args->op = FENCE_DEVSTATUS; } else if (!strcasecmp(value, "list") || !strcasecmp(value, "list-status")) { args->op = FENCE_HOSTLIST; } else if (!strcasecmp(value, "metadata")) { args->op = FENCE_METADATA; } else if (!strcasecmp(value, "validate-all")) { args->op = FENCE_VALIDATEALL; } else { printf("Unsupported operation: %s\n", value); args->flags |= F_ERR; } } static inline void assign_device(fence_virt_args_t *args, struct arg_info *arg, char *value) { if (!value) return; args->serial.device = strdup(value); } static inline void assign_params(fence_virt_args_t *args, struct arg_info *arg, char *value) { if (!value) return; args->serial.speed = strdup(value); } static inline void assign_domain(fence_virt_args_t *args, struct arg_info *arg, char *value) { if (args->domain) { printf("Domain/UUID may not be specified more than once\n"); args->flags |= F_ERR; return; } if (!value) return; args->domain = strdup(value); if (strlen(value) <= 0) { printf("Invalid domain name\n"); args->flags |= F_ERR; } if (strlen(value) >= MAX_DOMAINNAME_LENGTH) { errno = ENAMETOOLONG; printf("Invalid domain name: '%s' (%s)\n", value, strerror(errno)); args->flags |= F_ERR; } } static inline void assign_uuid_lookup(fence_virt_args_t *args, struct arg_info *arg, char *value) { if (!value) { /* GNU getopt sets optarg to NULL for options w/o a param We rely on this here... */ args->flags |= F_USE_UUID; return; } args->flags |= ( !!atoi(value) ? F_USE_UUID : 0); } static inline void assign_timeout(fence_virt_args_t *args, struct arg_info *arg, char *value) { char *p; int ret; if (!value) return; ret = strtol(value, &p, 0); if (ret <= 0 || *p != '\0') { printf("Invalid timeout: '%s'\n", value); args->flags |= F_ERR; } else args->timeout = ret; } static inline void assign_delay(fence_virt_args_t *args, struct arg_info *arg, char *value) { char *p; int ret; if (!value) return; ret = strtol(value, &p, 0); if (ret < 0 || *p != '\0') { printf("Invalid delay: '%s'\n", value); args->flags |= F_ERR; } else args->delay = ret; } static inline void assign_help(fence_virt_args_t *args, struct arg_info *arg, char *value) { args->flags |= F_HELP; } static inline void assign_version(fence_virt_args_t *args, struct arg_info *arg, char *value) { args->flags |= F_VERSION; } static void print_desc_xml(const char *desc) { const char *d; for (d = desc; *d; d++) { switch (*d) { case '<': printf("<"); break; case '>': printf(">"); break; default: printf("%c", *d); } } } /** ALL valid command line and stdin arguments for this fencing agent */ static struct arg_info _arg_info[] = { { '\xff', NULL, "agent", NULL, 0, 0, "string", NULL, "Not user serviceable", NULL }, { '\xff', NULL, "self", NULL, 0, 0, "string", NULL, "Not user serviceable", NULL }, { '\xff', NULL, "nodename", NULL, 0, 0, "string", NULL, "Not user serviceable", NULL }, { 'd', "-d", "debug", NULL, 0, 0, "boolean", NULL, "Specify (stdin) or increment (command line) debug level", assign_debug }, { 'i', "-i ", "ip_family", NULL, 0, 0, "string", "auto", "IP Family ([auto], ipv4, ipv6)", assign_family }, { 'a', "-a
", "multicast_address", NULL, 0, 0, "string", NULL, "Multicast address (default=" IPV4_MCAST_DEFAULT " / " IPV6_MCAST_DEFAULT ")", assign_address }, { 'T', "-T
", "ipaddr", NULL, 0, 0, "string", "127.0.0.1", "IP address to connect to in TCP mode (default=" IPV4_TCP_ADDR_DEFAULT " / " IPV6_TCP_ADDR_DEFAULT ")", assign_ip_address }, { 'S', "-S ", "vsock", NULL, 0, 0, "integer", "2", "vm socket CID to connect to in vsock mode", assign_cid }, { 'A', "-A
", "channel_address", NULL, 0, 0, "string", "10.0.2.179", "VM Channel IP address (default=" DEFAULT_CHANNEL_IP ")", assign_channel_address }, { 'p', "-p ", "ipport", NULL, 0, 0, "string", "1229", "TCP, Multicast, VMChannel, or VM socket port (default=1229)", assign_port }, { 'I', "-I ", "interface", NULL, 0, 0, "string", NULL, "Network interface name to listen on", assign_interface }, { 'r', "-r ", "retrans", NULL, 0, 0, "string", "20", "Multicast retransmit time (in 1/10sec; default=20)", assign_retrans }, { 'c', "-c ", "hash", NULL, 0, 0, "string", "sha256", "Packet hash strength (none, sha1, [sha256], sha512)", assign_hash }, { 'C', "-C ", "auth", NULL, 0, 0, "string", "sha256", "Authentication (none, sha1, [sha256], sha512)", assign_auth }, { 'k', "-k ", "key_file", NULL, 0, 0, "string", DEFAULT_KEY_FILE, "Shared key file (default=" DEFAULT_KEY_FILE ")", assign_key }, { 'D', "-D ", "serial_device", NULL, 0, 0, "string", DEFAULT_SERIAL_DEVICE, "Serial device (default=" DEFAULT_SERIAL_DEVICE ")", assign_device }, { 'P', "-P ", "serial_params", NULL, 0, 0, "string", DEFAULT_SERIAL_SPEED, "Serial Parameters (default=" DEFAULT_SERIAL_SPEED ")", assign_params }, { '\xff', NULL, "option", /* Deprecated */ NULL, 0, 0, "string", "reboot", "Fencing option (null, off, on, [reboot], status, list, list-status, monitor, validate-all, metadata)", assign_op }, { 'o', "-o ", "action", NULL, 0, 0, "string", "reboot", "Fencing action (null, off, on, [reboot], status, list, list-status, monitor, validate-all, metadata)", assign_op }, { 'n', "-n ", "plug", "port", 0, 0, "string", NULL, "Virtual Machine (domain name) to fence", assign_domain }, { 'H', "-H ", "port", NULL, 1, 0, "string", NULL, "Virtual Machine (domain name) to fence", assign_domain }, { SCHEMA_COMPAT, NULL, "domain", NULL, 0, 0, "string", NULL, "Virtual Machine (domain name) to fence (deprecated; use port)", assign_domain }, { 'u', "-u", "use_uuid", NULL, 0, 0, "string", "0", "Treat [domain] as UUID instead of domain name. This is provided for compatibility with older fence_xvmd installations.", assign_uuid_lookup }, { 't', "-t ", "timeout", NULL, 0, 0, "string", "30", "Fencing timeout (in seconds; default=30)", assign_timeout }, { 'h', "-h", NULL, NULL, 0, 0, "boolean", "0", "Help", assign_help }, { '?', "-?", NULL, NULL, 0, 0, "boolean", "0", "Help (alternate)", assign_help }, { 'w', "-w ", "delay", NULL, 0, 0, "string", "0", "Fencing delay (in seconds; default=0)", assign_delay }, { 'V', "-V", NULL, NULL, 0, 0, "boolean", "0", "Display version and exit", assign_version }, /* Terminator */ { 0, NULL, NULL, NULL, 0, 0, NULL, NULL, NULL, NULL } }; static struct arg_info * find_arg_by_char(char arg) { int x = 0; for (x = 0; _arg_info[x].opt != 0; x++) { if (_arg_info[x].opt == arg) return &_arg_info[x]; } return NULL; } static struct arg_info * find_arg_by_string(char *arg) { int x = 0; for (x = 0; _arg_info[x].opt != 0; x++) { if (!_arg_info[x].stdin_opt) continue; if (!strcasecmp(_arg_info[x].stdin_opt, arg)) return &_arg_info[x]; } return NULL; } /* ============================================================= */ /** Initialize an args structure. @param args Pointer to args structure to initialize. */ void args_init(fence_virt_args_t *args) { args->domain = NULL; //args->uri = NULL; args->op = FENCE_REBOOT; args->net.key_file = strdup(DEFAULT_KEY_FILE); args->net.hash = DEFAULT_HASH; args->net.auth = DEFAULT_AUTH; args->net.addr = NULL; args->net.ipaddr = NULL; args->net.cid = 0; args->net.port = DEFAULT_MCAST_PORT; args->net.ifindex = 0; args->net.family = 0; /* auto */ args->serial.device = NULL; args->serial.speed = strdup(DEFAULT_SERIAL_SPEED); args->serial.address = strdup(DEFAULT_CHANNEL_IP); args->timeout = 30; args->retr_time = 20; args->flags = 0; args->debug = 0; args->delay = 0; } #define _pr_int(piece) printf(" %s = %d\n", #piece, piece) #define _pr_str(piece) printf(" %s = %s\n", #piece, piece) /** Prints out the contents of an args structure for debugging. @param args Pointer to args structure to print out. */ void args_print(fence_virt_args_t *args) { printf("-- args @ %p --\n", args); _pr_str(args->domain); _pr_int(args->op); _pr_int(args->mode); _pr_int(args->debug); _pr_int(args->timeout); _pr_int(args->delay); _pr_int(args->retr_time); _pr_int(args->flags); _pr_str(args->net.addr); _pr_str(args->net.ipaddr); _pr_int(args->net.cid); _pr_str(args->net.key_file); _pr_int(args->net.port); _pr_int(args->net.hash); _pr_int(args->net.auth); _pr_int(args->net.family); _pr_int(args->net.ifindex); _pr_str(args->serial.device); _pr_str(args->serial.speed); _pr_str(args->serial.address); printf("-- end args --\n"); } /** Print out arguments and help information based on what is allowed in the getopt string optstr. @param progname Program name. @param optstr Getopt(3) style options string @param print_stdin 0 = print command line options + description, 1 = print fence-style stdin args + description */ static char * find_rev(const char *start, char *curr, char c) { while (curr > start) { if (*curr == c) return curr; --curr; } return NULL; } static void output_help_text(int arg_width, int help_width, const char *arg, const char *desc) { char out_buf[4096]; char *p, *start; const char *arg_print = arg; int len; memset(out_buf, 0, sizeof(out_buf)); strncpy(out_buf, desc, sizeof(out_buf) - 1); start = out_buf; do { p = NULL; len = strlen(start); if (len > help_width) { p = start + help_width; p = find_rev(start, p, ' '); if (p) { *p = 0; p++; } } printf(" %*.*s %*.*s\n", -arg_width, arg_width, arg_print, -help_width, help_width, start); if (!p) return; if (arg == arg_print) arg_print = " "; start = p; } while(1); } void args_usage(char *progname, const char *optstr, int print_stdin) { int x; struct arg_info *arg; if (!print_stdin) { if (progname) { printf("usage: %s [args]\n\nNOTE: reboot-action does not power on nodes that are powered off.\n\n", progname); } else { printf("usage: fence_virt [args]\n\nNOTE: reboot-action does not power on nodes that are powered off.\n\n"); } } for (x = 0; x < strlen(optstr); x++) { arg = find_arg_by_char(optstr[x]); if (!arg || arg->deprecated) continue; if (print_stdin) { if (arg && arg->stdin_opt) output_help_text(20, 55, arg->stdin_opt, arg->desc); } else { output_help_text(20, 55, arg->opt_desc, arg->desc); } } printf("\n"); } void args_metadata(char *progname, const char *optstr) { int x; struct arg_info *arg; printf("\n"); printf("\n", basename(progname)); printf("%s is an I/O Fencing agent which can be used with " "virtual machines.\n\nNOTE: reboot-action does not power on nodes that are powered off." "\n", basename(progname)); printf("https://libvirt.org\n"); printf("\n"); for (x = 0; x < strlen(optstr); x++) { arg = find_arg_by_char(optstr[x]); if (!arg) continue; if (!arg->stdin_opt) continue; if (arg->obsoletes) printf("\t\n", arg->stdin_opt, (!strcmp(arg->content_type, "boolean") || arg->default_value) ? 0 : 1, arg->obsoletes); else if (arg->deprecated) printf("\t\n", arg->stdin_opt, (!strcmp(arg->content_type, "boolean") || arg->default_value) ? 0 : 1, arg->deprecated); else printf("\t\n", arg->stdin_opt, (!strcmp(arg->content_type, "boolean") || arg->default_value || !strcmp(arg->stdin_opt, "multicast_address")) ? 0 : 1); printf("\t\t\n",arg->opt); if (arg->default_value) { printf("\t\t\n", arg->content_type, arg->default_value); } else { printf("\t\t\n", arg->content_type); } printf("\t\t"); print_desc_xml(arg->desc); printf("\n"); printf("\t\n"); } for (x = 0; _arg_info[x].opt != 0; x++) { if (_arg_info[x].opt != SCHEMA_COMPAT) continue; arg = &_arg_info[x]; printf("\t\n", arg->stdin_opt, (!strcmp(arg->content_type, "boolean") || arg->default_value || !strcmp(arg->stdin_opt, "domain")) ? 0 : 1); printf("\t\t\n"); if (arg->default_value) { printf("\t\t\n", arg->content_type, arg->default_value); } else { printf("\t\t\n", arg->content_type); } printf("\t\t"); print_desc_xml(arg->desc); printf("\n"); printf("\t\n"); } printf("\n"); printf("\n"); printf("\t\n"); printf("\t\n"); printf("\t\n"); printf("\t\n"); printf("\t\n"); printf("\t\n"); printf("\t\n"); printf("\t\n"); printf("\t\n"); printf("\t\n"); printf("\n"); printf("\n"); } /** Remove leading and trailing whitespace from a line of text. @param line Line to clean up @param linelen Max size of line @return 0 on success, -1 on failure */ static int cleanup(char *line, size_t linelen) { char *p; int x; /* Remove leading whitespace. */ p = line; for (x = 0; x < linelen; x++) { switch (line[x]) { case '\t': case ' ': break; case '\n': case '\r': return -1; default: goto eol; } } eol: /* Move the remainder down by as many whitespace chars as we chewed up */ if (x) memmove(p, &line[x], linelen-x); /* Remove trailing whitespace. */ for (x=0; x < linelen; x++) { switch(line[x]) { case '\t': case ' ': case '\r': case '\n': line[x] = 0; case 0: /* End of line */ return 0; } } return -1; } /** Parse args from stdin and assign to the specified args structure. @param optstr Command line option string in getopt(3) format @param args Args structure to fill in. */ void args_get_stdin(const char *optstr, fence_virt_args_t *args) { char in[256]; char *name, *val; struct arg_info *arg; while (fgets(in, sizeof(in), stdin)) { if (in[0] == '#') continue; if (cleanup(in, sizeof(in)) == -1) continue; name = in; if ((val = strchr(in, '='))) { *val = 0; ++val; } arg = find_arg_by_string(name); if (!arg || (arg->opt != '\xff' && arg->opt != SCHEMA_COMPAT && !strchr(optstr, arg->opt))) { fprintf(stderr, "Parse error: Ignoring unknown option '%s'\n", name); continue; } if (arg->assign) arg->assign(args, arg, val); } } /** Parse args from stdin and assign to the specified args structure. @param optstr Command line option string in getopt(3) format @param args Args structure to fill in. */ void args_get_getopt(int argc, char **argv, const char *optstr, fence_virt_args_t *args) { int opt; struct arg_info *arg; while ((opt = getopt(argc, argv, optstr)) != EOF) { arg = find_arg_by_char(opt); if (!arg) { args->flags |= F_ERR; continue; } if (arg->assign) arg->assign(args, arg, optarg); } } void args_finalize(fence_virt_args_t *args) { char *addr = NULL; if (!args->net.addr) { switch(args->net.family) { case 0: case PF_INET: addr = (char *)IPV4_MCAST_DEFAULT; break; case PF_INET6: addr = (char *)IPV6_MCAST_DEFAULT; break; default: args->flags |= F_ERR; break; } } if (!args->net.addr) args->net.addr = addr; if (!args->net.addr) { printf("No multicast address available\n"); args->flags |= F_ERR; } if (!args->net.addr) return; if (args->net.family) return; /* Set family */ if (strchr(args->net.addr, ':')) args->net.family = PF_INET6; if (strchr(args->net.addr, '.')) args->net.family = PF_INET; if (!args->net.family) { printf("Could not determine address family\n"); args->flags |= F_ERR; } }