/* * vmcp - Send commands to the z/VM control program * * Tool for accessing the control program of z/VM using the * kernel module vmcp * return codes: * 0: everything was fine (EXIT_SUCCESS) * 1: CP returned a nn zero response code (VMCP_CP) * 2: the response buffer was not large enough (VMCP_BUF) * 3: an internal Linux error occurred (VMCP_LIN) * 4: invalid options (VMCP_OPT) * * CREDITS: The idea is based on cpint of Neale Fergusson * * Copyright IBM Corp. 2005, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "lib/zt_common.h" #include "lib/vmcp.h" #include "lib/util_opt.h" #include "lib/util_prg.h" #define MAXBUFFER 1048576 #define MINBUFFER 4096 #define MAXCMDLEN 240 #define VMCP_OK 0 #define VMCP_CP 1 #define VMCP_BUF 2 #define VMCP_LIN 3 #define VMCP_OPT 4 static int keep_case = 0; static int buffersize = VMCP_DEFAULT_BUFSZ; static char command[MAXCMDLEN + 1]; static struct util_opt opt_vec[] = { UTIL_OPT_SECTION("OPTIONS"), { .option = { "keepcase", no_argument, NULL, 'k' }, .desc = "Do not convert CP-command string to uppercase", }, { .option = { "buffer", required_argument, NULL, 'b' }, .argument = "SIZE", .desc = "Specify buffer size in bytes, kilobytes (k) " "or megabytes (M). SIZE range from 4096 to 1048576 " "bytes" }, UTIL_OPT_HELP, UTIL_OPT_VERSION, UTIL_OPT_END }; static const struct util_prg prg = { .desc = "z/VM CP command interface", .args = "CP-command", .copyright_vec = { { .owner = "IBM Corp.", .pub_first = 2005, .pub_last = 2018, }, UTIL_PRG_COPYRIGHT_END } }; /* Parse STRING for buffer size in bytes, allowing size modifier suffix 'k' and * 'm'. Return buffer size in bytes on success, -1 on error. */ static long parse_buffersize(char *string) { char *suffix; long bytes; bytes = strtol(string, &suffix, 10); if (strlen(suffix) > 1) return -1; switch (*suffix) { case 'k': case 'K': bytes *= 1024; break; case 'm': case 'M': bytes *= 1048576; break; case '\0': break; default: return -1; } if ((bytes < MINBUFFER) || (bytes > MAXBUFFER)) return -1; return bytes; } /* Parse tool parameters. Fill in global variables keep_case, buffersize and * command according to parameters. Return VMCP_OK on success, VMCP_OPT * in case of parameter errors. In case of --help or --version, print * respective text to stdout and exit. */ static int parse_args(int argc, char **argv) { int opt; int index; do { opt = util_opt_getopt_long(argc, argv); switch (opt) { case -1: /* Reached end of parameter list. */ break; case 'h': util_prg_print_help(); util_opt_print_help(); exit(EXIT_SUCCESS); case 'v': util_prg_print_version(); exit(EXIT_SUCCESS); case 'k': keep_case = 1; break; case 'b': buffersize = (int) parse_buffersize(optarg); if (buffersize == -1) { fprintf(stderr, "Error: Invalid buffersize " "(needs to be between %d and %d)\n", MINBUFFER, MAXBUFFER); return VMCP_OPT; } break; default: fprintf(stderr, "Try 'vmcp --help' for more" " information.\n"); return VMCP_OPT; } } while (opt != -1); /* Merge remaining argv contents into command string. */ for (index = optind; index < argc; index++) { if (strlen(command) + (strlen(command) == 0 ? 0 : 1) + strlen(argv[index]) > MAXCMDLEN) { fprintf(stderr, "Error: Command too long (cannot be " "longer than %d characters)\n", MAXCMDLEN); return VMCP_OPT; } if (strlen(command) > 0) strcat(command, " "); strcat(command, argv[index]); } if (strlen(command) == 0) { util_prg_print_help(); util_opt_print_help(); return VMCP_OPT; } return VMCP_OK; } static inline void linux_error(const char *message) { fprintf(stderr, "Error: %s: %s\n", message, strerror(errno)); } /* Write COUNT bytes to FD from memory at location BUF. Return number of bytes * written on success, -1 otherwise. */ static ssize_t write_buffer(int fd, const char *buf, size_t count) { ssize_t ret; ssize_t done; for (done = 0; done < (ssize_t) count; done += ret) { ret = write(fd, &buf[done], count - done); if (ret == -1 && errno == EINTR) continue; if (ret == -1) return -1; if (ret == 0) break; } return done; } int main(int argc, char **argv) { struct vmcp_parm cp; int ret; util_prg_init(&prg); util_opt_init(opt_vec, NULL); ret = parse_args(argc, argv); if (ret != VMCP_OK) return ret; cp.buffer_size = buffersize; cp.cpcmd = command; cp.do_upper = !keep_case; ret = vmcp(&cp); switch (ret) { case VMCP_ERR_OPEN: linux_error("Could not open device " VMCP_DEVICE_NODE); return VMCP_LIN; case VMCP_ERR_SETBUF: linux_error("Could not set buffer size"); return VMCP_LIN; case VMCP_ERR_WRITE: linux_error("Could not issue CP command"); return VMCP_LIN; case VMCP_ERR_GETCODE: linux_error("Could not query return code"); return VMCP_LIN; case VMCP_ERR_GETSIZE: linux_error("Could not query response size"); return VMCP_LIN; case VMCP_ERR_READ: linux_error("Could not read CP response"); return VMCP_LIN; } write_buffer(STDOUT_FILENO, cp.response, MIN(cp.response_size, cp.buffer_size)); free(cp.response); if (ret == VMCP_ERR_TOOSMALL) { fprintf(stderr, "Error: output (%d bytes) was truncated, try " "--buffer to increase size\n", cp.response_size); return VMCP_BUF; } if (cp.cprc > 0) { fprintf(stderr, "Error: non-zero CP response for command '%s': " "#%d\n", command, cp.cprc); return VMCP_CP; } return EXIT_SUCCESS; }