[RFC PATCH 3/5] tools rpmb: add RPBM access tool

From: Alex Bennée
Date: Wed Mar 03 2021 - 12:50:08 EST


Add simple RPMB host testing tool. It can be used to program key,
write and read data block, and retrieve write counter.

[AJB: the principle differences are a simpler ioctl API which doesn't
need to do the low level frame construction itself. It also uses the
kernel keychain API for managing the keys]

Signed-off-by: Alex Bennée <alex.bennee@xxxxxxxxxx>
Cc: Tomas Winkler <tomas.winkler@xxxxxxxxx>
Cc: Alexander Usyskin <alexander.usyskin@xxxxxxxxx>
---
MAINTAINERS | 1 +
tools/Makefile | 14 +-
tools/rpmb/.gitignore | 2 +
tools/rpmb/Makefile | 41 +++
tools/rpmb/rpmb.c | 649 ++++++++++++++++++++++++++++++++++++++++++
5 files changed, 702 insertions(+), 5 deletions(-)
create mode 100644 tools/rpmb/.gitignore
create mode 100644 tools/rpmb/Makefile
create mode 100644 tools/rpmb/rpmb.c

diff --git a/MAINTAINERS b/MAINTAINERS
index c60b41b6e6bd..8b0768b16eae 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -15376,6 +15376,7 @@ S: Supported
F: drivers/char/rpmb/*
F: include/uapi/linux/rpmb.h
F: include/linux/rpmb.h
+F: tools/rpmb/

RTL2830 MEDIA DRIVER
M: Antti Palosaari <crope@xxxxxx>
diff --git a/tools/Makefile b/tools/Makefile
index 85af6ebbce91..916ab8f8cefc 100644
--- a/tools/Makefile
+++ b/tools/Makefile
@@ -27,6 +27,7 @@ help:
@echo ' objtool - an ELF object analysis tool'
@echo ' pci - PCI tools'
@echo ' perf - Linux performance measurement and analysis tool'
+ @echo ' rpmb - Replay protected memory block access tool'
@echo ' selftests - various kernel selftests'
@echo ' bootconfig - boot config tool'
@echo ' spi - spi tools'
@@ -64,7 +65,7 @@ acpi: FORCE
cpupower: FORCE
$(call descend,power/$@)

-cgroup firewire hv guest bootconfig spi usb virtio vm bpf iio gpio objtool leds wmi pci firmware debugging: FORCE
+cgroup firewire hv guest bootconfig rpmb spi usb virtio vm bpf iio gpio objtool leds wmi pci firmware debugging: FORCE
$(call descend,$@)

bpf/%: FORCE
@@ -100,7 +101,7 @@ kvm_stat: FORCE
$(call descend,kvm/$@)

all: acpi cgroup cpupower gpio hv firewire liblockdep \
- perf selftests bootconfig spi turbostat usb \
+ perf rpmb selftests bootconfig spi turbostat usb \
virtio vm bpf x86_energy_perf_policy \
tmon freefall iio objtool kvm_stat wmi \
pci debugging
@@ -111,7 +112,7 @@ acpi_install:
cpupower_install:
$(call descend,power/$(@:_install=),install)

-cgroup_install firewire_install gpio_install hv_install iio_install perf_install bootconfig_install spi_install usb_install virtio_install vm_install bpf_install objtool_install wmi_install pci_install debugging_install:
+cgroup_install firewire_install gpio_install hv_install iio_install rpmb_install perf_install bootconfig_install spi_install usb_install virtio_install vm_install bpf_install objtool_install wmi_install pci_install debugging_install:
$(call descend,$(@:_install=),install)

liblockdep_install:
@@ -134,7 +135,7 @@ kvm_stat_install:

install: acpi_install cgroup_install cpupower_install gpio_install \
hv_install firewire_install iio_install liblockdep_install \
- perf_install selftests_install turbostat_install usb_install \
+ perf_install rpmb_install selftests_install turbostat_install usb_install \
virtio_install vm_install bpf_install x86_energy_perf_policy_install \
tmon_install freefall_install objtool_install kvm_stat_install \
wmi_install pci_install debugging_install intel-speed-select_install
@@ -164,6 +165,9 @@ perf_clean:
$(Q)mkdir -p $(PERF_O) .
$(Q)$(MAKE) --no-print-directory -C perf O=$(PERF_O) subdir= clean

+rpmb_clean:
+ $(call descend,$(@:_clean=),clean)
+
selftests_clean:
$(call descend,testing/$(@:_clean=),clean)

@@ -180,7 +184,7 @@ build_clean:
$(call descend,build,clean)

clean: acpi_clean cgroup_clean cpupower_clean hv_clean firewire_clean \
- perf_clean selftests_clean turbostat_clean bootconfig_clean spi_clean usb_clean virtio_clean \
+ perf_clean rpmb_clean selftests_clean turbostat_clean bootconfig_clean spi_clean usb_clean virtio_clean \
vm_clean bpf_clean iio_clean x86_energy_perf_policy_clean tmon_clean \
freefall_clean build_clean libbpf_clean libsubcmd_clean liblockdep_clean \
gpio_clean objtool_clean leds_clean wmi_clean pci_clean firmware_clean debugging_clean \
diff --git a/tools/rpmb/.gitignore b/tools/rpmb/.gitignore
new file mode 100644
index 000000000000..218f680548e6
--- /dev/null
+++ b/tools/rpmb/.gitignore
@@ -0,0 +1,2 @@
+*.o
+rpmb
diff --git a/tools/rpmb/Makefile b/tools/rpmb/Makefile
new file mode 100644
index 000000000000..3d49a94ffb66
--- /dev/null
+++ b/tools/rpmb/Makefile
@@ -0,0 +1,41 @@
+# SPDX-License-Identifier: GPL-2.0
+include ../scripts/Makefile.include
+
+CC ?= $(CROSS_COMPILE)gcc
+LD ?= $(CROSS_COMPILE)ld
+PKG_CONFIG = $(CROSS_COMPILE)pkg-config
+
+ifeq ($(srctree),)
+srctree := $(patsubst %/,%,$(dir $(shell pwd)))
+srctree := $(patsubst %/,%,$(dir $(srctree)))
+#$(info Determined 'srctree' to be $(srctree))
+endif
+
+INSTALL = install
+prefix ?= /usr/local
+bindir = $(prefix)/bin
+
+
+CFLAGS += $(HOSTCFLAGS)
+CFLAGS += -D__EXPORTED_HEADERS__
+CFLAGS += -Wall -Wextra -ggdb
+ifdef RPMB_STATIC
+LDFLAGS += -pthread -static -Wl,-u,pthread_mutex_unlock
+CFLAGS += -pthread -static
+PKG_STATIC = --static
+endif
+CFLAGS += -I$(srctree)/include/uapi -I$(srctree)/include
+LDLIBS += $(shell $(PKG_CONFIG) --libs $(PKG_STATIC) libkeyutils)
+
+prog := rpmb
+
+all : $(prog)
+
+$(prog): rpmb.o
+
+clean :
+ $(RM) $(prog) *.o
+
+install: $(prog)
+ $(INSTALL) -m755 -d $(DESTDIR)$(bindir)
+ $(INSTALL) $(prog) $(DESTDIR)$(bindir)
diff --git a/tools/rpmb/rpmb.c b/tools/rpmb/rpmb.c
new file mode 100644
index 000000000000..d5b85af14f94
--- /dev/null
+++ b/tools/rpmb/rpmb.c
@@ -0,0 +1,649 @@
+// SPDX-License-Identifier: BSD-3-Clause
+/*
+ * Copyright (C) 2016-2019 Intel Corp. All rights reserved
+ * Copyright (C) 2021 Linaro Ltd
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <keyutils.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <libgen.h>
+#include <limits.h>
+#include <ctype.h>
+#include <errno.h>
+#include <stdint.h>
+#include <stdbool.h>
+
+#include "linux/rpmb.h"
+
+#define RPMB_KEY_SIZE 32
+#define RPMB_MAC_SIZE 32
+#define RPMB_NONCE_SIZE 16
+
+#define min(a,b) \
+ ({ __typeof__ (a) _a = (a); \
+ __typeof__ (b) _b = (b); \
+ _a < _b ? _a : _b; })
+
+
+static bool verbose;
+#define rpmb_dbg(fmt, ARGS...) do { \
+ if (verbose) \
+ fprintf(stderr, "rpmb: " fmt, ##ARGS); \
+} while (0)
+
+#define rpmb_msg(fmt, ARGS...) \
+ fprintf(stderr, "rpmb: " fmt, ##ARGS)
+
+#define rpmb_err(fmt, ARGS...) \
+ fprintf(stderr, "rpmb: error: " fmt, ##ARGS)
+
+static int open_dev_file(const char *devfile, struct rpmb_ioc_cap_cmd *cap)
+{
+ struct rpmb_ioc_ver_cmd ver;
+ int fd;
+ int ret;
+
+ fd = open(devfile, O_RDWR);
+ if (fd < 0)
+ rpmb_err("Cannot open: %s: %s.\n", devfile, strerror(errno));
+
+ ret = ioctl(fd, RPMB_IOC_VER_CMD, &ver);
+ if (ret < 0) {
+ rpmb_err("ioctl failure %d: %s.\n", ret, strerror(errno));
+ goto err;
+ }
+
+ printf("RPMB API Version %X\n", ver.api_version);
+
+ ret = ioctl(fd, RPMB_IOC_CAP_CMD, cap);
+ if (ret < 0) {
+ rpmb_err("ioctl failure %d: %s.\n", ret, strerror(errno));
+ goto err;
+ }
+
+ rpmb_dbg("RPMB rpmb_target = %hd\n", cap->target);
+ rpmb_dbg("RPMB capacity = %hd\n", cap->capacity);
+ rpmb_dbg("RPMB block_size = %hd\n", cap->block_size);
+ rpmb_dbg("RPMB wr_cnt_max = %hd\n", cap->wr_cnt_max);
+ rpmb_dbg("RPMB rd_cnt_max = %hd\n", cap->rd_cnt_max);
+ rpmb_dbg("RPMB auth_method = %hd\n", cap->auth_method);
+
+ return fd;
+err:
+ close(fd);
+ return -1;
+}
+
+static int open_rd_file(const char *datafile, const char *type)
+{
+ int fd;
+
+ if (!strcmp(datafile, "-"))
+ fd = STDIN_FILENO;
+ else
+ fd = open(datafile, O_RDONLY);
+
+ if (fd < 0)
+ rpmb_err("Cannot open %s: %s: %s.\n",
+ type, datafile, strerror(errno));
+
+ return fd;
+}
+
+static int open_wr_file(const char *datafile, const char *type)
+{
+ int fd;
+
+ if (!strcmp(datafile, "-"))
+ fd = STDOUT_FILENO;
+ else
+ fd = open(datafile, O_WRONLY | O_CREAT | O_APPEND, 0600);
+ if (fd < 0)
+ rpmb_err("Cannot open %s: %s: %s.\n",
+ type, datafile, strerror(errno));
+ return fd;
+}
+
+static void close_fd(int fd)
+{
+ if (fd > 0 && fd != STDIN_FILENO && fd != STDOUT_FILENO)
+ close(fd);
+}
+
+/* need to just cast out 'const' in write(2) */
+typedef ssize_t (*rwfunc_t)(int fd, void *buf, size_t count);
+/* blocking rw wrapper */
+static ssize_t rw(rwfunc_t func, int fd, unsigned char *buf, size_t size)
+{
+ ssize_t ntotal = 0, n;
+ char *_buf = (char *)buf;
+
+ do {
+ n = func(fd, _buf + ntotal, size - ntotal);
+ if (n == -1 && errno != EINTR) {
+ ntotal = -1;
+ break;
+ } else if (n > 0) {
+ ntotal += n;
+ }
+ } while (n != 0 && (size_t)ntotal != size);
+
+ return ntotal;
+}
+
+static ssize_t read_file(int fd, unsigned char *data, size_t size)
+{
+ ssize_t ret;
+
+ ret = rw(read, fd, data, size);
+ if (ret < 0) {
+ rpmb_err("cannot read file: %s\n.", strerror(errno));
+ } else if ((size_t)ret != size) {
+ rpmb_err("read %zd but must be %zu bytes length.\n", ret, size);
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static ssize_t write_file(int fd, unsigned char *data, size_t size)
+{
+ ssize_t ret;
+
+ ret = rw((rwfunc_t)write, fd, data, size);
+ if (ret < 0) {
+ rpmb_err("cannot read file: %s.\n", strerror(errno));
+ } else if ((size_t)ret != size) {
+ rpmb_err("data is %zd but must be %zu bytes length.\n",
+ ret, size);
+ ret = -EINVAL;
+ }
+ return ret;
+}
+
+static int op_get_info(int nargs, char *argv[])
+{
+ int dev_fd;
+ struct rpmb_ioc_cap_cmd cap;
+
+ if (nargs != 1)
+ return -EINVAL;
+
+ memset(&cap, 0, sizeof(cap));
+ dev_fd = open_dev_file(argv[0], &cap);
+ if (dev_fd < 0)
+ return -errno;
+ argv++;
+
+ printf("RPMB rpmb_target = %hd\n", cap.target);
+ printf("RPMB capacity = %hd\n", cap.capacity);
+ printf("RPMB block_size = %hd\n", cap.block_size);
+ printf("RPMB wr_cnt_max = %hd\n", cap.wr_cnt_max);
+ printf("RPMB rd_cnt_max = %hd\n", cap.rd_cnt_max);
+ printf("RPMB auth_method = %hd\n", cap.auth_method);
+
+ close(dev_fd);
+
+ return 0;
+}
+
+static int op_rpmb_add_key(int nargs, char *argv[])
+{
+ int key_fd, ret = -EINVAL;
+ unsigned char key_data[RPMB_KEY_SIZE];
+ key_serial_t key;
+
+ if (nargs != 1)
+ return -EINVAL;
+
+ key_fd = open_rd_file(argv[0], "key file");
+ if (key_fd < 0) {
+ perror("opening key file");
+ return ret;
+ }
+
+ if (read_file(key_fd, key_data, RPMB_KEY_SIZE) != RPMB_KEY_SIZE)
+ {
+ perror("reading key file");
+ return ret;
+ }
+
+ key = add_key("user", "RPMB MAC", key_data, RPMB_KEY_SIZE,
+ KEY_SPEC_SESSION_KEYRING);
+
+ if (key == -1) {
+ perror("add_key");
+ return ret;
+ }
+
+ printf("Key ID is %jx\n", (uintmax_t) key);
+
+ return 0;
+}
+
+static key_serial_t get_key(char *keyid) {
+ key_serial_t key = -1;
+
+ if (keyid) {
+ if (sscanf(keyid, "%jx", (uintmax_t *) &key) != 1)
+ perror("reading keyid");
+ } else {
+ key = request_key("user", "RPMB MAC", NULL,
+ KEY_SPEC_SESSION_KEYRING);
+ }
+ return key;
+}
+
+static int op_rpmb_program_key(int nargs, char *argv[])
+{
+ int ret, fd = -1;
+ key_serial_t key;
+ struct rpmb_ioc_cap_cmd cap;
+
+ ret = -EINVAL;
+ if (nargs < 1 || nargs > 2)
+ return ret;
+
+ fd = open_dev_file(argv[0], &cap);
+ if (fd < 0) {
+ perror("opening RPMB device");
+ return ret;
+ }
+ argv++;
+
+ key = get_key(nargs == 2 ? argv[0] : NULL);
+ if (key == -1)
+ goto out;
+
+ ret = ioctl(fd, RPMB_IOC_PKEY_CMD, &key);
+ if (ret < 0) {
+ rpmb_err("pkey ioctl failure %d: %s.\n", ret, strerror(errno));
+ }
+
+out:
+ close_fd(fd);
+ return ret;
+}
+
+
+static int op_rpmb_get_write_counter(int nargs, char **argv)
+{
+ int ret, fd = -1;
+ unsigned int counter;
+ struct rpmb_ioc_cap_cmd cap;
+
+ ret = -EINVAL;
+ if (nargs != 1)
+ return ret;
+
+ fd = open_dev_file(argv[0], &cap);
+ if (fd < 0) {
+ perror("opening RPMB device");
+ return ret;
+ }
+
+ ret = ioctl(fd, RPMB_IOC_COUNTER_CMD, &counter);
+ if (ret < 0) {
+ rpmb_err("counter ioctl failure %d: %s.\n", ret, strerror(errno));
+ }
+
+ printf("Counter value is: %ud\n", counter);
+
+ close_fd(fd);
+ return ret;
+}
+
+static int op_rpmb_read_blocks(int nargs, char **argv)
+{
+ int ret, data_fd, fd = -1;
+ struct rpmb_ioc_cap_cmd cap;
+ unsigned long numarg;
+ uint16_t addr, blocks_cnt;
+
+ ret = -EINVAL;
+ if (nargs != 4)
+ return ret;
+
+ fd = open_dev_file(argv[0], &cap);
+ if (fd < 0) {
+ perror("opening RPMB device");
+ return ret;
+ }
+ argv++;
+
+ errno = 0;
+ numarg = strtoul(argv[0], NULL, 0);
+ if (errno || numarg > USHRT_MAX) {
+ rpmb_err("wrong block address\n");
+ goto out;
+ }
+ addr = (uint16_t)numarg;
+ argv++;
+
+ errno = 0;
+ numarg = strtoul(argv[0], NULL, 0);
+ if (errno || numarg > USHRT_MAX) {
+ rpmb_err("wrong blocks count\n");
+ goto out;
+ }
+ blocks_cnt = (uint16_t)numarg;
+ argv++;
+
+ if (blocks_cnt == 0) {
+ rpmb_err("wrong blocks count\n");
+ goto out;
+ }
+
+ data_fd = open_wr_file(argv[0], "output data");
+ if (data_fd < 0)
+ goto out;
+ argv++;
+
+ while (blocks_cnt > 0) {
+ int to_copy = min(blocks_cnt, cap.rd_cnt_max);
+ int length = to_copy * 256;
+ void *data = malloc(length);
+ struct rpmb_ioc_blocks_cmd cmd;
+ if (!data) {
+ ret = ENOMEM;
+ goto out;
+ }
+ cmd.addr = addr;
+ cmd.count = to_copy;
+ cmd.data = data;
+
+ ret = ioctl(fd, RPMB_IOC_RBLOCKS_CMD, &cmd);
+ if (ret < 0) {
+ rpmb_err("rblocks ioctl failure %d: %s.\n", ret,
+ strerror(errno));
+ goto out;
+ }
+
+ ret = write_file(data_fd, data, length);
+ if (ret < 0) {
+ perror("writing data");
+ goto out;
+ }
+
+ free(data);
+ addr += to_copy;
+ blocks_cnt -= to_copy;
+ }
+
+ ret = 0;
+out:
+ close_fd(fd);
+ close_fd(data_fd);
+
+ return ret;
+}
+
+static int op_rpmb_write_blocks(int nargs, char **argv)
+{
+ int ret, data_fd, fd = -1;
+ struct rpmb_ioc_cap_cmd cap;
+ unsigned long numarg;
+ uint16_t addr, blocks_cnt;
+ key_serial_t key;
+
+ ret = -EINVAL;
+ if (nargs < 4 || nargs > 5)
+ return ret;
+
+ fd = open_dev_file(argv[0], &cap);
+ if (fd < 0) {
+ perror("opening RPMB device");
+ return ret;
+ }
+ argv++;
+
+ errno = 0;
+ numarg = strtoul(argv[0], NULL, 0);
+ if (errno || numarg > USHRT_MAX) {
+ rpmb_err("wrong block address\n");
+ goto out;
+ }
+ addr = (uint16_t)numarg;
+ argv++;
+
+ errno = 0;
+ numarg = strtoul(argv[0], NULL, 0);
+ if (errno || numarg > USHRT_MAX) {
+ rpmb_err("wrong blocks count\n");
+ goto out;
+ }
+ blocks_cnt = (uint16_t)numarg;
+ argv++;
+
+ if (blocks_cnt == 0) {
+ rpmb_err("wrong blocks count\n");
+ goto out;
+ }
+
+ data_fd = open_wr_file(argv[0], "input data");
+ if (data_fd < 0)
+ goto out;
+ argv++;
+
+ key = get_key(nargs == 5 ? argv[0] : NULL);
+ if (key == -1)
+ goto out;
+
+ while (blocks_cnt > 0) {
+ int to_copy = min(blocks_cnt, cap.wr_cnt_max);
+ int length = to_copy * 256;
+ void *data = malloc(length);
+ struct rpmb_ioc_blocks_cmd cmd;
+ if (!data) {
+ ret = ENOMEM;
+ goto out;
+ }
+ cmd.key =
+ cmd.addr = addr;
+ cmd.count = to_copy;
+ cmd.data = data;
+
+ ret = read_file(data_fd, data, length);
+ if (ret < 0) {
+ perror("reading data");
+ goto out;
+ }
+
+ ret = ioctl(fd, RPMB_IOC_WBLOCKS_CMD, &cmd);
+ if (ret < 0) {
+ rpmb_err("wblocks ioctl failure %d: %s.\n", ret,
+ strerror(errno));
+ goto out;
+ }
+
+ free(data);
+ addr += to_copy;
+ blocks_cnt -= to_copy;
+ }
+
+ ret = 0;
+out:
+ close_fd(fd);
+ close_fd(data_fd);
+
+ return ret;
+}
+
+typedef int (*rpmb_op)(int argc, char *argv[]);
+
+struct rpmb_cmd {
+ const char *op_name;
+ rpmb_op op;
+ const char *usage; /* usage title */
+ const char *help; /* help */
+};
+
+static const struct rpmb_cmd cmds[] = {
+ {
+ "get-info",
+ op_get_info,
+ "<RPMB_DEVICE>",
+ " Get RPMB device info\n",
+ },
+ {
+ "add-key",
+ op_rpmb_add_key,
+ "<KEY_FILE>",
+ " Load a 32 byte KEY_FILE into the session keyring.\n"
+ " Returns a KEYID to use in future transactions.",
+ },
+ {
+ "program-key",
+ op_rpmb_program_key,
+ "<RPMB_DEVICE> <KEYID>",
+ " Program authentication KEYID\n"
+ " NOTE: This is a one-time programmable irreversible change.\n",
+ },
+ {
+ "write-counter",
+ op_rpmb_get_write_counter,
+ "<RPMB_DEVICE>",
+ " Rertrive write counter value from the <RPMB_DEVICE> to stdout.\n"
+ },
+ {
+ "write-blocks",
+ op_rpmb_write_blocks,
+ "<RPMB_DEVICE> <address> <block_count> <DATA_FILE> <KEYID>",
+ " <block count> of 256 bytes will be written from the DATA_FILE\n"
+ " to the <RPMB_DEVICE> at block offset <address>.\n"
+ " When DATA_FILE is -, read from standard input.\n",
+ },
+ {
+ "read-blocks",
+ op_rpmb_read_blocks,
+ "<RPMB_DEVICE> <address> <blocks count> <OUTPUT_FILE>",
+ " <block count> of 256 bytes will be read from <RPMB_DEVICE>\n"
+ " to the OUTPUT_FILE\n"
+ " When OUTPUT_FILE is -, write to standard output\n",
+ },
+
+ { NULL, NULL, NULL, NULL }
+};
+
+static void help(const char *prog, const struct rpmb_cmd *cmd)
+{
+ printf("%s %s %s\n", prog, cmd->op_name, cmd->usage);
+ printf("%s\n", cmd->help);
+}
+
+static void usage(const char *prog)
+{
+ int i;
+
+ printf("\n");
+ printf("Usage: %s [-v] <command> <args>\n\n", prog);
+ for (i = 0; cmds[i].op_name; i++)
+ printf(" %s %s %s\n",
+ prog, cmds[i].op_name, cmds[i].usage);
+
+ printf("\n");
+ printf(" %s -v/--verbose: runs in verbose mode\n", prog);
+ printf(" %s help : shows this help\n", prog);
+ printf(" %s help <command>: shows detailed help\n", prog);
+}
+
+static bool call_for_help(const char *arg)
+{
+ return !strcmp(arg, "help") ||
+ !strcmp(arg, "-h") ||
+ !strcmp(arg, "--help");
+}
+
+static bool parse_verbose(const char *arg)
+{
+ return !strcmp(arg, "-v") ||
+ !strcmp(arg, "--verbose");
+}
+
+static const
+struct rpmb_cmd *parse_args(const char *prog, int *_argc, char **_argv[])
+{
+ int i;
+ int argc = *_argc;
+ char **argv = *_argv;
+ const struct rpmb_cmd *cmd = NULL;
+ bool need_help = false;
+
+ argc--; argv++;
+
+ if (argc == 0)
+ goto out;
+
+ if (call_for_help(argv[0])) {
+ argc--; argv++;
+ if (argc == 0)
+ goto out;
+
+ need_help = true;
+ }
+
+ if (parse_verbose(argv[0])) {
+ argc--; argv++;
+ if (argc == 0)
+ goto out;
+
+ verbose = true;
+ }
+
+ for (i = 0; cmds[i].op_name; i++) {
+ if (!strncmp(argv[0], cmds[i].op_name,
+ strlen(cmds[i].op_name))) {
+ cmd = &cmds[i];
+ argc--; argv++;
+ break;
+ }
+ }
+
+ if (!cmd)
+ goto out;
+
+ if (need_help || (argc > 0 && call_for_help(argv[0]))) {
+ help(prog, cmd);
+ argc--; argv++;
+ return NULL;
+ }
+
+out:
+ *_argc = argc;
+ *_argv = argv;
+
+ if (!cmd)
+ usage(prog);
+
+ return cmd;
+}
+
+int main(int argc, char *argv[])
+{
+ const char *prog = basename(argv[0]);
+ const struct rpmb_cmd *cmd;
+ int ret;
+
+ cmd = parse_args(prog, &argc, &argv);
+ if (!cmd)
+ exit(EXIT_SUCCESS);
+
+ ret = cmd->op(argc, argv);
+ if (ret == -EINVAL)
+ help(prog, cmd);
+
+ if (ret) {
+ exit(EXIT_FAILURE);
+ }
+
+ exit(EXIT_SUCCESS);
+}
--
2.20.1