314 lines
9.2 KiB
C
314 lines
9.2 KiB
C
/*
|
|
* blkpr.c -- persistent reservations on a block device.
|
|
*
|
|
* Copyright (C) 2021-2022 zhenwei pi <pizhenwei@bytedance.com>
|
|
*
|
|
* 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 of the License, 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. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
* This program uses IOC_PR_XXX ioctl to run persistent reservations
|
|
* command on a block device if the device supports it.
|
|
*/
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <getopt.h>
|
|
#include <locale.h>
|
|
#include <sys/ioctl.h>
|
|
#include <linux/pr.h>
|
|
|
|
#include "nls.h"
|
|
#include "c.h"
|
|
#include "closestream.h"
|
|
#include "strutils.h"
|
|
#include "xalloc.h"
|
|
|
|
struct type_string {
|
|
int type;
|
|
char *str;
|
|
char *desc;
|
|
};
|
|
|
|
/* This array should keep align with enum pr_type of linux/types.h */
|
|
static const struct type_string pr_type[] = {
|
|
{PR_WRITE_EXCLUSIVE, "write-exclusive",
|
|
" * write-exclusive: Only the initiator that owns the reservation can\n"
|
|
" write to the device. Any initiator can read from the device.\n"},
|
|
|
|
{PR_EXCLUSIVE_ACCESS, "exclusive-access",
|
|
" * exclusive-access: Only the initiator that owns the reservation can\n"
|
|
" access the device.\n"},
|
|
|
|
{PR_WRITE_EXCLUSIVE_REG_ONLY, "write-exclusive-reg-only",
|
|
" * write-exclusive-reg-only: Only initiators with a registered key can\n"
|
|
" write to the device, any initiator can read from the device.\n"},
|
|
|
|
{PR_EXCLUSIVE_ACCESS_REG_ONLY, "exclusive-access-reg-only",
|
|
" * exclusive-access-reg-only: Only initiators with a registered key can\n"
|
|
" access the device.\n"},
|
|
|
|
{PR_WRITE_EXCLUSIVE_ALL_REGS, "write-exclusive-all-regs",
|
|
" * write-exclusive-all-regs: Only initiators with a registered key can\n"
|
|
" write to the device. Any initiator can read from the device. All\n"
|
|
" initiators with a registered key are considered reservation holders.\n"
|
|
" Please reference the SPC spec on the meaning of a reservation holder\n"
|
|
" if you want to use this type.\n"},
|
|
|
|
{PR_EXCLUSIVE_ACCESS_ALL_REGS, "exclusive-access-all-regs",
|
|
" * exclusive-access-all-regs: Only initiators with a registered key can\n"
|
|
" access the device. All initiators with a registered key are considered\n"
|
|
" reservation holders. Please reference the SPC spec on the meaning of\n"
|
|
" a reservation holder if you want to use this type.\n"}
|
|
};
|
|
|
|
static const struct type_string pr_command[] = {
|
|
{IOC_PR_REGISTER, "register",
|
|
" * register: This command registers a new reservation if the key argument\n"
|
|
" is non-null. If no existing reservation exists oldkey must be zero, if\n"
|
|
" an existing reservation should be replaced oldkey must contain the old\n"
|
|
" reservation key. If the key argument is 0 it unregisters the existing\n"
|
|
" reservation passed in oldkey.\n"
|
|
},
|
|
|
|
{IOC_PR_RESERVE, "reserve",
|
|
" * reserve: This command reserves the device and thus restricts access for\n"
|
|
" other devices based on the type argument. The key argument must be\n"
|
|
" the existing reservation key for the device as acquired by the register,\n"
|
|
" preempt, preempt-abort commands.\n"},
|
|
|
|
{IOC_PR_RELEASE, "release",
|
|
" * release: This command releases the reservation specified by key and flags\n"
|
|
" and thus removes any access restriction implied by it.\n"},
|
|
|
|
{IOC_PR_PREEMPT, "preempt",
|
|
" * preempt: This command releases the existing reservation referred to by\n"
|
|
" old_key and replaces it with a new reservation of type for the\n"
|
|
" reservation key key.\n"},
|
|
|
|
{IOC_PR_PREEMPT_ABORT, "preempt-abort",
|
|
" * preempt-abort: This command works like preempt except that it also aborts\n"
|
|
" any outstanding command sent over a connection identified by oldkey.\n"},
|
|
|
|
{IOC_PR_CLEAR, "clear",
|
|
" * clear: This command unregisters both key and any other reservation\n"
|
|
" key registered with the device and drops any existing reservation.\n"},
|
|
};
|
|
|
|
static const struct type_string pr_flag[] = {
|
|
{PR_FL_IGNORE_KEY, "ignore-key",
|
|
" * ignore-key: Ignore the existing reservation key. This is commonly\n"
|
|
" supported for register command, and some implementation may support\n"
|
|
" the flag for reserve command.\n"}
|
|
};
|
|
|
|
static void print_type(FILE *out, const struct type_string *ts, size_t nmem)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < nmem; i++) {
|
|
fprintf(out, "%s\n", ts[i].desc);
|
|
}
|
|
}
|
|
|
|
|
|
static int parse_type_by_str(const struct type_string *ts, int nmem, char *pattern)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < nmem; i++) {
|
|
if (!strcmp(ts[i].str, pattern))
|
|
return ts[i].type;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
|
|
#define PRINT_SUPPORTED(XX) \
|
|
static void print_##XX(FILE *out) \
|
|
{ print_type(out, XX, ARRAY_SIZE(XX)); }
|
|
|
|
#define PARSE(XX) \
|
|
static int parse_##XX(char *pattern) \
|
|
{ return parse_type_by_str(XX, ARRAY_SIZE(XX), pattern); }
|
|
|
|
PRINT_SUPPORTED(pr_type)
|
|
PRINT_SUPPORTED(pr_command)
|
|
PRINT_SUPPORTED(pr_flag)
|
|
|
|
PARSE(pr_type)
|
|
PARSE(pr_command)
|
|
PARSE(pr_flag)
|
|
|
|
static int do_pr(char *path, uint64_t key, uint64_t oldkey, int op, int type, int flag)
|
|
{
|
|
struct pr_registration pr_reg;
|
|
struct pr_reservation pr_res;
|
|
struct pr_preempt pr_prt;
|
|
struct pr_clear pr_clr;
|
|
int fd, ret;
|
|
|
|
fd = open(path, O_RDWR);
|
|
if (fd < 0)
|
|
err(EXIT_FAILURE, _("cannot open %s"), path);
|
|
|
|
switch (op) {
|
|
case IOC_PR_REGISTER:
|
|
pr_reg.old_key = oldkey;
|
|
pr_reg.new_key = key;
|
|
pr_reg.flags = flag;
|
|
ret = ioctl(fd, op, &pr_reg);
|
|
break;
|
|
case IOC_PR_RESERVE:
|
|
case IOC_PR_RELEASE:
|
|
pr_res.key = key;
|
|
pr_res.type = type;
|
|
pr_res.flags = flag;
|
|
ret = ioctl(fd, op, &pr_res);
|
|
break;
|
|
case IOC_PR_PREEMPT:
|
|
case IOC_PR_PREEMPT_ABORT:
|
|
pr_prt.old_key = oldkey;
|
|
pr_prt.new_key = key;
|
|
pr_prt.type = type;
|
|
pr_prt.flags = flag;
|
|
ret = ioctl(fd, op, &pr_prt);
|
|
break;
|
|
case IOC_PR_CLEAR:
|
|
pr_clr.key = key;
|
|
pr_clr.flags = flag;
|
|
ret = ioctl(fd, op, &pr_clr);
|
|
break;
|
|
default:
|
|
errno = EINVAL;
|
|
err(EXIT_FAILURE, _("unknown command"));
|
|
}
|
|
|
|
close(fd);
|
|
if (ret < 0)
|
|
err(EXIT_FAILURE, _("pr ioctl failed"));
|
|
if (ret > 0)
|
|
errx(EXIT_FAILURE, _("error code 0x%x, for more detailed information see specification of device model."), ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void __attribute__((__noreturn__)) usage(void)
|
|
{
|
|
FILE *out = stdout;
|
|
|
|
fputs(USAGE_HEADER, out);
|
|
fprintf(out,
|
|
_(" %s [options] <device>\n"), program_invocation_short_name);
|
|
|
|
fputs(USAGE_SEPARATOR, out);
|
|
fputs(_("Persistent reservations on a device.\n"), out);
|
|
|
|
fputs(USAGE_OPTIONS, out);
|
|
fputs(_(" -c, --command <cmd> command of persistent reservations\n"), out);
|
|
fputs(_(" -k, --key <num> key to operate\n"), out);
|
|
fputs(_(" -K, --oldkey <num> old key to operate\n"), out);
|
|
fputs(_(" -f, --flag <flag> command flag\n"), out);
|
|
fputs(_(" -t, --type <type> command type\n"), out);
|
|
|
|
fputs(USAGE_SEPARATOR, out);
|
|
printf(USAGE_HELP_OPTIONS(26));
|
|
|
|
fputs(USAGE_ARGUMENTS, out);
|
|
|
|
fputs(_(" <cmd> is an command, available command:\n"), out);
|
|
print_pr_command(out);
|
|
|
|
fputs(_(" <flag> is a command flag, available flags:\n"), out);
|
|
print_pr_flag(out);
|
|
|
|
fputs(_(" <type> is a command type, available types:\n"), out);
|
|
print_pr_type(out);
|
|
|
|
printf(USAGE_MAN_TAIL("blkpr(8)"));
|
|
exit(EXIT_SUCCESS);
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
char c;
|
|
char *path;
|
|
uint64_t key = 0, oldkey = 0;
|
|
int command = -1, type = -1, flag = 0;
|
|
|
|
static const struct option longopts[] = {
|
|
{ "help", no_argument, NULL, 'h' },
|
|
{ "version", no_argument, NULL, 'V' },
|
|
{ "command", required_argument, NULL, 'c' },
|
|
{ "key", required_argument, NULL, 'k' },
|
|
{ "oldkey", required_argument, NULL, 'K' },
|
|
{ "flag", required_argument, NULL, 'f' },
|
|
{ "type", required_argument, NULL, 't' },
|
|
{ NULL, 0, NULL, 0 }
|
|
};
|
|
|
|
setlocale(LC_ALL, "");
|
|
bindtextdomain(PACKAGE, LOCALEDIR);
|
|
textdomain(PACKAGE);
|
|
close_stdout_atexit();
|
|
|
|
errno = EINVAL;
|
|
while ((c = getopt_long(argc, argv, "hVc:k:K:f:t:", longopts, NULL)) != -1) {
|
|
switch(c) {
|
|
case 'k':
|
|
key = strtosize_or_err(optarg,
|
|
_("failed to parse key"));
|
|
break;
|
|
case 'K':
|
|
oldkey = strtosize_or_err(optarg,
|
|
_("failed to parse old key"));
|
|
break;
|
|
case 'c':
|
|
command = parse_pr_command(optarg);
|
|
if (command < 0)
|
|
err(EXIT_FAILURE, _("unknown command"));
|
|
break;
|
|
case 't':
|
|
type = parse_pr_type(optarg);
|
|
if (type < 0)
|
|
err(EXIT_FAILURE, _("unknown type"));
|
|
break;
|
|
case 'f':
|
|
flag = parse_pr_flag(optarg);
|
|
if (flag < 0)
|
|
err(EXIT_FAILURE, _("unknown flag"));
|
|
break;
|
|
|
|
case 'h':
|
|
usage();
|
|
case 'V':
|
|
print_version(EXIT_SUCCESS);
|
|
default:
|
|
errtryhelp(EXIT_FAILURE);
|
|
}
|
|
}
|
|
|
|
if (optind == argc)
|
|
errx(EXIT_FAILURE, _("no device specified"));
|
|
|
|
path = argv[optind++];
|
|
if (optind != argc) {
|
|
warnx(_("unexpected number of arguments"));
|
|
errtryhelp(EXIT_FAILURE);
|
|
}
|
|
|
|
do_pr(path, key, oldkey, command, type, flag);
|
|
|
|
return EXIT_SUCCESS;
|
|
}
|