Files
s390-tools/vmur/vmur.cpp
Thomas Richter 4f00cb6346 vmur: Use libvmcp and remove local vmcp code
Use the new libvmcp.a to send commands to z/VM CP.
The error messages remain unchanged.

Signed-off-by: Thomas Richter <tmricht@linux.vnet.ibm.com>
Reviewed-by: Hendrik Brueckner <brueckner@linux.vnet.ibm.com>
Reviewed-by: Jan Höppner <hoeppner@linux.vnet.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2018-05-07 13:42:03 +02:00

2350 lines
55 KiB
C++

/*
* vmur - Work with z/VM spool file queues (reader, punch, printer)
*
* Copyright IBM Corp. 2007, 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 <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <libgen.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <libgen.h>
#include <signal.h>
#include <iconv.h>
#include <sys/types.h>
#include <dirent.h>
#include <sys/sysmacros.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <ctype.h>
#include <linux/types.h>
#include "lib/vmdump.h"
#include "lib/zt_common.h"
#include "lib/util_libc.h"
#include "lib/vmcp.h"
#include "vmur.h"
#define CP_PREFIX_LEN 11
/* Program name */
static char *prog_name;
/* Short description */
static const char tool_name[] =
"vmur: Control virtual reader, punch, and printer";
/* Copyright notice */
static const char copyright_notice[] = "Copyright IBM Corp. 2007, 2017";
/*
* Structure vmur is used to store the command line parameters and
* other information needed at different places
*/
struct vmur {
char spoolid[5];
int spoolid_specified;
char spoolfile_name[9];
int spoolfile_name_specified;
char spoolfile_type[9];
int spoolfile_type_specified;
char devnode[PATH_MAX];
int devnode_specified;
char node[9];
int node_specified;
char file_name[PATH_MAX];
int file_name_specified;
char queue[4];
int queue_specified;
char user[9];
int user_specified;
int blocked_separator;
int blocked_padding;
int blocked_specified;
int rdr_specified;
int text_specified;
int force_specified;
int stdout_specified;
int hold_specified;
int convert_specified;
enum ur_action action;
int devno;
int ur_reclen;
int file_reclen;
enum spoolfile_fmt spoolfile_fmt;
struct sigaction sigact;
iconv_t iconv;
int lock_fd;
/* ur device spool state */
char spool_restore_cmd[MAXCMDLEN];
int spool_restore_needed;
char spool_class;
int spool_class_specified;
char spool_dest[9];
int spool_dest_specified;
char spool_form[9];
int spool_form_specified;
char spool_dist[9];
int spool_dist_specified;
} vmur_info;
/*
* Print version information.
*/
static void print_version (void)
{
printf("%s version %s\n", tool_name, RELEASE_STRING);
printf("%s\n", copyright_notice);
}
/*
* Convert string to to_upper
*/
static char *to_upper(char *str)
{
char *ptr = str;
while (*ptr) {
*ptr = toupper(*ptr);
ptr++;
}
return str;
}
/*
* Convert string to valid CP spool file name
*/
static char *to_valid_cpname(char *str)
{
if (strlen(str) > 8)
str[8] = 0;
while (*str) {
if (!isprint(*str) || isspace(*str))
*str = '_';
str++;
}
return str;
}
/*
* Convert string to valid Linux file name
*/
static char *to_valid_linux_name(char *str)
{
while (*str) {
if (*str == '/')
*str = '_';
str++;
}
return str;
}
/*
* Print out usage text
*/
static char HELP_TEXT[] =
"Usage: vmur receive [OPTIONS] [SPOOLID] [FILE]\n"
" vmur punch [OPTIONS] [FILE]\n"
" vmur print [OPTIONS] [FILE]\n"
" vmur purge [OPTIONS] [SPOOLID]\n"
" vmur order [OPTIONS] [SPOOLID]\n"
" vmur list [OPTIONS] [SPOOLID]\n"
"\n"
"Control virtual reader, punch, and printer. Available commands are:\n"
" * REceive: Receive spool files from reader queue\n"
" * PUNch: Punch a file to punch queue\n"
" * PRint: Print a file to printer queue\n"
" * PURge: Purge spool files\n"
" * ORder: Order spool file\n"
" * LIst: List spool files\n"
"\n"
"General options:\n"
"\n"
"-h, --help Print this help, then exit.\n"
"-v, --version Print version information, then exit.\n"
"\n"
"Options of 'receive' command:\n"
"\n"
"-d, --device Device node of the VM virtual reader.\n"
" If omitted, /dev/vmrdr-0.0.000c is assumed.\n"
"-t, --text Indicates text data causing EBCDIC to ASCII\n"
" conversion.\n"
"-b, --blocked Use blocked mode.\n"
"-c, --convert Specifies to convert VMDUMP file into a format\n"
" appropriate for further analysis with (l)crash.\n"
"-O, --stdout Write spool file to stdout.\n"
"-f, --force Overwrite files without prompt.\n"
"-H, --hold Hold spool file in reader after receive.\n"
"-C, --class Specify the spool class to match a reader file.\n"
"\n"
"Options for 'punch' and 'print' command:\n"
"\n"
"-d, --device Device node of the VM virtual punch or printer.\n"
" If omitted, /dev/vmpun-0.0.000d or\n"
" /dev/vmprt-0.0.000e is assumed, respectively.\n"
"-t, --text Indicates text data causing ASCII to EBCDIC\n"
" conversion.\n"
"-b, --blocked Use blocked mode.\n"
"-r, --rdr Indicates to transfer file from punch/printer\n"
" to reader.\n"
"-u, --user Transfer file to user's reader.\n"
" If omitted: Your guest machine's reader.\n"
"-n, --node Remote node to send the file to.\n"
" If omitted: Your local VM node.\n"
"-N, --name Name of new spool file.\n"
"-f, --force Convert file name to valid spool file name\n"
" automatically without prompt.\n"
"-C, --class Spool class to be assigned to the created spool file.\n"
" --form Form to be assigned to the created spool file.\n"
" --dest Destination to be assigned to the created spool file.\n"
" --dist Distribution code for the resulting spool file.\n"
"\n"
"Options for 'purge' command:\n"
"\n"
"-f, --force Purge without prompt.\n"
"\n"
"Options for 'order', 'list', and 'purge' commands:\n"
"\n"
"-q, --queue Target queue for command. Possible queues are:\n"
" 'rdr' (default), 'pun' and 'prt'.\n";
static void usage(void)
{
printf("%s", HELP_TEXT);
}
/*
* Signal handler
*/
static void set_signal_handler(struct vmur *info,
void (*handler) (int, siginfo_t *, void *))
{
info->sigact.sa_flags = (SA_NODEFER | SA_SIGINFO | SA_RESETHAND);
info->sigact.sa_sigaction = handler;
if (sigemptyset(&info->sigact.sa_mask) < 0)
goto fail;
if (sigaction(SIGINT, &info->sigact, NULL) < 0)
goto fail;
if (sigaction(SIGTERM, &info->sigact, NULL) < 0)
goto fail;
return;
fail:
ERR_EXIT("Could not initialize signal handler (errno = %i)\n", errno);
}
/*
* Strip leading CP header from error message
*/
static void strip_cperr(char *buf)
{
unsigned int i;
for (i = 0; i < strlen(buf) - CP_PREFIX_LEN; i++) {
if (strncmp(buf + i, "HCP", 3) == 0) {
int offs = i + CP_PREFIX_LEN;
memmove(buf, buf + offs, strlen(buf) - offs);
/* terminate string and remove newline */
buf[strlen(buf) - offs - 1] = 0;
return;
}
}
}
/*
* Handle CP error message and exit
*/
static void cperr_exit(char *cpcmd, int cprc, char *buf)
{
if (strlen(buf) <= CP_PREFIX_LEN)
ERR_EXIT("CP command '%s' failed with rc=%i\n", cpcmd, cprc);
strip_cperr(buf);
ERR_EXIT("%s\n", buf);
}
static void _cpcmd(char *cpcmd, char **resp, int *rc, int retry, int upper)
{
struct vmcp_parm cp;
char cmd[MAXCMDLEN];
int ret;
strcpy(cmd, cpcmd);
cp.cpcmd = cmd;
cp.do_upper = upper;
cp.buffer_size = VMCP_DEFAULT_BUFSZ;
retry:
ret = vmcp(&cp);
switch (ret) {
case VMCP_ERR_OPEN:
ERR_EXIT("Could not issue CP command: \"%s\"\n"
"Ensure that vmcp kernel module is loaded!\n", cmd);
case VMCP_ERR_SETBUF:
goto fail;
case VMCP_ERR_WRITE:
goto fail;
case VMCP_ERR_GETCODE:
goto fail;
case VMCP_ERR_GETSIZE:
goto fail;
case VMCP_ERR_READ:
goto fail;
case VMCP_ERR_TOOSMALL:
if (retry) {
cp.buffer_size = cp.response_size;
free(cp.response);
goto retry;
}
ERR_EXIT("Not enough buffer space (%u/%u) for CP "
"command '%s'.\nSorry, please issue command "
" on your 3270 console!\n", cp.response_size,
cp.buffer_size, cmd);
}
if (rc == NULL) {
if (cp.cprc != 0) {
/* caller wants us to handle the error */
cperr_exit(cmd, cp.cprc, cp.response);
}
} else {
*rc = cp.cprc;
}
if (resp)
*resp = cp.response;
else
free(cp.response);
return;
fail:
ERR_EXIT("CP command '%s' failed.\n", cmd);
}
/*
* Issue CP command:
* @cpcmd: CP command to be issued.
* @resp: CP command response on success, an error message on error.
* @rc: CP return code.
* @retry: retry = 1 -> CP command can be retried.
* This function converts the command string to uppercase.
*/
static void cpcmd(char *cpcmd, char **resp, int *rc, int retry)
{
_cpcmd(cpcmd, resp, rc, retry, 1);
}
/*
* Issue CP command:
* @cpcmd: CP command to be issued.
* @resp: CP command response on success, an error message on error.
* @rc: CP return code.
* @retry: retry = 1 -> CP command can be retried.
* This function does not convert the command string to uppercase.
*/
static void cpcmd_cs(char *cpcmd, char **resp, int *rc, int retry)
{
_cpcmd(cpcmd, resp, rc, retry, 0);
}
/*
* Extract minor from sysfs file
*/
static int get_minor(char *path)
{
FILE *fd;
char buf[20];
int major, minor, rc;
fd = fopen(path, "r");
if (!fd)
ERR_EXIT("Could not open %s (err = %i)\n", path, errno);
rc = fread(buf, sizeof(buf), 1, fd);
if (rc == -1)
ERR_EXIT("Could not read %s (err = %i)\n", path, errno);
fclose(fd);
if (sscanf(buf, "%i:%i", &major, &minor) != 2)
ERR_EXIT("Malformed content of %s: %s\n", path, buf);
return minor;
}
/*
* Find device number of ur device
*/
static int get_ur_devno(int minor)
{
struct dirent *direntp;
char dev_file[PATH_MAX];
DIR *fd;
char bus_id[9] = {};
int devno;
errno = 0;
fd = opendir(SYSFS_CLASS_DIR);
if (!fd)
ERR_EXIT("Could not open %s (err = %i)\n", SYSFS_CLASS_DIR,
errno);
while ((direntp = readdir(fd))) {
if (strcmp(direntp->d_name, ".") == 0)
continue;
if (strcmp(direntp->d_name, "..") == 0)
continue;
sprintf(dev_file, "%s/%s/dev", SYSFS_CLASS_DIR,
direntp->d_name);
if (get_minor(dev_file) == minor) {
/* extract device id from <vmxxx-0.0.yyyy> */
memcpy(bus_id, &direntp->d_name[6], 8);
goto found;
}
};
if (errno != 0)
ERR_EXIT("Could not read %s (err = %i)\n",
SYSFS_CLASS_DIR, errno);
else
ERR_EXIT("Device is not online\n");
found:
if (sscanf(bus_id, "0.0.%x", &devno) != 1)
ERR_EXIT("Could not extract device number from %s\n",
direntp->d_name);
closedir(fd);
return devno;
}
/*
* Extract major/minor from device node
*/
static dev_t get_node_dev(char *devnode)
{
struct stat stat_info;
if (stat(devnode, &stat_info)) {
ERR("Unable to get status for '%s': %s\n", devnode,
strerror(errno));
ERR_EXIT("Please check if device is online!\n");
}
return stat_info.st_rdev;
}
/*
* Extract major of vmur driver
*/
static int get_driver_major(unsigned long *major)
{
FILE *fh;
char string[PROC_DEVICES_FILE_WIDTH];
char last_string[PROC_DEVICES_FILE_WIDTH];
fh = fopen(PROC_DEVICES_FILE, "r");
if (!fh) {
ERR("WARNING: Cannot check for vmur in file %s.\n%s\n",
PROC_DEVICES_FILE, strerror(errno));
return -1; /* check not possible, just continue */
}
while (fscanf(fh, "%s", string) != EOF) {
if (strcmp(string, "vmur") == 0) {
fclose(fh);
*major = atoi(last_string);
return 0;
} else {
strcpy(last_string, string);
}
}
fclose(fh);
ERR_EXIT("Unit record device driver not loaded.\n");
}
/*
* Copy printable characters
*/
static void strncpy_graph(char *dest, const char *src, size_t len)
{
size_t n;
for (n = 0; n < len; n++) {
if (!isgraph(src[n]))
break;
dest[n] = src[n];
}
dest[n] = '\0';
}
/*
* Create CP command to restore spooling options
*/
static void save_spool_options(struct vmur *info)
{
char cmd[MAXCMDLEN], *resp;
char cl, value[9], *tmp;
int n;
/* Retrieve spooling options for ur device */
sprintf(cmd, "QUERY VIRTUAL %X", info->devno);
cpcmd(cmd, &resp, NULL, 0);
/* Prepare CP spool restore command */
n = sprintf(info->spool_restore_cmd, "SPOOL %X", info->devno);
/* Save the CLASS value if required */
if (info->spool_class_specified) {
cl = resp[13];
n += sprintf(info->spool_restore_cmd + n, " CL %c", cl);
}
/* Save FORM value if required */
if (info->spool_form_specified) {
tmp = strstr(resp, "FORM ");
if (tmp == NULL)
ERR_EXIT("Could not retrieve value for the FORM "
"spooling option\n");
strncpy_graph(value, tmp + 5, 8);
n += sprintf(info->spool_restore_cmd + n, " FORM %s", value);
}
/* Save DEST value if required */
if (info->spool_dest_specified) {
tmp = strstr(resp, "DEST ");
if (tmp == NULL)
ERR_EXIT("Could not retrieve value for the DEST "
"spooling option\n");
strncpy_graph(value, tmp + 5, 8);
n += sprintf(info->spool_restore_cmd + n, " DEST %s", value);
}
/* Save DIST value if required */
if (info->spool_dist_specified) {
tmp = strstr(resp, "DIST ");
if (tmp == NULL)
ERR_EXIT("Could not retrieve value for the DIST "
"spooling option\n");
strncpy_graph(value, tmp + 5, 8);
n += sprintf(info->spool_restore_cmd + n, " DIST %s", value);
}
free(resp);
++info->spool_restore_needed;
}
/*
* Restore saved spooling options for a ur device
*/
static void restore_spool_options(struct vmur *info)
{
if (!info->spool_restore_needed)
return;
cpcmd(info->spool_restore_cmd, NULL, NULL, 0);
--info->spool_restore_needed;
}
/*
* Returns non-zero if spool options have to be saved, changed, and restored,
* otherwise zero is returned.
*/
static int require_spool_setup(struct vmur *info)
{
return !!(info->spool_class_specified ||
info->spool_form_specified ||
info->spool_dest_specified ||
info->spool_dist_specified);
}
/*
* Setup spooling options for a ur device
*/
static void setup_spool_options(struct vmur *info)
{
char cmd[MAXCMDLEN];
int n, rc;
/*
* Check if spool options must be changed. If so, save the current
* spool option values and restore them at program exit.
*/
if (!require_spool_setup(info))
return;
/* Save spool options */
save_spool_options(info);
/* Change spool options */
n = sprintf(cmd, "SPOOL %X", info->devno);
if (info->spool_class_specified)
n += sprintf(cmd + n, " CLASS %c", info->spool_class);
if (info->spool_form_specified)
n += sprintf(cmd + n, " FORM %s", info->spool_form);
if (info->spool_dest_specified)
n += sprintf(cmd + n, " DEST %s", info->spool_dest);
if (info->spool_dist_specified)
n += sprintf(cmd + n, " DIST %s", info->spool_dist);
cpcmd(cmd, NULL, &rc, 0);
if (rc)
ERR_EXIT("Could not set spooling options (rc=%i)\n", rc);
}
/*
* Setup and check ur device
*/
static void setup_ur_device(struct vmur *info)
{
unsigned long driver_major;
dev_t node_dev;
int rc;
/*
* Few vmur commands do not require a particular device node and,
* therefore, no device setup is required.
*/
if (!strlen(info->devnode))
return;
node_dev = get_node_dev(info->devnode);
rc = get_driver_major(&driver_major);
if ((rc == 0) && (driver_major != major(node_dev)))
ERR_EXIT("'%s' is not a unit record device.\n",
info->devnode);
info->devno = get_ur_devno(minor(node_dev));
}
/*
* initialize the vmur info structure
*/
static void init_info(struct vmur *info)
{
memset(info, 0, sizeof(struct vmur));
strcpy(info->queue, "rdr");
info->lock_fd = -1;
}
/*
* set positional spoolid parameter
*/
static void set_spoolid(struct vmur *info, char **argv, int argc, int optind,
int mandatory)
{
char *str;
if (argc <= optind) {
if (mandatory)
ERR_EXIT("No spool id specified.\n");
else
return;
}
if ((argc > optind + 1) && (info->action != RECEIVE))
ERR_EXIT("More than one spool id specified.\n");
str = argv[optind];
if (strlen(str) > 4)
goto invalid;
while (*str) {
if (!isdigit(*str))
goto invalid;
str++;
}
strcpy(info->spoolid, argv[optind]);
++info->spoolid_specified;
return;
invalid:
ERR_EXIT("Spoolid must be a decimal number in range 0-9999\n");
}
/*
* set positional file parameter
*/
static void set_file(struct vmur *info, char **argv, int argc, int optind)
{
if (argc <= optind)
return;
if (argc > optind + 1)
ERR_EXIT("More than one file specified.\n");
strncpy(info->file_name, argv[optind], sizeof(info->file_name) - 1);
++info->file_name_specified;
}
/*
* Set queue: rdr, pun or prt
*/
static void set_queue(struct vmur *info, char *queue)
{
if (strcmp(queue, "rdr") == 0)
strcpy(info->queue, "rdr");
else if (strcmp(queue, "pun") == 0)
strcpy(info->queue, "pun");
else if (strcmp(queue, "prt") == 0)
strcpy(info->queue, "prt");
else
ERR_EXIT("Invalid queue: %s\n", queue);
return;
}
/*
* Set block mode and the separator and padding byte
*/
static void set_blocked(struct vmur *info, char *blocked)
{
if (sscanf(blocked, "0x%x,0x%x", &info->blocked_separator,
&info->blocked_padding) != 2)
goto fail;
if (info->blocked_separator > 255)
goto fail;
if (info->blocked_padding > 255)
goto fail;
++info->blocked_specified;
return;
fail:
ERR_EXIT("Invalid blocked parameter. It must have the format "
"'0xSS,0xPP'.\n");
}
/*
* Set spool class value
*/
static void set_spool_class(struct vmur *info, const char *val, int is_rdr)
{
char cl = val[0];
if (strlen(val) > 1 || (!isalnum(cl) && cl != '*'))
ERR_EXIT("Class must be one of A through Z, 0 through 9, "
"or an asterisk (*)\n");
if (cl == '*' && !is_rdr)
ERR_EXIT("The asterisk (*) class is only valid for readers\n");
info->spool_class = toupper(cl);
++info->spool_class_specified;
}
/*
* Parse the command line: General options
*/
static void check_std_opts(int opt)
{
switch (opt) {
case 'v':
print_version();
exit(0);
case 'h':
usage();
exit(0);
}
}
static void std_usage_exit(void)
{
fprintf(stderr, "Try '%s --help' for more information.\n", prog_name);
exit(1);
}
/*
* Parse the command line: Receive command
*/
static void parse_opts_receive(struct vmur *info, int argc, char *argv[])
{
int opt, index;
static struct option long_options[] = {
{ "version", no_argument, NULL, 'v'},
{ "help", no_argument, NULL, 'h'},
{ "text", no_argument, NULL, 't'},
{ "stdout", no_argument, NULL, 'O'},
{ "force", no_argument, NULL, 'f'},
{ "hold", no_argument, NULL, 'H'},
{ "convert", no_argument, NULL, 'c'},
{ "device", required_argument, NULL, 'd'},
{ "blocked", required_argument, NULL, 'b'},
{ "class", required_argument, NULL, 'C'},
{ 0, 0, 0, 0 }
};
static const char option_string[] = "vhtOfHcd:b:C:";
strcpy(info->devnode, VMRDR_DEVICE_NODE);
while (1) {
opt = getopt_long(argc, argv, option_string,
long_options, &index);
if (opt == -1)
break;
check_std_opts(opt);
switch (opt) {
case 't':
++info->text_specified;
break;
case 'd':
++info->devnode_specified;
strncpy(info->devnode, optarg,
sizeof(info->devnode) - 1);
break;
case 'O':
++info->stdout_specified;
break;
case 'b':
set_blocked(info, optarg);
break;
case 'f':
++info->force_specified;
break;
case 'H':
++info->hold_specified;
break;
case 'c':
++info->convert_specified;
break;
case 'C':
set_spool_class(info, optarg, 1);
break;
default:
std_usage_exit();
}
}
set_spoolid(info, argv, argc, optind + 1, 1);
set_file(info, argv, argc, optind + 2);
CHECK_SPEC_MAX(info->text_specified, 1, "text");
CHECK_SPEC_MAX(info->devnode_specified, 1, "devnode");
CHECK_SPEC_MAX(info->file_name_specified, 1, "file");
CHECK_SPEC_MAX(info->blocked_specified, 1, "blocked");
CHECK_SPEC_MAX(info->force_specified, 1, "force");
CHECK_SPEC_MAX(info->hold_specified, 1, "hold");
CHECK_SPEC_MAX(info->stdout_specified, 1, "stdout");
CHECK_SPEC_MAX(info->convert_specified, 1, "convert");
CHECK_SPEC_MAX(info->spool_class_specified, 1, "class");
if (info->stdout_specified && info->file_name_specified)
ERR_EXIT("File name not allowed, when --stdout specified!\n");
if (info->blocked_specified + info->text_specified +
info->convert_specified > 1)
ERR_EXIT("Conflicting options: -b, -t and -c are mutually "
"exclusive.\n");
if (!info->spool_class_specified)
set_spool_class(info, "*", 1);
}
/*
* Validate VM userID
*/
static int invalid_userid(char *operand)
{
if (strlen(operand) > 8)
return 1;
while (*operand) {
if (!isalnum(*operand) &&
(strchr("@#$_-", *operand) == NULL))
return 1;
operand++;
}
return 0;
}
/*
* Validate CP command operand such as nodeID or name/type
*/
static int invalid_operand(char *operand)
{
if (strlen(operand) > 8)
return 1;
while (*operand) {
if (!isprint(*operand) || isspace(*operand))
return 1;
operand++;
}
return 0;
}
/*
* Check whether user is in the CP directory
*/
static int check_local_user(const char *user)
{
char cmd[MAXCMDLEN];
int cprc;
strcpy(cmd, "LINK ");
strcat(cmd, user);
cpcmd(cmd, NULL, &cprc, 0);
if ((cprc == 53) /* user not in CP directory */
|| (cprc == 20)) /* Userid missing or invalid */
return 1;
else
return 0;
}
/*
* Set spool file name/type in vmur structure and validate name/type
*/
static void set_spoolfile_name(struct vmur *info, char *name)
{
char spoolfile_name[PATH_MAX], spoolfile_type[PATH_MAX] = {};
int i, flag = 0;
strcpy(spoolfile_name, name);
if (strlen(spoolfile_name) == 0)
ERR_EXIT("Empty spool file name is invalid\n");
/* check for period delimiting name from type */
if (spoolfile_name[strlen(spoolfile_name) - 1] == '.')
flag = 1; /* name/type string ends with period */
for (i = strlen(spoolfile_name) - 1; i > 0; i--) {
if (spoolfile_name[i] == '.') {
if (flag && (spoolfile_name[i-1] == '.'))
continue;
else
break;
}
}
if ((i > 0) && (i < (int) strlen(spoolfile_name) - 1)) {
strcpy(spoolfile_type, spoolfile_name + i + 1);
spoolfile_name[i] = 0;
++info->spoolfile_type_specified;
}
if (info->force_specified) {
/* adjust spool file name, in order to have a valid one */
to_valid_cpname(spoolfile_name);
if (info->spoolfile_type_specified)
to_valid_cpname(spoolfile_type);
goto out;
}
if (invalid_operand(spoolfile_name) || invalid_operand(spoolfile_type))
goto invalid;
out:
strcpy(info->spoolfile_name, spoolfile_name),
++info->spoolfile_name_specified;
if (info->spoolfile_type_specified)
strcpy(info->spoolfile_type, spoolfile_type);
return;
invalid:
ERR_EXIT("Malformed spool file name: %s\n"
"Specify --force, if the name should be converted "
"automatically.\n", name);
}
/*
* Parse the command line: punch command
*/
static void parse_opts_punch_print(struct vmur *info, int argc, char *argv[])
{
int opt, index;
char *spoolfile_name = NULL;
static struct option long_options[] = {
{ "version", no_argument, NULL, 'v'},
{ "help", no_argument, NULL, 'h'},
{ "text", no_argument, NULL, 't'},
{ "rdr", no_argument, NULL, 'r'},
{ "force", no_argument, NULL, 'f'},
{ "user", required_argument, NULL, 'u'},
{ "node", required_argument, NULL, 'n'},
{ "device", required_argument, NULL, 's'},
{ "blocked", required_argument, NULL, 'b'},
{ "name", required_argument, NULL, 'N'},
{ "class", required_argument, NULL, 'C'},
{ "dest", required_argument, NULL, 'D'},
{ "form", required_argument, NULL, 'F'},
{ "dist", required_argument, NULL, 'I'},
{ 0, 0, 0, 0 }
};
static const char option_string[] = "vhtrfu:n:d:b:N:C:";
if (info->action == PUNCH) {
strcpy(info->devnode, VMPUN_DEVICE_NODE);
info->ur_reclen = VMPUN_RECLEN;
} else {
strcpy(info->devnode, VMPRT_DEVICE_NODE);
info->ur_reclen = VMPRT_RECLEN;
}
while (1) {
opt = getopt_long(argc, argv, option_string,
long_options, &index);
if (opt == -1)
break;
check_std_opts(opt);
switch (opt) {
case 'd':
++info->devnode_specified;
strcpy(info->devnode, optarg);
break;
case 'N':
++info->spoolfile_name_specified;
spoolfile_name = optarg;
break;
case 'r':
++info->rdr_specified;
break;
case 'f':
++info->force_specified;
break;
case 't':
++info->text_specified;
break;
case 'u':
++info->user_specified;
if (invalid_userid(optarg))
ERR_EXIT("Invalid userid: %s\n", optarg);
else
strcpy(info->user, optarg);
break;
case 'b':
set_blocked(info, optarg);
break;
case 'n':
++info->node_specified;
if (invalid_operand(optarg))
ERR_EXIT("Invalid node specified.\n");
else
strcpy(info->node, optarg);
break;
case 'C':
set_spool_class(info, optarg, 0);
break;
case 'D':
++info->spool_dest_specified;
if (invalid_operand(optarg))
ERR_EXIT("Invalid destination: %s\n", optarg);
else
strcpy(info->spool_dest, optarg);
break;
case 'F':
++info->spool_form_specified;
if (invalid_operand(optarg))
ERR_EXIT("Invalid form: %s\n", optarg);
else
strcpy(info->spool_form, optarg);
break;
case 'I':
++info->spool_dist_specified;
if (invalid_operand(optarg))
ERR_EXIT("Invalid distribution code: %s\n", optarg);
else
strcpy(info->spool_dist, optarg);
break;
default:
std_usage_exit();
}
}
CHECK_SPEC_MAX(info->rdr_specified, 1, "rdr");
CHECK_SPEC_MAX(info->force_specified, 1, "force");
CHECK_SPEC_MAX(info->user_specified, 1, "user");
CHECK_SPEC_MAX(info->file_name_specified, 1, "file");
CHECK_SPEC_MAX(info->spoolfile_name_specified, 1, "name");
CHECK_SPEC_MAX(info->user_specified, 1, "user");
CHECK_SPEC_MAX(info->node_specified, 1, "node");
CHECK_SPEC_MAX(info->blocked_specified, 1, "blocked");
CHECK_SPEC_MAX(info->spool_class_specified, 1, "class");
CHECK_SPEC_MAX(info->spool_form_specified, 1, "form");
CHECK_SPEC_MAX(info->spool_dest_specified, 1, "dest");
CHECK_SPEC_MAX(info->spool_dist_specified, 1, "dist");
if (info->user_specified && !info->rdr_specified)
ERR_EXIT("--user without --rdr specified\n");
if (info->node_specified && !info->user_specified)
ERR_EXIT("--node without --user specified\n");
if ((info->user_specified && !info->node_specified)
&& check_local_user(info->user))
ERR_EXIT("Invalid userid: %s\n", info->user);
if (info->node_specified && check_local_user(RSCS_USERID))
ERR_EXIT("Invalid RSCS userid: %s\n", info->node);
if (info->blocked_specified && info->text_specified)
ERR_EXIT("Conflicting options: -b together with -t "
"specified\n");
set_file(info, argv, argc, optind + 1);
if (info->spoolfile_name_specified)
set_spoolfile_name(info, spoolfile_name);
else if (info->file_name_specified)
set_spoolfile_name(info, basename(info->file_name));
else
ERR_EXIT("No name for spool file specified!\n");
}
/*
* Parse the command line: Purge command
*/
static void parse_opts_purge(struct vmur *info, int argc, char *argv[])
{
int opt, index;
static struct option long_options[] = {
{ "version", no_argument, NULL, 'v'},
{ "help", no_argument, NULL, 'h'},
{ "force", no_argument, NULL, 'f'},
{ "queue", no_argument, NULL, 'q'},
{ "class", required_argument, NULL, 'C'},
{ "dest", required_argument, NULL, 'D'},
{ "form", required_argument, NULL, 'F'},
{ 0, 0, 0, 0 }
};
static const char option_string[] = "fvhq:C:";
while (1) {
opt = getopt_long(argc, argv, option_string,
long_options, &index);
if (opt == -1)
break;
check_std_opts(opt);
switch (opt) {
case 'f':
++info->force_specified;
break;
case 'q':
++info->queue_specified;
set_queue(info, optarg);
break;
case 'C':
set_spool_class(info, optarg, 0);
break;
case 'D':
++info->spool_dest_specified;
if (invalid_operand(optarg))
ERR_EXIT("Invalid destination: %s\n", optarg);
else
strcpy(info->spool_dest, optarg);
break;
case 'F':
++info->spool_form_specified;
if (invalid_operand(optarg))
ERR_EXIT("Invalid form: %s\n", optarg);
else
strcpy(info->spool_form, optarg);
break;
default:
std_usage_exit();
}
}
CHECK_SPEC_MAX(info->force_specified, 1, "force");
CHECK_SPEC_MAX(info->queue_specified, 1, "queue");
CHECK_SPEC_MAX(info->spool_class_specified, 1, "class");
CHECK_SPEC_MAX(info->spool_form_specified, 1, "form");
CHECK_SPEC_MAX(info->spool_dest_specified, 1, "dest");
set_spoolid(info, argv, argc, optind + 1, 0);
}
/*
* Parse the command line: Order command
*/
static void parse_opts_order(struct vmur *info, int argc, char *argv[])
{
int opt, index;
static struct option long_options[] = {
{ "version", no_argument, NULL, 'v'},
{ "help", no_argument, NULL, 'h'},
{ "queue", required_argument, NULL, 'q'},
{ 0, 0, 0, 0 }
};
static const char option_string[] = "vhq:";
while (1) {
opt = getopt_long(argc, argv, option_string,
long_options, &index);
if (opt == -1)
break;
check_std_opts(opt);
switch (opt) {
case 'q':
++info->queue_specified;
set_queue(info, optarg);
break;
default:
std_usage_exit();
}
}
CHECK_SPEC_MAX(info->queue_specified, 1, "queue");
set_spoolid(info, argv, argc, optind + 1, 1);
}
/*
* Parse the command line: List command
*/
static void parse_opts_list(struct vmur *info, int argc, char *argv[])
{
int opt, index;
static struct option long_options[] = {
{ "version", no_argument, NULL, 'v'},
{ "help", no_argument, NULL, 'h'},
{ "queue", required_argument, NULL, 'q'},
{ 0, 0, 0, 0 }
};
static const char option_string[] = "vhq:";
while (1) {
opt = getopt_long(argc, argv, option_string,
long_options, &index);
if (opt == -1)
break;
check_std_opts(opt);
switch (opt) {
case 'q':
++info->queue_specified;
set_queue(info, optarg);
break;
default:
std_usage_exit();
}
}
CHECK_SPEC_MAX(info->queue_specified, 1, "queue");
set_spoolid(info, argv, argc, optind + 1, 0);
}
/*
* Parse the command line: Default options
*/
static void parse_opts_default(int argc, char *argv[])
{
int opt, index;
static struct option long_options[] = {
{ "version", no_argument, NULL, 'v'},
{ "help", no_argument, NULL, 'h'},
{ 0, 0, 0, 0 }
};
static const char option_string[] = "vh";
while (1) {
opt = getopt_long(argc, argv, option_string,
long_options, &index);
if (opt == -1)
break;
check_std_opts(opt);
std_usage_exit();
}
}
/*
* Parse action strings
*/
static int is_action(enum ur_action action, char *str)
{
char action_str[80] = {};
if (strlen(str) < ur_action_prefix_len[action])
return 0;
if (strlen(str) > strlen(ur_action_str[action]))
return 0;
strncpy(action_str, ur_action_str[action], strlen(str));
if (strcasecmp(str, action_str) != 0)
return 0;
return 1;
}
/*
* Get action string and set action field in info structure
*/
static int set_action(struct vmur *info, char *str)
{
int action;
for (action = 0; action < LAST; action++) {
if (is_action((enum ur_action) action, str)) {
info->action = (enum ur_action) action;
return 0;
}
}
return -EINVAL;
}
/*
* The toplevel parameter parsing function
*/
static void parse_opts(struct vmur *info, int argc, char *argv[])
{
if (argc == 1)
ERR_EXIT("Missing command\n");
if (set_action(info, argv[1])) {
parse_opts_default(argc, argv);
ERR("Unknown command '%s'\n", argv[1]);
std_usage_exit();
}
switch (info->action) {
case RECEIVE:
return parse_opts_receive(info, argc, argv);
case PUNCH:
return parse_opts_punch_print(info, argc, argv);
case PRINT:
return parse_opts_punch_print(info, argc, argv);
case PURGE:
return parse_opts_purge(info, argc, argv);
case ORDER:
return parse_opts_order(info, argc, argv);
case LIST:
return parse_opts_list(info, argc, argv);
default:
ERR_EXIT("Internal error. Unknown action: %i\n", info->action);
}
}
/*
* Check if spool file has hold state "NONE"
*/
static void check_hold_state(char *spoolid)
{
char cmd[MAXCMDLEN];
char *response;
sprintf(cmd, "QUERY READER * %s ALL SHORTDATE", spoolid);
cpcmd(cmd, &response, 0, 0);
response[114] = 0;
if (strcmp(&response[110], "NONE") != 0)
ERR_EXIT("Could not receive spool file %s: hold state = %s\n",
spoolid, &response[110]);
free(response);
}
/*
* Issue CP command ORDER RDR <spoolid>.
* If ORDER fails, the CP command response is as follows:
* NO FILES ORDERED
* HCPxxxnnnt <message text>
* If ORDER returns successfully, CP command CHANGE RDR <spoolid> NOHOLD
* is issued.
*/
static void order_change_reader_file(struct vmur *info)
{
char cmd[MAXCMDLEN];
sprintf(cmd, "ORDER * READER %s", info->spoolid);
cpcmd(cmd, NULL, NULL, 0);
sprintf(cmd, "CHANGE * READER %s NOHOLD", info->spoolid);
cpcmd(cmd, NULL, NULL, 0);
check_hold_state(info->spoolid);
}
/*
* Issue CP command CLOSE RDR
*/
static void close_reader(struct vmur *info, const char *hold)
{
char cmd[MAXCMDLEN];
sprintf(cmd, "CLOSE %X %s", info->devno, hold);
cpcmd(cmd, NULL, NULL, 0);
}
/*
* Check whether reader can handle class of reader spool file
*/
static int check_class(struct vmur *info)
{
char cmd[MAXCMDLEN];
char device_class, file_class;
char *buf;
sprintf(cmd, "QUERY VIRTUAL %X", info->devno);
cpcmd(cmd, &buf, NULL, 0);
device_class = buf[13];
free(buf);
if (device_class == '*') /* reader device can handle any class */
return 0;
sprintf(cmd, "QUERY RDR * %s ALL SHORTDATE", info->spoolid);
cpcmd(cmd, &buf, NULL, 0);
file_class = buf[91];
free(buf);
if (file_class != device_class)
return 1;
return 0;
}
/*
* Get file name for spoolid from reader
*/
static int get_filename_from_reader(struct vmur *info)
{
char cmd[MAXCMDLEN];
char name[9] = {0};
char type[9] = {0};
char *buf;
sprintf(cmd, "QUERY RDR * %s ALL SHORTDATE", info->spoolid);
cpcmd(cmd, &buf, NULL, 0);
memcpy(name, &buf[130], 8);
util_strstrip(name);
memcpy(type, &buf[140], 8);
util_strstrip(type);
free(buf);
if (strlen(name) == 0) {
ERR("Please specify name to receive "
"unnamed spool file: %s\n", info->spoolid);
return 1;
}
to_valid_linux_name(name);
to_valid_linux_name(type);
if (strlen(type) == 0)
strcpy(info->file_name, name);
else
sprintf(info->file_name, "%s.%s", name, type);
info->file_name_specified = 1;
return 0;
}
/*
* Serialize access to unit record devices and their related spool file queues:
* open lock file and apply lock
*/
static void acquire_lock(struct vmur *info)
{
char failed_action[10] = {};
info->lock_fd = open(LOCK_FILE, O_RDONLY | O_CREAT, S_IRUSR);
if (info->lock_fd == -1) {
ERR("WARNING: Unable to open lock file %s, continuing "
"without any serialization.\n", LOCK_FILE);
return;
}
if (flock(info->lock_fd, LOCK_EX | LOCK_NB) == -1) {
switch (info->action) {
case RECEIVE:
strcpy(failed_action, "received");
break;
case PUNCH:
strcpy(failed_action, "punched");
break;
case PRINT:
strcpy(failed_action, "printed");
break;
case PURGE:
strcpy(failed_action, "purged");
break;
case ORDER:
strcpy(failed_action, "ordered");
break;
default:
strcpy(failed_action, "processed");
}
ERR("A concurrent instance of vmur is already active."
" No file %s.\n", failed_action);
exit(EBUSY);
}
}
/*
* Release lock and close lock file
*/
static void release_lock(struct vmur *info)
{
flock(info->lock_fd, LOCK_UN);
close(info->lock_fd);
info->lock_fd = -1;
}
/*
* Check if file already exists. If yes, ask if it should be overwritten.
*/
static void check_overwrite(struct vmur *info)
{
char buf[5] = {};
char *str;
struct stat stat_info;
if (info->force_specified)
return;
if (stat(info->file_name, &stat_info))
return;
if (S_ISDIR(stat_info.st_mode)) {
ERR("Cannot overwrite directory '%s'\n", info->file_name);
exit(1);
}
fprintf(stderr, "%s: Overwrite '%s'? ", prog_name, info->file_name);
str = fgets(buf, sizeof(buf), stdin);
if (!str)
exit(1);
if (strcasecmp(buf, "y\n") == 0)
return;
if (strcasecmp(buf, "yes\n") == 0)
return;
close_reader(info, "HOLD");
exit(0);
}
/*
* Get format of spool file: Check VMDUMP and NETDATA flags,
* set record length of file
*/
enum spoolfile_fmt get_spoolfile_fmt(struct vmur *info,
struct splink_page *buf)
{
struct splink_record *rec;
char netdata_id[5] = {0xc9, 0xd5, 0xd4, 0xd9, 0xf0}; /* EBCDIC: INMR0 */
unsigned int i;
rec = (struct splink_record *) &buf->data;
if (buf->magic != 0)
return TYPE_VMDUMP;
info->file_reclen = buf->rec_len;
for (i = 0; i < buf->data_recs; i++) {
if (rec->ccw.flag & CCW_IMMED_FLAG) {
rec = (struct splink_record *) ((char *) rec +
sizeof(rec->ccw));
continue; /* skip immediate CCWs */
}
if ((rec->data.flag & IS_CONTROL_RECORD) &&
(memcmp(rec->data.magic, netdata_id, 5) == 0))
return TYPE_NETDATA;
rec = (struct splink_record *) ((char *) rec + rec->record_len);
}
return TYPE_NORMAL;
}
/*
* Convert record for text mode: Do EBCDIC->ASCII translation
*/
static int convert_text(struct vmur *info, struct splink_record *rec,
char **out_ptr)
{
size_t in_count = rec->ccw.data_len;
size_t out_count = rec->ccw.data_len;
char *data_ptr = (char *) &rec->data;
int rc;
if ((rec->ccw.data_len == 1) && (data_ptr[0] == 0x40))
goto out; /* one blank -> just a newline */
rc = iconv(info->iconv, &data_ptr, &in_count, out_ptr, &out_count);
if ((rc == -1) || (in_count != 0)) {
ERR("Code page translation EBCDIC-ASCII failed\n");
return -1;
}
out:
**out_ptr = ASCII_LF;
*out_ptr += 1;
return 0;
}
/*
* Convert record for binary mode: Fill up missing 0x40 bytes
*/
static void convert_binary(struct vmur *info, struct splink_record *rec,
char **out_ptr)
{
int residual;
memcpy(*out_ptr, &rec->data, rec->ccw.data_len);
*out_ptr += rec->ccw.data_len;
/* Since CP removed trailing EBCDIC blanks, we have */
/* to insert them again */
residual = info->file_reclen - rec->ccw.data_len;
memset(*out_ptr, 0x40, residual);
*out_ptr += residual;
}
/*
* Convert record for blocked mode: remove padding bytes and add separator
*/
static void convert_blocked(struct vmur *info, struct splink_record *rec,
char **out_ptr)
{
int residual, i;
memcpy(*out_ptr, &rec->data, rec->ccw.data_len);
*out_ptr += rec->ccw.data_len;
/* Since CP removed trailing EBCDIC blanks, we have */
/* to insert them again */
residual = info->file_reclen - rec->ccw.data_len;
memset(*out_ptr, 0x40, residual);
*out_ptr += residual;
/* Now remove trailing padding bytes */
for (i = 0; i < info->file_reclen; i++) {
if (*(*out_ptr - 1) != info->blocked_padding)
break;
*out_ptr -= 1;
}
/* ... and insert separator */
**out_ptr = info->blocked_separator;
*out_ptr += 1;
}
/*
* Extract data from VM spool file data blocks (SPLINK).
*/
ssize_t convert_sfdata(struct vmur *info, struct splink_page *in, char *out)
{
struct splink_record *rec;
char *out_ptr = out;
unsigned int i, rc = 0;
rec = (struct splink_record *) &in->data;
for (i = 0; i < in->data_recs; i++) {
if (rec->ccw.opcode == NOP) {
rec = (struct splink_record *) ((char *) rec +
rec->record_len);
continue; /* skip NOP CCWs */
} else if (rec->ccw.flag & CCW_IMMED_FLAG) {
rec = (struct splink_record *) ((char *) rec +
sizeof(rec->ccw));
continue; /* skip immediate CCWs */
}
if (info->text_specified) {
rc = convert_text(info, rec, &out_ptr);
if (rc)
return rc;
} else if (info->blocked_specified) {
convert_blocked(info, rec, &out_ptr);
} else
convert_binary(info, rec, &out_ptr);
rec = (struct splink_record *) ((char *) rec + rec->record_len);
}
return out_ptr - out;
}
/*
* Write normal spool file data.
*/
int write_normal(struct vmur *info, struct splink_page *sfdata, int count,
int fho)
{
char *outbuf;
int len, i;
for (i = 0; i < count; i++) {
struct splink_page *data;
data = &sfdata[i];
outbuf = (char *) malloc((info->file_reclen + 1) *
data->data_recs);
if (!outbuf) {
ERR("Out of memory\n");
return -ENOMEM;
}
len = convert_sfdata(info, data, outbuf);
if (len < 0) {
ERR("Data conversion failed\n");
return -EINVAL;
}
if (write(fho, outbuf, len) == -1) {
ERR("Write to file %s failed: %s\n", info->file_name,
strerror(errno));
return -errno;
}
free(outbuf);
}
return 0;
}
/*
* Write vmdump data.
*/
int write_vmdump(struct vmur *info, struct splink_page *sfdata, int count,
int fho)
{
if (write(fho, sfdata, count * sizeof(sfdata[0])) == -1) {
ERR("Write to file %s failed: %s\n", info->file_name,
strerror(errno));
return -errno;
}
return 0;
}
/*
* Clean up and restore spool options
*/
static void cleanup_atexit_fn(void)
{
restore_spool_options(&vmur_info);
release_lock(&vmur_info);
}
/*
* Close VM virtual reader in case of a signal e.g. CTRL-C
*/
static void ur_receive_sig_handler(int UNUSED(sig), siginfo_t *UNUSED(sip),
void *UNUSED(p))
{
close_reader(&vmur_info, "HOLD");
cleanup_atexit_fn();
ERR_EXIT("Operation terminated, spool file received incompletely.\n");
}
/*
* Receive reader file.
*/
static void ur_receive(struct vmur *info)
{
struct splink_page sfdata[READ_BLOCKS];
enum spoolfile_fmt type;
int fhi, fho = STDOUT_FILENO, count;
int rc;
if (check_class(info))
ERR_EXIT("Reader device class does not match the specified "
"spool file class\n");
close_reader(info, "HOLD");
order_change_reader_file(info);
fhi = open(info->devnode, O_RDONLY | O_NONBLOCK);
if (fhi == -1)
ERR_EXIT("Could not open device %s\n%s\n", info->devnode,
strerror(errno));
set_signal_handler(info, ur_receive_sig_handler);
if (!info->stdout_specified) {
if (!info->file_name_specified &&
get_filename_from_reader(info))
goto fail;
check_overwrite(info);
}
/* Read first block and check spool file format */
count = read(fhi, &sfdata, READ_BLOCKS * sizeof(sfdata[0]));
if (count == -1) {
ERR("Could not read from device %s\n%s\n", info->devnode,
strerror(errno));
goto fail;
}
type = get_spoolfile_fmt(info, &sfdata[0]);
if (info->convert_specified) {
if (type != TYPE_VMDUMP) {
ERR("Reader file %s does not have VMDUMP format, "
"conversion not possible.\n", info->spoolid);
goto fail;
} else {
close(fhi);
if (info->stdout_specified)
rc = vmdump_convert(info->devnode, NULL,
prog_name);
else
rc = vmdump_convert(info->devnode,
info->file_name, prog_name);
if (rc)
goto fail;
else
goto vm_convert_done;
}
}
if (type == TYPE_VMDUMP)
ERR("INFO: Reader file %s has VMDUMP format.\n", info->spoolid);
if (type == TYPE_NETDATA)
ERR("INFO: Reader file %s has NETDATA format.\n",
info->spoolid);
if (type != TYPE_VMDUMP) {
int spoolid = atoi(info->spoolid);
if (spoolid != sfdata->spoolid) {
check_hold_state(info->spoolid);
ERR_EXIT("Could not receive spool file %s. Spoolid "
"mismatch (%i)\n", info->spoolid,
sfdata->spoolid);
}
}
/* read spool file data, convert it, and write it to output file or
* stdout */
if (!info->stdout_specified) {
fho = open(info->file_name, O_WRONLY | O_CREAT | O_TRUNC,
S_IRUSR | S_IWUSR);
if (fho == -1) {
ERR("Could not open file %s\n%s\n", info->file_name,
strerror(errno));
goto fail;
}
}
while (count != 0) {
int blocks;
blocks = count / sizeof(sfdata[0]);
if (type == TYPE_VMDUMP)
rc = write_vmdump(info, &sfdata[0], blocks, fho);
else
rc = write_normal(info, &sfdata[0], blocks, fho);
if (rc)
goto fail;
/* read next records */
count = read(fhi, &sfdata, READ_BLOCKS * sizeof(sfdata[0]));
if (count == -1) {
ERR("Could not read from device %s\n%s\n",
info->devnode, strerror(errno));
goto fail;
}
}
if (fho != STDOUT_FILENO)
close(fho);
close(fhi);
vm_convert_done:
if (info->hold_specified)
close_reader(info, "HOLD");
else
close_reader(info, "NOHOLD NOKEEP");
return;
fail:
close_reader(info, "HOLD");
exit(1);
}
/*
* Issue CP command CLOSE PUNCH
*/
static void close_ur_device(struct vmur *info)
{
char cmd[MAXCMDLEN], spoolid[5] = {}, *response;
int cprc;
if (info->node_specified) {
sprintf(cmd, "SPOOL %X NOCONT", info->devno);
cpcmd(cmd, NULL, NULL, 0);
}
if (info->rdr_specified) {
sprintf(cmd, "CLOSE %X TO ", info->devno);
if (info->node_specified)
strcat(cmd, RSCS_USERID);
else if (info->user_specified)
strcat(cmd, to_upper(info->user));
else
strcat(cmd, "*");
strcat(cmd, " RDR");
} else {
sprintf(cmd, "CLOSE %X", info->devno);
}
strcat(cmd, " NAME ");
strcat(cmd, info->spoolfile_name);
if (info->spoolfile_type_specified) {
strcat(cmd, " ");
strcat(cmd, info->spoolfile_type);
}
cpcmd_cs(cmd, &response, &cprc, 0);
memcpy(spoolid, &response[9], 4);
free(response);
if (cprc == 439) {
if (info->action == PUNCH)
sprintf(cmd, "PURGE * PUN %s", spoolid);
else
sprintf(cmd, "PURGE * PRT %s", spoolid);
cpcmd(cmd, NULL, NULL, 0);
ERR_EXIT("User %s spool fileid limit exceeded.\n"
, info->user);
}
if (info->rdr_specified) {
if (info->node_specified)
printf("Reader file with spoolid %s created and "
"transferred to %s.\n", spoolid, RSCS_USERID);
else if (info->user_specified)
printf("Reader file with spoolid %s created and "
"transferred to %s.\n", spoolid, info->user);
else
printf("Reader file with spoolid %s created.\n",
spoolid);
} else if (info->action == PUNCH)
printf("Punch file with spoolid %s created.\n", spoolid);
else
printf("Printer file with spoolid %s created.\n", spoolid);
}
/*
* Issue CP command CLOSE PUNCH
*/
static void close_ur_device_simple(struct vmur *info)
{
char cmd[MAXCMDLEN];
sprintf(cmd, "CLOSE %X", info->devno);
cpcmd(cmd, NULL, NULL, 0);
}
/*
* Issue CP command CLOSE PUNCH PURGE
*/
static void close_ur_device_purge(struct vmur *info)
{
char cmd[MAXCMDLEN];
if (info->node_specified) {
sprintf(cmd, "SPOOL %X NOCONT", info->devno);
cpcmd(cmd, NULL, NULL, 0);
}
sprintf(cmd, "CLOSE %X PURGE", info->devno);
cpcmd(cmd, NULL, NULL, 0);
}
/*
* Issue "CP QUERY VIRTUAL devno" to retrieve punch device information
* Exit, if devno has CONT status
*/
static int is_punch_cont(struct vmur *info)
{
char *buf;
char cmd[MAXCMDLEN];
int rc = 0;
sprintf(cmd, "QUERY VIRTUAL %X", info->devno);
cpcmd(cmd, &buf, NULL, 0);
if (!strncmp(buf + 15, " CONT", 6))
rc = 1;
free(buf);
return rc;
}
/*
* Provide tag information for RSCS
*/
static void rscs_punch_setup(struct vmur *info)
{
char cmd[MAXCMDLEN];
sprintf(cmd, "SPOOL %X CONT", info->devno);
cpcmd(cmd, NULL, NULL, 0);
sprintf(cmd, "TAG DEV %X %s %s", info->devno, info->node, info->user);
cpcmd(cmd, NULL, NULL, 0);
return;
}
/*
* Purge punch file in case of a signal e.g. CTRL-C
*/
static void ur_write_sig_handler(int UNUSED(sig), siginfo_t *UNUSED(sip),
void *UNUSED(p))
{
close_ur_device_purge(&vmur_info);
cleanup_atexit_fn();
ERR_EXIT("Operation terminated, no spool file created.\n");
}
/*
* Read on line from fd not including newline
*/
static int read_line(int fd, char *buf, int len, int lf)
{
int offs = 0;
memset(buf, 0, len);
do {
int rc;
rc = read(fd, buf + offs, 1);
if (rc < 0)
return -EIO;
if (rc == 0)
return -ENODATA;
if (*(buf + offs) == lf)
goto found;
offs++;
} while (offs < len);
return -EINVAL;
found:
*(buf + offs) = 0;
return offs;
}
/*
* Read text file for punch/print
*/
static int read_text_file(struct vmur *info, int fd, char *out_buf, size_t len)
{
unsigned int pos = 0;
static int line = 1;
char sep, pad;
char *buf;
int rc;
sep = '\n';
pad = ' ';
buf = (char *) malloc(info->ur_reclen + 1);
if (!buf)
return -ENOMEM;
do {
int line_len;
size_t rec_len, out_len;
char *in_ptr, *out_ptr;
line_len = read_line(fd, buf, info->ur_reclen + 1, sep);
if (line_len == -ENODATA) {
break;
} else if (line_len == -EINVAL) {
ERR("Input line %i too long. Unit record length"
" must not exceed %i\n", line, info->ur_reclen);
goto fail;
} else if (line_len < 0) {
ERR("Read failed: %s", strerror(errno));
goto fail;
}
line++;
memset(buf + line_len, pad, info->ur_reclen - line_len);
rec_len = out_len = info->ur_reclen;
in_ptr = buf;
out_ptr = &out_buf[pos];
rc = iconv(info->iconv, &in_ptr, &rec_len, &out_ptr, &out_len);
if ((rc == -1) || (out_len != 0)) {
ERR("Code page conversion failed at line %i\n", line);
goto fail;
}
pos += info->ur_reclen;
} while (pos < len);
free(buf);
return pos;
fail:
free(buf);
return -1;
}
/*
* Read blocked file for punch/print
*/
static int read_blocked_file(struct vmur *info, int fd, char *out_buf,
size_t len)
{
unsigned int pos = 0;
static int line = 1;
char sep, pad;
int line_len;
char *buf;
sep = info->blocked_separator;
pad = info->blocked_padding;
buf = (char *) malloc(info->ur_reclen + 1);
if (!buf)
return -ENOMEM;
do {
line_len = read_line(fd, buf, info->ur_reclen + 1, sep);
if (line_len == -ENODATA) {
break;
} else if (line_len == -EINVAL) {
ERR("Input line %i too long. Unit record length"
" must not exceed %i\n", line, info->ur_reclen);
goto fail;
} else if (line_len < 0) {
ERR("Read failed: %s", strerror(errno));
goto fail;
}
line++;
memset(buf + line_len, pad, info->ur_reclen - line_len);
memcpy(&out_buf[pos], buf, info->ur_reclen);
pos += info->ur_reclen;
} while (pos < len);
free(buf);
return pos;
fail:
free(buf);
return -1;
}
/*
* Read file for punch/print
*/
static int read_input_file(struct vmur *info, int fd, char *out_buf, size_t len)
{
int rc;
if (info->text_specified) {
rc = read_text_file(info, fd, out_buf, len);
} else if (info->blocked_specified) {
rc = read_blocked_file(info, fd, out_buf, len);
} else {
rc = read(fd, out_buf, len);
if (rc == -1) {
ERR("Could not read file %s\n%s\n", info->file_name,
strerror(errno));
return -EIO;
}
}
return rc;
}
/*
* Write function for punch and printer
*/
static void ur_write(struct vmur *info)
{
int fhi, fho, residual, anything_written = 0;
char *sfdata;
ssize_t count;
/* close punch preventively */
close_ur_device_simple(info);
/* Check punch. If punch is spooled CONT, exit */
if (is_punch_cont(info))
ERR_EXIT("Virtual punch device %X is spooled CONT.\n",
info->devno);
sfdata = (char *) malloc(info->ur_reclen * VMUR_REC_COUNT);
if (!sfdata)
ERR_EXIT("Could allocate memory for buffer (%i)\n",
info->ur_reclen);
/* Open Linux file */
if (info->file_name_specified) {
fhi = open(info->file_name, O_RDONLY);
if (fhi == -1)
ERR_EXIT("Could not open file %s\n%s\n",
info->file_name, strerror(errno));
} else {
fhi = STDIN_FILENO;
}
if (info->node_specified)
rscs_punch_setup(info);
/* Open UR device */
fho = open(info->devnode, O_WRONLY | O_NONBLOCK);
if (fho == -1) {
ERR("Could not open device %s\n%s\n", info->devnode,
strerror(errno));
goto fail;
}
set_signal_handler(info, ur_write_sig_handler);
/* read linux file data, and write it to VM punch device */
do {
count = read_input_file(info, fhi, sfdata,
info->ur_reclen * VMUR_REC_COUNT);
if (count < 0)
goto fail;
else if (count == 0)
break; /* EOF */
residual = (info->ur_reclen - (count % info->ur_reclen))
% info->ur_reclen;
memset(sfdata + count, 0, residual);
if (write(fho, sfdata, count + residual) == -1) {
ERR("Could not write on device %s (%s)\n",
info->devnode, strerror(errno));
if (errno == EIO)
ERR("Spool file limit exceeded or spool space "
"full?\n");
goto fail;
} else
anything_written = 1;
} while (1);
close(fho);
if (anything_written)
close_ur_device(info);
else
ERR_EXIT("No spool file created - probably empty input.\n");
free(sfdata);
if (fhi != STDIN_FILENO)
close(fhi);
return;
fail:
close_ur_device_purge(info);
exit(1);
}
/*
* Ask if file should be purged, if -f is not specified
*/
static void ur_purge_question(struct vmur *info)
{
char buf[5] = {};
char *str;
if (info->force_specified)
return;
fprintf(stderr, "%s: purge selected %s file(s)? ", prog_name,
info->queue);
/*
* Release the vmur session lock while waiting for user input. It is
* safe to release the lock here, because the ur_purge operation does
* not modify spool options.
*/
release_lock(info);
str = fgets(buf, sizeof(buf), stdin);
acquire_lock(info);
if (!str)
exit(1);
if (strcasecmp(buf, "y\n") == 0)
return;
if (strcasecmp(buf, "yes\n") == 0)
return;
exit(0);
}
/*
* Purge spool file
*/
static int ur_purge(struct vmur *info)
{
char *buf, cmd[MAXCMDLEN];
int n, m;
ur_purge_question(info);
/* Prepare the CP PURGE command */
n = m = sprintf(cmd, "PURGE * %s", info->queue);
/* Add selection criteria to match spool files */
if (info->spoolid_specified)
n += sprintf(cmd + n, " %s", info->spoolid);
if (info->spool_class_specified)
n += sprintf(cmd + n, " CLASS %c", info->spool_class);
if (info->spool_form_specified)
n += sprintf(cmd + n, " FORM %s", info->spool_form);
if (info->spool_dest_specified)
n += sprintf(cmd + n, " DEST %s", info->spool_dest);
/* Purge all files if no spoolid or any of the class, form, or dest
* option is specified.
*/
if (n == m)
n += sprintf(cmd + n, " ALL");
cpcmd(cmd, &buf, NULL, 0);
ERR("%s", buf);
free(buf);
return 0;
}
/*
* Order spool file to top of the queue
*/
static int ur_order(struct vmur *info)
{
char cmd[MAXCMDLEN];
sprintf(cmd, "ORDER * %s %s", info->queue, info->spoolid);
cpcmd(cmd, NULL, NULL, 0);
return 0;
}
/*
* List spool files
*/
static int ur_list(struct vmur *info)
{
char *buf;
char cmd[MAXCMDLEN];
if (info->spoolid_specified)
sprintf(cmd, "QUERY %s * %s ALL", info->queue, info->spoolid);
else
sprintf(cmd, "QUERY %s * ALL", info->queue);
cpcmd(cmd, &buf, NULL, 1);
printf("%s", buf);
free(buf);
return 0;
}
/*
* Initialize iconv: "from" -> "to"
*/
static void setup_iconv(struct vmur *info, const char *from, const char *to)
{
info->iconv = iconv_open(to, from);
if (info->iconv == ((iconv_t) -1))
ERR_EXIT("Could not initialize conversion table %s->%s.\n",
from, to);
}
int main(int argc, char **argv)
{
/* Set name of program */
prog_name = basename(argv[0]);
/* Set default values */
init_info(&vmur_info);
/* Parse command line options and check syntax */
parse_opts(&vmur_info, argc, argv);
/* Register cleanup function */
if (atexit(cleanup_atexit_fn))
ERR_EXIT("Could not set up vmur session cleanup\n");
/* Acquire a lock to serialize concurrent vmur invocations */
acquire_lock(&vmur_info);
/* Retrieve ur device number */
setup_ur_device(&vmur_info);
switch (vmur_info.action) {
case RECEIVE:
/* Setup spool options */
setup_spool_options(&vmur_info);
if (vmur_info.text_specified)
setup_iconv(&vmur_info, EBCDIC_CODE_PAGE,
ASCII_CODE_PAGE);
ur_receive(&vmur_info);
break;
case PUNCH:
case PRINT:
/* Setup spool options */
setup_spool_options(&vmur_info);
if (vmur_info.text_specified)
setup_iconv(&vmur_info, ASCII_CODE_PAGE,
EBCDIC_CODE_PAGE);
ur_write(&vmur_info);
break;
case PURGE:
ur_purge(&vmur_info);
break;
case ORDER:
ur_order(&vmur_info);
break;
case LIST:
ur_list(&vmur_info);
break;
default:
ERR("Internal error: unknown action '%i'\n", vmur_info.action);
return -EINVAL;
}
return 0;
}