Files
s390-tools/tunedasd/src/tunedasd.c
Michael Holzheu e0c48d6af8 Fix copyright dates
Signed-off-by: Michael Holzheu <holzheu@linux.vnet.ibm.com>
2017-08-21 12:06:12 +02:00

599 lines
14 KiB
C

/*
* tunedasd - Adjust tunable parameters on DASD devices
*
* Copyright IBM Corp. 2004, 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 <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "lib/util_file.h"
#include "lib/util_opt.h"
#include "lib/util_prg.h"
#include "lib/zt_common.h"
#include "disk.h"
#include "error.h"
#include "tunedasd.h"
static const struct util_prg prg = {
.desc = "Adjust tunable DASD parameters. More than one DEVICE node can "
"be specified, but at least one (e.g. /dev/dasda)",
.args = "DEVICE1 [DEVICE2...]",
.copyright_vec = {
{
.owner = "IBM Corp.",
.pub_first = 1999,
.pub_last = 2017,
},
UTIL_PRG_COPYRIGHT_END
}
};
/* Defines for options with no short command */
#define OPT_PATH_RESET_ALL 128
#define OPT_ENABLE_STATS 129
#define OPT_DISABLE_STATS 130
static struct util_opt opt_vec[] = {
UTIL_OPT_SECTION("CACHING MODES (ECKD ONLY)"),
{
.option = { "cache", required_argument, NULL, 'c' },
.argument = "BEHAVIOUR",
.desc = "Specify caching behaviour on storage server: "
"normal, bypass, inhibit, sequential, prestage, or record",
},
{
.option = { "no_cyl", required_argument, NULL, 'n' },
.argument = "NUM",
.desc = "NUM cylinders to be cached (only valid with -c/--cache)",
},
{
.option = { "get_cache", no_argument, NULL, 'g' },
.desc = "Get current storage server caching behaviour",
},
UTIL_OPT_SECTION("RESERVE / RELEASE"),
{
.option = { "release", no_argument, NULL, 'L' },
.desc = "Release device",
},
{
.option = { "slock", no_argument, NULL, 'O' },
.desc = "Unconditional reservce device\n"
"NOTE: Use with care, this breaks an existing lock",
},
{
.option = { "query_reserve", no_argument, NULL, 'Q' },
.desc = "Print reserve status of device",
},
{
.option = { "reserve", no_argument, NULL, 'S' },
.desc = "Reserve device",
},
UTIL_OPT_SECTION("PERFORMANCE STATISTICS"),
{
.option = {
"enable-stats", no_argument, NULL, OPT_ENABLE_STATS
},
.desc = "Enable performance statistics globally",
.flags = UTIL_OPT_FLAG_NOSHORT,
},
{
.option = {
"disable-stats", no_argument, NULL, OPT_DISABLE_STATS
},
.desc = "Disable performance statistics globally",
.flags = UTIL_OPT_FLAG_NOSHORT,
},
{
.option = { "prof_item", required_argument, NULL, 'I' },
.argument = "ROW",
.desc = "Print single profile item: reqs, sects, sizes, total, "
"totsect, start, irq, irqsect, end, or queue",
},
{
.option = { "profile", no_argument, NULL, 'P' },
.desc = "Print profile info of device",
},
{
.option = { "reset_prof", no_argument, NULL, 'R' },
.desc = "Reset profile info of device",
},
UTIL_OPT_SECTION("MISC"),
{
.option = { "path_reset", required_argument, NULL, 'p' },
.argument = "CHPID",
.desc = "Reset channel path CHPID of a device",
},
{
.option = {
"path_reset_all", no_argument, NULL, OPT_PATH_RESET_ALL
},
.desc = "Reset all channel paths of a device",
.flags = UTIL_OPT_FLAG_NOSHORT,
},
UTIL_OPT_HELP,
UTIL_OPT_VERSION,
UTIL_OPT_END
};
#define CMD_KEYWORD_NUM 16
#define DEVICES_NUM 256
enum cmd_keyword_id {
cmd_keyword_help,
cmd_keyword_version,
cmd_keyword_get_cache,
cmd_keyword_cache,
cmd_keyword_no_cyl,
cmd_keyword_reserve,
cmd_keyword_release,
cmd_keyword_slock,
cmd_keyword_profile,
cmd_keyword_prof_item,
cmd_keyword_reset_prof,
cmd_keyword_query_reserve,
cmd_keyword_path,
cmd_keyword_path_all,
cmd_keyword_enable_stats,
cmd_keyword_disable_stats,
};
/* Mapping of keyword IDs to strings */
static const struct {
char* keyword;
enum cmd_keyword_id id;
} keyword_list[] = {
{ "help", cmd_keyword_help },
{ "version", cmd_keyword_version },
{ "get_cache", cmd_keyword_get_cache },
{ "cache", cmd_keyword_cache },
{ "no_cyl", cmd_keyword_no_cyl },
{ "reserve", cmd_keyword_reserve },
{ "release", cmd_keyword_release },
{ "slock", cmd_keyword_slock },
{ "profile", cmd_keyword_profile },
{ "prof_item", cmd_keyword_prof_item },
{ "reset_prof", cmd_keyword_reset_prof },
{ "query_reserve", cmd_keyword_query_reserve },
{ "path_reset", cmd_keyword_path },
{ "path_reset_all", cmd_keyword_path_all },
{ "enable-stats", cmd_keyword_enable_stats },
{ "disable-stats", cmd_keyword_disable_stats }
};
enum cmd_key_state {
req, /* Keyword is required */
opt, /* Keyword is optional */
inv /* Keyword is invalid */
};
/* Determines which combination of keywords are valid */
static enum cmd_key_state cmd_key_table[CMD_KEYWORD_NUM][CMD_KEYWORD_NUM] = {
/* help vers get_ cach no_c rese rele sloc prof prof rese quer path path
* ion cach e yl rve ase k ile _ite t_pr y_re _all
* e m of serv
*/
/* help */ { req, opt, opt, opt, opt, opt, opt, opt, opt, opt, opt, inv, inv, inv, inv, inv },
/* version */ { inv, req, inv, inv, inv, inv, inv, inv, inv, inv, inv, inv, inv, inv, inv, inv },
/* get_cache */ { opt, opt, req, inv, inv, inv, inv, inv, inv, inv, inv, inv, inv, inv, inv, inv },
/* cache */ { opt, opt, inv, req, opt, inv, inv, inv, inv, inv, inv, inv, inv, inv, inv, inv },
/* no_cyl */ { opt, opt, inv, req, req, inv, inv, inv, inv, inv, inv, inv, inv, inv, inv, inv },
/* reserve */ { opt, opt, inv, inv, inv, req, inv, inv, inv, inv, inv, inv, inv, inv, inv, inv },
/* release */ { opt, opt, inv, inv, inv, inv, req, inv, inv, inv, inv, inv, inv, inv, inv, inv },
/* slock */ { opt, opt, inv, inv, inv, inv, inv, req, inv, inv, inv, inv, inv, inv, inv, inv },
/* profile */ { opt, opt, inv, inv, inv, inv, inv, inv, req, opt, inv, inv, inv, inv, inv, inv },
/* prof_item */ { opt, opt, inv, inv, inv, inv, inv, inv, req, req, inv, inv, inv, inv, inv, inv },
/* reset_prof */ { opt, opt, inv, inv, inv, inv, inv, inv, inv, inv, req, inv, inv, inv, inv, inv },
/* query_reserve */ { inv, inv, inv, inv, inv, inv, inv, inv, inv, inv, inv, req, inv, inv, inv, inv },
/* path */ { inv, inv, inv, inv, inv, inv, inv, inv, inv, inv, inv, inv, req, inv, inv, inv },
/* path_all */ { inv, inv, inv, inv, inv, inv, inv, inv, inv, inv, inv, inv, inv, req, inv, inv },
/* enable-stats */ { inv, inv, inv, inv, inv, inv, inv, inv, inv, inv, inv, inv, inv, inv, req, inv },
/* disable-stats */ { inv, inv, inv, inv, inv, inv, inv, inv, inv, inv, inv, inv, inv, inv, inv, req },
};
struct parameter {
int kw_given;
char *data;
};
struct command_line {
struct parameter parm[CMD_KEYWORD_NUM];
char * devices[DEVICES_NUM];
int device_id;
};
/* Error message string */
#define ERROR_STRING_SIZE 1024
static char error_string[ERROR_STRING_SIZE];
/*
* Generate and print an error message based on the formatted
* text string FMT and a variable amount of extra arguments.
*/
void
error_print (const char* fmt, ...)
{
va_list args;
va_start (args, fmt);
vsnprintf (error_string, ERROR_STRING_SIZE, fmt, args);
va_end (args);
fprintf (stderr, "Error: %s\n", error_string);
}
/*
* Print usage information.
*/
static void print_usage(void)
{
util_prg_print_help();
util_opt_print_help();
}
/*
* Check whether calling user is root. Return 0 if user is root, non-zero
* otherwise.
*/
static int
check_for_root (void)
{
if (geteuid () != 0) {
error_print ("Must be root to perform this operation");
return -1;
} else {
return 0;
}
}
/*
* Retrieve name of keyword identified by ID.
*/
static char *get_keyword_name(enum cmd_keyword_id id)
{
unsigned int i;
for (i = 0; i < sizeof (keyword_list) / sizeof (keyword_list[0]);
i++) {
if (id == keyword_list[i].id) {
return keyword_list[i].keyword;
}
}
return "<unknown>";
}
/*
* Check the given function for given options and valid combinations of
* options
*/
static int check_key_state(struct command_line *cmdline)
{
int i,j;
/* Find first given keyword */
for (i = 0; i < CMD_KEYWORD_NUM && !cmdline->parm[i].kw_given; i++);
if (i >= CMD_KEYWORD_NUM) {
error_print ("No valid parameter specified");
print_usage ();
return -1;
}
/* Check keywords */
for (j = 0; j < CMD_KEYWORD_NUM; j++) {
switch (cmd_key_table[i][j]) {
case req:
/* Missing keyword on command line */
if (!(cmdline->parm[j].kw_given)) {
error_print ("Option '%s' required when "
"specifying '%s'",
get_keyword_name (j),
get_keyword_name (i));
return -1;
}
break;
case inv:
/* Invalid keyword on command line */
if (cmdline->parm[j].kw_given) {
error_print ("Only one of options '%s' and "
"'%s' allowed",
get_keyword_name (i),
get_keyword_name (j));
return -1;
}
break;
case opt:
break;
}
}
return 0;
}
/*
* Save the given command together with its parameter.
*/
static int
store_option (struct command_line* cmdline, enum cmd_keyword_id keyword,
char* value)
{
if ((cmdline->parm[(int) keyword]).kw_given) {
error_print ("Option '%s' specified more than once",
get_keyword_name (keyword));
return -1;
}
cmdline->parm[(int) keyword].kw_given = 1;
cmdline->parm[(int) keyword].data = value;
return 0;
}
/*
* Parse the command line for valid parameters.
*/
static int get_command_line(int argc, char *argv[], struct command_line *line)
{
struct command_line cmdline;
int opt;
int rc;
util_prg_init(&prg);
util_opt_init(opt_vec, NULL);
memset ((void *) &cmdline, 0, sizeof (struct command_line));
/* Process options */
do {
opt = util_opt_getopt_long(argc, argv);
rc = 0;
switch (opt) {
case 'h':
rc = store_option (&cmdline, cmd_keyword_help,
optarg);
break;
case 'v':
rc = store_option (&cmdline, cmd_keyword_version,
optarg);
break;
case 'g':
rc = store_option (&cmdline, cmd_keyword_get_cache,
optarg);
break;
case 'c':
rc = check_cache (optarg);
if (rc >= 0) {
rc = store_option (&cmdline, cmd_keyword_cache,
optarg);
}
break;
case 'n':
rc = check_no_cyl (optarg);
if (rc >= 0) {
rc = store_option (&cmdline,
cmd_keyword_no_cyl,
optarg);
}
break;
case 'p':
rc = store_option(&cmdline, cmd_keyword_path, optarg);
break;
case OPT_PATH_RESET_ALL:
rc = store_option(&cmdline, cmd_keyword_path_all,
optarg);
break;
case 'S':
rc = store_option (&cmdline, cmd_keyword_reserve,
optarg);
break;
case 'L':
rc = store_option (&cmdline, cmd_keyword_release,
optarg);
break;
case 'O':
rc = store_option (&cmdline, cmd_keyword_slock,
optarg);
break;
case OPT_ENABLE_STATS:
rc = store_option(&cmdline, cmd_keyword_enable_stats,
optarg);
break;
case OPT_DISABLE_STATS:
rc = store_option(&cmdline, cmd_keyword_disable_stats,
optarg);
break;
case 'P':
rc = store_option (&cmdline, cmd_keyword_profile,
optarg);
break;
case 'I':
rc = check_prof_item (optarg);
if (rc >= 0) {
rc = store_option (&cmdline,
cmd_keyword_prof_item,
optarg);
}
break;
case 'R':
rc = store_option (&cmdline, cmd_keyword_reset_prof,
optarg);
break;
case 'Q':
rc = store_option (&cmdline, cmd_keyword_query_reserve,
optarg);
break;
case -1:
/* End of options string - start of devices list */
cmdline.device_id = optind;
break;
default:
fprintf(stderr, "Try 'tunedasd --help' for more"
" information.\n");
rc = -1;
break;
}
if (rc < 0) {
return rc;
}
} while (opt != -1);
*line = cmdline;
return 0;
}
/*
* Execute the command.
*/
static int do_command(char *device, struct command_line cmdline)
{
int i, rc;
rc = 0;
for (i = 0; !cmdline.parm[i].kw_given; i++);
switch (i) {
case cmd_keyword_get_cache:
rc = disk_get_cache (device);
break;
case cmd_keyword_cache:
rc = disk_set_cache (device,
cmdline.parm[cmd_keyword_cache].data,
cmdline.parm[cmd_keyword_no_cyl].data);
break;
case cmd_keyword_no_cyl:
break;
case cmd_keyword_reserve:
rc = disk_reserve (device);
break;
case cmd_keyword_release:
rc = disk_release (device);
break;
case cmd_keyword_slock:
rc = disk_slock (device);
break;
case cmd_keyword_profile:
rc = disk_profile (device,
cmdline.parm[cmd_keyword_prof_item].data);
break;
case cmd_keyword_reset_prof:
rc = disk_reset_prof (device);
break;
case cmd_keyword_prof_item:
break;
case cmd_keyword_query_reserve:
rc = disk_query_reserve_status(device);
break;
case cmd_keyword_path:
rc = disk_reset_chpid(device,
cmdline.parm[cmd_keyword_path].data);
break;
case cmd_keyword_path_all:
rc = disk_reset_chpid(device, NULL);
break;
default:
error_print ("Unknown command '%s' specified",
get_keyword_name (i));
break;
}
return rc;
}
/*
* Enable/Disable DASD performance statistics globally by writing
* 'set on' or 'set off' to /proc/dasd/statistics.
*/
static int tunedasd_set_global_stats(int val)
{
const char *path = "/proc/dasd/statistics";
int rc = 0;
if (val)
rc = util_file_write_s("set on", path);
else
rc = util_file_write_s("set off", path);
if (rc)
error_print("Could not enable/disable performance statistics");
else
printf("Performance statistics %sabled\n", val ? "en" : "dis");
return rc;
}
/*
* Main.
*/
int
main (int argc, char* argv[])
{
struct command_line cmdline;
int rc, finalrc;
/* Find out what we're supposed to do */
rc = get_command_line (argc, argv, &cmdline);
if (rc) {
return 1;
}
rc= check_key_state (&cmdline);
if (rc) {
return 1;
}
/* Check for priority options --help and --version */
if (cmdline.parm[cmd_keyword_help].kw_given) {
print_usage ();
return 0;
} else if (cmdline.parm[cmd_keyword_version].kw_given) {
util_prg_print_version();
return 0;
}
/* Make sure we're running as root */
if (check_for_root ()) {
return 1;
}
/* Enable/Disable performance statistics */
if (cmdline.parm[cmd_keyword_enable_stats].kw_given)
return tunedasd_set_global_stats(1);
if (cmdline.parm[cmd_keyword_disable_stats].kw_given)
return tunedasd_set_global_stats(0);
/* Do each of the commands on each of the devices
* and don't care about the return codes */
if (cmdline.device_id >= argc) {
error_print ("Missing device");
print_usage ();
return 1;
}
finalrc = 0;
while (cmdline.device_id < argc) {
rc = do_command (argv[cmdline.device_id], cmdline);
if (rc && !finalrc)
finalrc = rc;
cmdline.device_id++;
}
return finalrc;
}