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 <[email protected]>
Cc: Tomas Winkler <[email protected]>
Cc: Alexander Usyskin <[email protected]>
---
 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 <[email protected]>
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

Reply via email to