| File: | .build-ci/../plugins/exclusion/exclusion-nvme.c |
| Warning: | line 593, column 10 An undefined value may be read from 'errno' |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
| 1 | // SPDX-License-Identifier: GPL-2.0-or-later | |||
| 2 | /* | |||
| 3 | * This file is part of nvme-cli. | |||
| 4 | * Copyright (c) 2026 Dell Technologies Inc. or its subsidiaries. | |||
| 5 | * | |||
| 6 | * Authors: Martin Belanger <martin.belanger@dell.com> | |||
| 7 | */ | |||
| 8 | #include <errno(*__errno_location ()).h> | |||
| 9 | #include <fcntl.h> | |||
| 10 | #include <limits.h> | |||
| 11 | #include <stdint.h> | |||
| 12 | #include <stdio.h> | |||
| 13 | #include <stdlib.h> | |||
| 14 | #include <string.h> | |||
| 15 | #include <sys/stat.h> | |||
| 16 | #include <sys/wait.h> | |||
| 17 | #include <unistd.h> | |||
| 18 | ||||
| 19 | #include <libnvme.h> | |||
| 20 | ||||
| 21 | #include "common.h" | |||
| 22 | #include "nvme.h" | |||
| 23 | #include "nvme-print.h" | |||
| 24 | #include "src/cleanup.h" | |||
| 25 | ||||
| 26 | #define CREATE_CMD | |||
| 27 | #include "exclusion-nvme.h" | |||
| 28 | ||||
| 29 | /* | |||
| 30 | * Mutating exclusion commands write the root-owned exclusion lists under | |||
| 31 | * /etc/nvme. Fail early with a clear message rather than letting the write | |||
| 32 | * fail later with a bare -EACCES. | |||
| 33 | */ | |||
| 34 | static int require_root(void) | |||
| 35 | { | |||
| 36 | if (geteuid() != 0) { | |||
| 37 | nvme_show_error("this command requires root privileges (try sudo)")nvme_show_message(1, "this command requires root privileges (try sudo)" ); | |||
| 38 | return -EPERM1; | |||
| 39 | } | |||
| 40 | return 0; | |||
| 41 | } | |||
| 42 | ||||
| 43 | static int excl_create(int argc, char **argv, struct command *acmd, | |||
| 44 | struct plugin *plugin) | |||
| 45 | { | |||
| 46 | const char *desc = "Create a new NVMeoF exclusion list."; | |||
| 47 | const char *name_help = "exclusion list name (omit for the default list)"; | |||
| 48 | __cleanup_nvme_global_ctx__attribute__((cleanup(cleanup_nvme_global_ctx))) struct libnvme_global_ctx *ctx = NULL((void*)0); | |||
| 49 | int ret; | |||
| 50 | ||||
| 51 | struct config { | |||
| 52 | char *name; | |||
| 53 | } cfg = { 0 }; | |||
| 54 | ||||
| 55 | NVME_ARGS(opts,nvme_args.supported_output_formats = (NORMAL | JSON | BINARY) ; struct argconfig_commandline_options opts[] = { {"", 0, ((void *)0), CFG_GROUP_SEPARATOR, ((void*)0), 0, "Options", 0, ((void *)0)}, {"name", 'N', "NAME", CFG_STRING, &cfg.name, 1, name_help , 0, }, {"", 0, ((void*)0), CFG_GROUP_SEPARATOR, ((void*)0), 0 , "Global options", 0, ((void*)0)}, {"verbose", 'v', "NUM", CFG_INCREMENT , &nvme_args.verbose, 0, "Increase output verbosity", 0, } , {"quiet", 0, ((void*)0), CFG_FLAG, &nvme_args.quiet, 0, "suppress output messages, overwrites verbose mode", 0, }, { "output-format", 'o', "FMT", CFG_STRING, &nvme_args.output_format , 1, "Output format: normal|json|binary", 0, }, {"timeout", 0 , "NUM", CFG_POSITIVE, &nvme_args.timeout, 1, "timeout value, in milliseconds" , 0, }, {"dry-run", 0, ((void*)0), CFG_FLAG, &nvme_args.dry_run , 0, "show command instead of executing", 0, }, {"no-retries" , 0, ((void*)0), CFG_FLAG, &nvme_args.no_retries, 0, "disable retry logic on errors" , 0, }, {"no-ioctl-probing", 0, ((void*)0), CFG_FLAG, &nvme_args .no_ioctl_probing, 0, "disable 64-bit IOCTL support probing", 0, }, {"output-format-version", 0, "NUM", CFG_POSITIVE, & nvme_args.output_format_ver, 1, "output format version: 1|2", 0, }, {"human-readable", 'H', ((void*)0), CFG_FLAG, &nvme_args .verbose, 0, ((void*)0), 0, ((void*)0), 1}, {"set-options", 0 , "KEY=VALUE", CFG_STRING, &nvme_args.set_options, 1, "set a libnvme library option (key=value[,key=value,...]);" , 0, }, { ((void*)0) } } | |||
| 56 | OPT_STRING("name", 'N', "NAME", &cfg.name, name_help))nvme_args.supported_output_formats = (NORMAL | JSON | BINARY) ; struct argconfig_commandline_options opts[] = { {"", 0, ((void *)0), CFG_GROUP_SEPARATOR, ((void*)0), 0, "Options", 0, ((void *)0)}, {"name", 'N', "NAME", CFG_STRING, &cfg.name, 1, name_help , 0, }, {"", 0, ((void*)0), CFG_GROUP_SEPARATOR, ((void*)0), 0 , "Global options", 0, ((void*)0)}, {"verbose", 'v', "NUM", CFG_INCREMENT , &nvme_args.verbose, 0, "Increase output verbosity", 0, } , {"quiet", 0, ((void*)0), CFG_FLAG, &nvme_args.quiet, 0, "suppress output messages, overwrites verbose mode", 0, }, { "output-format", 'o', "FMT", CFG_STRING, &nvme_args.output_format , 1, "Output format: normal|json|binary", 0, }, {"timeout", 0 , "NUM", CFG_POSITIVE, &nvme_args.timeout, 1, "timeout value, in milliseconds" , 0, }, {"dry-run", 0, ((void*)0), CFG_FLAG, &nvme_args.dry_run , 0, "show command instead of executing", 0, }, {"no-retries" , 0, ((void*)0), CFG_FLAG, &nvme_args.no_retries, 0, "disable retry logic on errors" , 0, }, {"no-ioctl-probing", 0, ((void*)0), CFG_FLAG, &nvme_args .no_ioctl_probing, 0, "disable 64-bit IOCTL support probing", 0, }, {"output-format-version", 0, "NUM", CFG_POSITIVE, & nvme_args.output_format_ver, 1, "output format version: 1|2", 0, }, {"human-readable", 'H', ((void*)0), CFG_FLAG, &nvme_args .verbose, 0, ((void*)0), 0, ((void*)0), 1}, {"set-options", 0 , "KEY=VALUE", CFG_STRING, &nvme_args.set_options, 1, "set a libnvme library option (key=value[,key=value,...]);" , 0, }, { ((void*)0) } }; | |||
| 57 | ||||
| 58 | ret = parse_args(argc, argv, desc, opts); | |||
| 59 | if (ret) | |||
| 60 | return ret; | |||
| 61 | ||||
| 62 | ret = require_root(); | |||
| 63 | if (ret) | |||
| 64 | return ret; | |||
| 65 | ||||
| 66 | ret = nvme_create_global_ctx(&ctx); | |||
| 67 | if (ret) | |||
| 68 | return ret; | |||
| 69 | ret = libnvmf_exclusion_create(ctx, cfg.name); | |||
| 70 | if (ret == -EEXIST17) | |||
| 71 | nvme_show_error("exclusion list '%s' already exists", cfg.name)nvme_show_message(1, "exclusion list '%s' already exists", cfg .name); | |||
| 72 | else if (ret) | |||
| 73 | nvme_show_error("create failed: %s", libnvme_strerror(-ret))nvme_show_message(1, "create failed: %s", libnvme_strerror(-ret )); | |||
| 74 | return ret; | |||
| 75 | } | |||
| 76 | ||||
| 77 | static int excl_delete(int argc, char **argv, struct command *acmd, | |||
| 78 | struct plugin *plugin) | |||
| 79 | { | |||
| 80 | const char *desc = "Delete an NVMeoF exclusion list entirely."; | |||
| 81 | const char *name_help = "exclusion list name (omit for the default list)"; | |||
| 82 | __cleanup_nvme_global_ctx__attribute__((cleanup(cleanup_nvme_global_ctx))) struct libnvme_global_ctx *ctx = NULL((void*)0); | |||
| 83 | int ret; | |||
| 84 | ||||
| 85 | struct config { | |||
| 86 | char *name; | |||
| 87 | } cfg = { 0 }; | |||
| 88 | ||||
| 89 | NVME_ARGS(opts,nvme_args.supported_output_formats = (NORMAL | JSON | BINARY) ; struct argconfig_commandline_options opts[] = { {"", 0, ((void *)0), CFG_GROUP_SEPARATOR, ((void*)0), 0, "Options", 0, ((void *)0)}, {"name", 'N', "NAME", CFG_STRING, &cfg.name, 1, name_help , 0, }, {"", 0, ((void*)0), CFG_GROUP_SEPARATOR, ((void*)0), 0 , "Global options", 0, ((void*)0)}, {"verbose", 'v', "NUM", CFG_INCREMENT , &nvme_args.verbose, 0, "Increase output verbosity", 0, } , {"quiet", 0, ((void*)0), CFG_FLAG, &nvme_args.quiet, 0, "suppress output messages, overwrites verbose mode", 0, }, { "output-format", 'o', "FMT", CFG_STRING, &nvme_args.output_format , 1, "Output format: normal|json|binary", 0, }, {"timeout", 0 , "NUM", CFG_POSITIVE, &nvme_args.timeout, 1, "timeout value, in milliseconds" , 0, }, {"dry-run", 0, ((void*)0), CFG_FLAG, &nvme_args.dry_run , 0, "show command instead of executing", 0, }, {"no-retries" , 0, ((void*)0), CFG_FLAG, &nvme_args.no_retries, 0, "disable retry logic on errors" , 0, }, {"no-ioctl-probing", 0, ((void*)0), CFG_FLAG, &nvme_args .no_ioctl_probing, 0, "disable 64-bit IOCTL support probing", 0, }, {"output-format-version", 0, "NUM", CFG_POSITIVE, & nvme_args.output_format_ver, 1, "output format version: 1|2", 0, }, {"human-readable", 'H', ((void*)0), CFG_FLAG, &nvme_args .verbose, 0, ((void*)0), 0, ((void*)0), 1}, {"set-options", 0 , "KEY=VALUE", CFG_STRING, &nvme_args.set_options, 1, "set a libnvme library option (key=value[,key=value,...]);" , 0, }, { ((void*)0) } } | |||
| 90 | OPT_STRING("name", 'N', "NAME", &cfg.name, name_help))nvme_args.supported_output_formats = (NORMAL | JSON | BINARY) ; struct argconfig_commandline_options opts[] = { {"", 0, ((void *)0), CFG_GROUP_SEPARATOR, ((void*)0), 0, "Options", 0, ((void *)0)}, {"name", 'N', "NAME", CFG_STRING, &cfg.name, 1, name_help , 0, }, {"", 0, ((void*)0), CFG_GROUP_SEPARATOR, ((void*)0), 0 , "Global options", 0, ((void*)0)}, {"verbose", 'v', "NUM", CFG_INCREMENT , &nvme_args.verbose, 0, "Increase output verbosity", 0, } , {"quiet", 0, ((void*)0), CFG_FLAG, &nvme_args.quiet, 0, "suppress output messages, overwrites verbose mode", 0, }, { "output-format", 'o', "FMT", CFG_STRING, &nvme_args.output_format , 1, "Output format: normal|json|binary", 0, }, {"timeout", 0 , "NUM", CFG_POSITIVE, &nvme_args.timeout, 1, "timeout value, in milliseconds" , 0, }, {"dry-run", 0, ((void*)0), CFG_FLAG, &nvme_args.dry_run , 0, "show command instead of executing", 0, }, {"no-retries" , 0, ((void*)0), CFG_FLAG, &nvme_args.no_retries, 0, "disable retry logic on errors" , 0, }, {"no-ioctl-probing", 0, ((void*)0), CFG_FLAG, &nvme_args .no_ioctl_probing, 0, "disable 64-bit IOCTL support probing", 0, }, {"output-format-version", 0, "NUM", CFG_POSITIVE, & nvme_args.output_format_ver, 1, "output format version: 1|2", 0, }, {"human-readable", 'H', ((void*)0), CFG_FLAG, &nvme_args .verbose, 0, ((void*)0), 0, ((void*)0), 1}, {"set-options", 0 , "KEY=VALUE", CFG_STRING, &nvme_args.set_options, 1, "set a libnvme library option (key=value[,key=value,...]);" , 0, }, { ((void*)0) } }; | |||
| 91 | ||||
| 92 | ret = parse_args(argc, argv, desc, opts); | |||
| 93 | if (ret) | |||
| 94 | return ret; | |||
| 95 | ||||
| 96 | ret = require_root(); | |||
| 97 | if (ret) | |||
| 98 | return ret; | |||
| 99 | ||||
| 100 | ret = nvme_create_global_ctx(&ctx); | |||
| 101 | if (ret) | |||
| 102 | return ret; | |||
| 103 | ret = libnvmf_exclusion_delete(ctx, cfg.name); | |||
| 104 | if (ret == -ENOENT2) | |||
| 105 | nvme_show_error("exclusion list '%s' not found", cfg.name)nvme_show_message(1, "exclusion list '%s' not found", cfg.name ); | |||
| 106 | else if (ret) | |||
| 107 | nvme_show_error("delete failed: %s", libnvme_strerror(-ret))nvme_show_message(1, "delete failed: %s", libnvme_strerror(-ret )); | |||
| 108 | return ret; | |||
| 109 | } | |||
| 110 | ||||
| 111 | static void print_list_name(const char *name, void *user_data __attribute__((unused))) | |||
| 112 | { | |||
| 113 | printf("%s\n", name); | |||
| 114 | } | |||
| 115 | ||||
| 116 | static void print_entry(const char *entry, void *user_data __attribute__((unused))) | |||
| 117 | { | |||
| 118 | printf(" %s\n", entry); | |||
| 119 | } | |||
| 120 | ||||
| 121 | static int excl_list(int argc, char **argv, struct command *acmd, | |||
| 122 | struct plugin *plugin) | |||
| 123 | { | |||
| 124 | const char *desc = "List exclusion lists, or entries within a named list."; | |||
| 125 | const char *name_help = "exclusion list name (omit to list all lists)"; | |||
| 126 | __cleanup_nvme_global_ctx__attribute__((cleanup(cleanup_nvme_global_ctx))) struct libnvme_global_ctx *ctx = NULL((void*)0); | |||
| 127 | int ret; | |||
| 128 | ||||
| 129 | struct config { | |||
| 130 | char *name; | |||
| 131 | } cfg = { 0 }; | |||
| 132 | ||||
| 133 | NVME_ARGS(opts,nvme_args.supported_output_formats = (NORMAL | JSON | BINARY) ; struct argconfig_commandline_options opts[] = { {"", 0, ((void *)0), CFG_GROUP_SEPARATOR, ((void*)0), 0, "Options", 0, ((void *)0)}, {"name", 'N', "NAME", CFG_STRING, &cfg.name, 1, name_help , 0, }, {"", 0, ((void*)0), CFG_GROUP_SEPARATOR, ((void*)0), 0 , "Global options", 0, ((void*)0)}, {"verbose", 'v', "NUM", CFG_INCREMENT , &nvme_args.verbose, 0, "Increase output verbosity", 0, } , {"quiet", 0, ((void*)0), CFG_FLAG, &nvme_args.quiet, 0, "suppress output messages, overwrites verbose mode", 0, }, { "output-format", 'o', "FMT", CFG_STRING, &nvme_args.output_format , 1, "Output format: normal|json|binary", 0, }, {"timeout", 0 , "NUM", CFG_POSITIVE, &nvme_args.timeout, 1, "timeout value, in milliseconds" , 0, }, {"dry-run", 0, ((void*)0), CFG_FLAG, &nvme_args.dry_run , 0, "show command instead of executing", 0, }, {"no-retries" , 0, ((void*)0), CFG_FLAG, &nvme_args.no_retries, 0, "disable retry logic on errors" , 0, }, {"no-ioctl-probing", 0, ((void*)0), CFG_FLAG, &nvme_args .no_ioctl_probing, 0, "disable 64-bit IOCTL support probing", 0, }, {"output-format-version", 0, "NUM", CFG_POSITIVE, & nvme_args.output_format_ver, 1, "output format version: 1|2", 0, }, {"human-readable", 'H', ((void*)0), CFG_FLAG, &nvme_args .verbose, 0, ((void*)0), 0, ((void*)0), 1}, {"set-options", 0 , "KEY=VALUE", CFG_STRING, &nvme_args.set_options, 1, "set a libnvme library option (key=value[,key=value,...]);" , 0, }, { ((void*)0) } } | |||
| 134 | OPT_STRING("name", 'N', "NAME", &cfg.name, name_help))nvme_args.supported_output_formats = (NORMAL | JSON | BINARY) ; struct argconfig_commandline_options opts[] = { {"", 0, ((void *)0), CFG_GROUP_SEPARATOR, ((void*)0), 0, "Options", 0, ((void *)0)}, {"name", 'N', "NAME", CFG_STRING, &cfg.name, 1, name_help , 0, }, {"", 0, ((void*)0), CFG_GROUP_SEPARATOR, ((void*)0), 0 , "Global options", 0, ((void*)0)}, {"verbose", 'v', "NUM", CFG_INCREMENT , &nvme_args.verbose, 0, "Increase output verbosity", 0, } , {"quiet", 0, ((void*)0), CFG_FLAG, &nvme_args.quiet, 0, "suppress output messages, overwrites verbose mode", 0, }, { "output-format", 'o', "FMT", CFG_STRING, &nvme_args.output_format , 1, "Output format: normal|json|binary", 0, }, {"timeout", 0 , "NUM", CFG_POSITIVE, &nvme_args.timeout, 1, "timeout value, in milliseconds" , 0, }, {"dry-run", 0, ((void*)0), CFG_FLAG, &nvme_args.dry_run , 0, "show command instead of executing", 0, }, {"no-retries" , 0, ((void*)0), CFG_FLAG, &nvme_args.no_retries, 0, "disable retry logic on errors" , 0, }, {"no-ioctl-probing", 0, ((void*)0), CFG_FLAG, &nvme_args .no_ioctl_probing, 0, "disable 64-bit IOCTL support probing", 0, }, {"output-format-version", 0, "NUM", CFG_POSITIVE, & nvme_args.output_format_ver, 1, "output format version: 1|2", 0, }, {"human-readable", 'H', ((void*)0), CFG_FLAG, &nvme_args .verbose, 0, ((void*)0), 0, ((void*)0), 1}, {"set-options", 0 , "KEY=VALUE", CFG_STRING, &nvme_args.set_options, 1, "set a libnvme library option (key=value[,key=value,...]);" , 0, }, { ((void*)0) } }; | |||
| 135 | ||||
| 136 | ret = parse_args(argc, argv, desc, opts); | |||
| 137 | if (ret) | |||
| 138 | return ret; | |||
| 139 | ||||
| 140 | ret = nvme_create_global_ctx(&ctx); | |||
| 141 | if (ret) | |||
| 142 | return ret; | |||
| 143 | ||||
| 144 | if (!cfg.name) { | |||
| 145 | ret = libnvmf_exclusion_list_for_each(ctx, print_list_name, NULL((void*)0)); | |||
| 146 | if (ret) | |||
| 147 | nvme_show_error("list failed: %s", libnvme_strerror(-ret))nvme_show_message(1, "list failed: %s", libnvme_strerror(-ret )); | |||
| 148 | return ret; | |||
| 149 | } | |||
| 150 | ||||
| 151 | ret = libnvmf_exclusion_entry_for_each(ctx, cfg.name, print_entry, NULL((void*)0)); | |||
| 152 | if (ret == -ENOENT2) | |||
| 153 | nvme_show_error("exclusion list '%s' not found", cfg.name)nvme_show_message(1, "exclusion list '%s' not found", cfg.name ); | |||
| 154 | else if (ret) | |||
| 155 | nvme_show_error("list failed: %s", libnvme_strerror(-ret))nvme_show_message(1, "list failed: %s", libnvme_strerror(-ret )); | |||
| 156 | return ret; | |||
| 157 | } | |||
| 158 | ||||
| 159 | static int excl_add(int argc, char **argv, struct command *acmd, | |||
| 160 | struct plugin *plugin) | |||
| 161 | { | |||
| 162 | const char *desc = "Add an entry to an NVMeoF exclusion list."; | |||
| 163 | const char *name_help = "exclusion list name (omit for the default list)"; | |||
| 164 | const char *entry_help = "semicolon-separated key=value entry " | |||
| 165 | "(e.g. \"transport=tcp;traddr=192.168.1.1\")"; | |||
| 166 | __cleanup_nvme_global_ctx__attribute__((cleanup(cleanup_nvme_global_ctx))) struct libnvme_global_ctx *ctx = NULL((void*)0); | |||
| 167 | int ret; | |||
| 168 | ||||
| 169 | struct config { | |||
| 170 | char *name; | |||
| 171 | char *entry; | |||
| 172 | } cfg = { 0 }; | |||
| 173 | ||||
| 174 | NVME_ARGS(opts,nvme_args.supported_output_formats = (NORMAL | JSON | BINARY) ; struct argconfig_commandline_options opts[] = { {"", 0, ((void *)0), CFG_GROUP_SEPARATOR, ((void*)0), 0, "Options", 0, ((void *)0)}, {"name", 'N', "NAME", CFG_STRING, &cfg.name, 1, name_help , 0, }, {"entry", 'e', "ENTRY", CFG_STRING, &cfg.entry, 1 , entry_help, 0, }, {"", 0, ((void*)0), CFG_GROUP_SEPARATOR, ( (void*)0), 0, "Global options", 0, ((void*)0)}, {"verbose", 'v' , "NUM", CFG_INCREMENT, &nvme_args.verbose, 0, "Increase output verbosity" , 0, }, {"quiet", 0, ((void*)0), CFG_FLAG, &nvme_args.quiet , 0, "suppress output messages, overwrites verbose mode", 0, } , {"output-format", 'o', "FMT", CFG_STRING, &nvme_args.output_format , 1, "Output format: normal|json|binary", 0, }, {"timeout", 0 , "NUM", CFG_POSITIVE, &nvme_args.timeout, 1, "timeout value, in milliseconds" , 0, }, {"dry-run", 0, ((void*)0), CFG_FLAG, &nvme_args.dry_run , 0, "show command instead of executing", 0, }, {"no-retries" , 0, ((void*)0), CFG_FLAG, &nvme_args.no_retries, 0, "disable retry logic on errors" , 0, }, {"no-ioctl-probing", 0, ((void*)0), CFG_FLAG, &nvme_args .no_ioctl_probing, 0, "disable 64-bit IOCTL support probing", 0, }, {"output-format-version", 0, "NUM", CFG_POSITIVE, & nvme_args.output_format_ver, 1, "output format version: 1|2", 0, }, {"human-readable", 'H', ((void*)0), CFG_FLAG, &nvme_args .verbose, 0, ((void*)0), 0, ((void*)0), 1}, {"set-options", 0 , "KEY=VALUE", CFG_STRING, &nvme_args.set_options, 1, "set a libnvme library option (key=value[,key=value,...]);" , 0, }, { ((void*)0) } } | |||
| 175 | OPT_STRING("name", 'N', "NAME", &cfg.name, name_help),nvme_args.supported_output_formats = (NORMAL | JSON | BINARY) ; struct argconfig_commandline_options opts[] = { {"", 0, ((void *)0), CFG_GROUP_SEPARATOR, ((void*)0), 0, "Options", 0, ((void *)0)}, {"name", 'N', "NAME", CFG_STRING, &cfg.name, 1, name_help , 0, }, {"entry", 'e', "ENTRY", CFG_STRING, &cfg.entry, 1 , entry_help, 0, }, {"", 0, ((void*)0), CFG_GROUP_SEPARATOR, ( (void*)0), 0, "Global options", 0, ((void*)0)}, {"verbose", 'v' , "NUM", CFG_INCREMENT, &nvme_args.verbose, 0, "Increase output verbosity" , 0, }, {"quiet", 0, ((void*)0), CFG_FLAG, &nvme_args.quiet , 0, "suppress output messages, overwrites verbose mode", 0, } , {"output-format", 'o', "FMT", CFG_STRING, &nvme_args.output_format , 1, "Output format: normal|json|binary", 0, }, {"timeout", 0 , "NUM", CFG_POSITIVE, &nvme_args.timeout, 1, "timeout value, in milliseconds" , 0, }, {"dry-run", 0, ((void*)0), CFG_FLAG, &nvme_args.dry_run , 0, "show command instead of executing", 0, }, {"no-retries" , 0, ((void*)0), CFG_FLAG, &nvme_args.no_retries, 0, "disable retry logic on errors" , 0, }, {"no-ioctl-probing", 0, ((void*)0), CFG_FLAG, &nvme_args .no_ioctl_probing, 0, "disable 64-bit IOCTL support probing", 0, }, {"output-format-version", 0, "NUM", CFG_POSITIVE, & nvme_args.output_format_ver, 1, "output format version: 1|2", 0, }, {"human-readable", 'H', ((void*)0), CFG_FLAG, &nvme_args .verbose, 0, ((void*)0), 0, ((void*)0), 1}, {"set-options", 0 , "KEY=VALUE", CFG_STRING, &nvme_args.set_options, 1, "set a libnvme library option (key=value[,key=value,...]);" , 0, }, { ((void*)0) } } | |||
| 176 | OPT_STRING("entry", 'e', "ENTRY", &cfg.entry, entry_help))nvme_args.supported_output_formats = (NORMAL | JSON | BINARY) ; struct argconfig_commandline_options opts[] = { {"", 0, ((void *)0), CFG_GROUP_SEPARATOR, ((void*)0), 0, "Options", 0, ((void *)0)}, {"name", 'N', "NAME", CFG_STRING, &cfg.name, 1, name_help , 0, }, {"entry", 'e', "ENTRY", CFG_STRING, &cfg.entry, 1 , entry_help, 0, }, {"", 0, ((void*)0), CFG_GROUP_SEPARATOR, ( (void*)0), 0, "Global options", 0, ((void*)0)}, {"verbose", 'v' , "NUM", CFG_INCREMENT, &nvme_args.verbose, 0, "Increase output verbosity" , 0, }, {"quiet", 0, ((void*)0), CFG_FLAG, &nvme_args.quiet , 0, "suppress output messages, overwrites verbose mode", 0, } , {"output-format", 'o', "FMT", CFG_STRING, &nvme_args.output_format , 1, "Output format: normal|json|binary", 0, }, {"timeout", 0 , "NUM", CFG_POSITIVE, &nvme_args.timeout, 1, "timeout value, in milliseconds" , 0, }, {"dry-run", 0, ((void*)0), CFG_FLAG, &nvme_args.dry_run , 0, "show command instead of executing", 0, }, {"no-retries" , 0, ((void*)0), CFG_FLAG, &nvme_args.no_retries, 0, "disable retry logic on errors" , 0, }, {"no-ioctl-probing", 0, ((void*)0), CFG_FLAG, &nvme_args .no_ioctl_probing, 0, "disable 64-bit IOCTL support probing", 0, }, {"output-format-version", 0, "NUM", CFG_POSITIVE, & nvme_args.output_format_ver, 1, "output format version: 1|2", 0, }, {"human-readable", 'H', ((void*)0), CFG_FLAG, &nvme_args .verbose, 0, ((void*)0), 0, ((void*)0), 1}, {"set-options", 0 , "KEY=VALUE", CFG_STRING, &nvme_args.set_options, 1, "set a libnvme library option (key=value[,key=value,...]);" , 0, }, { ((void*)0) } }; | |||
| 177 | ||||
| 178 | ret = parse_args(argc, argv, desc, opts); | |||
| 179 | if (ret) | |||
| 180 | return ret; | |||
| 181 | ||||
| 182 | if (!cfg.entry) { | |||
| 183 | nvme_show_error("--entry required")nvme_show_message(1, "--entry required"); | |||
| 184 | return -EINVAL22; | |||
| 185 | } | |||
| 186 | ||||
| 187 | ret = require_root(); | |||
| 188 | if (ret) | |||
| 189 | return ret; | |||
| 190 | ||||
| 191 | ret = nvme_create_global_ctx(&ctx); | |||
| 192 | if (ret) | |||
| 193 | return ret; | |||
| 194 | if (!libnvmf_exclusion_entry_valid(ctx, cfg.entry)) { | |||
| 195 | nvme_show_error("invalid entry: %s", cfg.entry)nvme_show_message(1, "invalid entry: %s", cfg.entry); | |||
| 196 | return -EINVAL22; | |||
| 197 | } | |||
| 198 | ||||
| 199 | ret = libnvmf_exclusion_add(ctx, cfg.name, cfg.entry); | |||
| 200 | if (ret == -EINVAL22) | |||
| 201 | nvme_show_error("invalid list name: %s", cfg.name)nvme_show_message(1, "invalid list name: %s", cfg.name); | |||
| 202 | else if (ret) | |||
| 203 | nvme_show_error("add failed: %s", libnvme_strerror(-ret))nvme_show_message(1, "add failed: %s", libnvme_strerror(-ret) ); | |||
| 204 | return ret; | |||
| 205 | } | |||
| 206 | ||||
| 207 | struct entry_collection { | |||
| 208 | char **entries; | |||
| 209 | size_t count, cap; | |||
| 210 | }; | |||
| 211 | ||||
| 212 | static void collect_entry(const char *entry, void *user_data) | |||
| 213 | { | |||
| 214 | struct entry_collection *ec = user_data; | |||
| 215 | ||||
| 216 | if (ec->count == ec->cap) { | |||
| 217 | size_t newcap = ec->cap ? ec->cap * 2 : 16; | |||
| 218 | char **newarr = realloc(ec->entries, newcap * sizeof(*newarr)); | |||
| 219 | ||||
| 220 | if (!newarr) | |||
| 221 | return; | |||
| 222 | ec->entries = newarr; | |||
| 223 | ec->cap = newcap; | |||
| 224 | } | |||
| 225 | ec->entries[ec->count] = strdup(entry); | |||
| 226 | if (ec->entries[ec->count]) | |||
| 227 | ec->count++; | |||
| 228 | } | |||
| 229 | ||||
| 230 | static int cmp_entry(const void *a, const void *b) | |||
| 231 | { | |||
| 232 | return strcmp(*(const char * const *)a, *(const char * const *)b); | |||
| 233 | } | |||
| 234 | ||||
| 235 | static int excl_remove(int argc, char **argv, struct command *acmd, | |||
| 236 | struct plugin *plugin) | |||
| 237 | { | |||
| 238 | const char *desc = "Interactively remove an entry from an NVMeoF exclusion list."; | |||
| 239 | const char *name_help = "exclusion list name (omit for the default list)"; | |||
| 240 | __cleanup_nvme_global_ctx__attribute__((cleanup(cleanup_nvme_global_ctx))) struct libnvme_global_ctx *ctx = NULL((void*)0); | |||
| 241 | struct entry_collection ec = { 0 }; | |||
| 242 | char answer[32]; | |||
| 243 | size_t i, choice; | |||
| 244 | int ret; | |||
| 245 | ||||
| 246 | struct config { | |||
| 247 | char *name; | |||
| 248 | } cfg = { 0 }; | |||
| 249 | ||||
| 250 | NVME_ARGS(opts,nvme_args.supported_output_formats = (NORMAL | JSON | BINARY) ; struct argconfig_commandline_options opts[] = { {"", 0, ((void *)0), CFG_GROUP_SEPARATOR, ((void*)0), 0, "Options", 0, ((void *)0)}, {"name", 'N', "NAME", CFG_STRING, &cfg.name, 1, name_help , 0, }, {"", 0, ((void*)0), CFG_GROUP_SEPARATOR, ((void*)0), 0 , "Global options", 0, ((void*)0)}, {"verbose", 'v', "NUM", CFG_INCREMENT , &nvme_args.verbose, 0, "Increase output verbosity", 0, } , {"quiet", 0, ((void*)0), CFG_FLAG, &nvme_args.quiet, 0, "suppress output messages, overwrites verbose mode", 0, }, { "output-format", 'o', "FMT", CFG_STRING, &nvme_args.output_format , 1, "Output format: normal|json|binary", 0, }, {"timeout", 0 , "NUM", CFG_POSITIVE, &nvme_args.timeout, 1, "timeout value, in milliseconds" , 0, }, {"dry-run", 0, ((void*)0), CFG_FLAG, &nvme_args.dry_run , 0, "show command instead of executing", 0, }, {"no-retries" , 0, ((void*)0), CFG_FLAG, &nvme_args.no_retries, 0, "disable retry logic on errors" , 0, }, {"no-ioctl-probing", 0, ((void*)0), CFG_FLAG, &nvme_args .no_ioctl_probing, 0, "disable 64-bit IOCTL support probing", 0, }, {"output-format-version", 0, "NUM", CFG_POSITIVE, & nvme_args.output_format_ver, 1, "output format version: 1|2", 0, }, {"human-readable", 'H', ((void*)0), CFG_FLAG, &nvme_args .verbose, 0, ((void*)0), 0, ((void*)0), 1}, {"set-options", 0 , "KEY=VALUE", CFG_STRING, &nvme_args.set_options, 1, "set a libnvme library option (key=value[,key=value,...]);" , 0, }, { ((void*)0) } } | |||
| 251 | OPT_STRING("name", 'N', "NAME", &cfg.name, name_help))nvme_args.supported_output_formats = (NORMAL | JSON | BINARY) ; struct argconfig_commandline_options opts[] = { {"", 0, ((void *)0), CFG_GROUP_SEPARATOR, ((void*)0), 0, "Options", 0, ((void *)0)}, {"name", 'N', "NAME", CFG_STRING, &cfg.name, 1, name_help , 0, }, {"", 0, ((void*)0), CFG_GROUP_SEPARATOR, ((void*)0), 0 , "Global options", 0, ((void*)0)}, {"verbose", 'v', "NUM", CFG_INCREMENT , &nvme_args.verbose, 0, "Increase output verbosity", 0, } , {"quiet", 0, ((void*)0), CFG_FLAG, &nvme_args.quiet, 0, "suppress output messages, overwrites verbose mode", 0, }, { "output-format", 'o', "FMT", CFG_STRING, &nvme_args.output_format , 1, "Output format: normal|json|binary", 0, }, {"timeout", 0 , "NUM", CFG_POSITIVE, &nvme_args.timeout, 1, "timeout value, in milliseconds" , 0, }, {"dry-run", 0, ((void*)0), CFG_FLAG, &nvme_args.dry_run , 0, "show command instead of executing", 0, }, {"no-retries" , 0, ((void*)0), CFG_FLAG, &nvme_args.no_retries, 0, "disable retry logic on errors" , 0, }, {"no-ioctl-probing", 0, ((void*)0), CFG_FLAG, &nvme_args .no_ioctl_probing, 0, "disable 64-bit IOCTL support probing", 0, }, {"output-format-version", 0, "NUM", CFG_POSITIVE, & nvme_args.output_format_ver, 1, "output format version: 1|2", 0, }, {"human-readable", 'H', ((void*)0), CFG_FLAG, &nvme_args .verbose, 0, ((void*)0), 0, ((void*)0), 1}, {"set-options", 0 , "KEY=VALUE", CFG_STRING, &nvme_args.set_options, 1, "set a libnvme library option (key=value[,key=value,...]);" , 0, }, { ((void*)0) } }; | |||
| 252 | ||||
| 253 | ret = parse_args(argc, argv, desc, opts); | |||
| 254 | if (ret) | |||
| 255 | return ret; | |||
| 256 | ||||
| 257 | ret = require_root(); | |||
| 258 | if (ret) | |||
| 259 | return ret; | |||
| 260 | ||||
| 261 | ret = nvme_create_global_ctx(&ctx); | |||
| 262 | if (ret) | |||
| 263 | return ret; | |||
| 264 | ret = libnvmf_exclusion_entry_for_each(ctx, cfg.name, collect_entry, &ec); | |||
| 265 | if (ret) { | |||
| 266 | if (ret == -ENOENT2) | |||
| 267 | nvme_show_error("exclusion list '%s' not found", cfg.name)nvme_show_message(1, "exclusion list '%s' not found", cfg.name ); | |||
| 268 | else | |||
| 269 | nvme_show_error("list failed: %s", libnvme_strerror(-ret))nvme_show_message(1, "list failed: %s", libnvme_strerror(-ret )); | |||
| 270 | return ret; | |||
| 271 | } | |||
| 272 | ||||
| 273 | if (ec.count == 0) { | |||
| 274 | printf("exclusion list '%s' has no entries\n", cfg.name); | |||
| 275 | free(ec.entries); | |||
| 276 | return 0; | |||
| 277 | } | |||
| 278 | ||||
| 279 | qsort(ec.entries, ec.count, sizeof(*ec.entries), cmp_entry); | |||
| 280 | ||||
| 281 | for (i = 0; i < ec.count; i++) | |||
| 282 | printf("%3zu. %s\n", i + 1, ec.entries[i]); | |||
| 283 | ||||
| 284 | printf("Which entry do you want to delete? [1-%zu, or empty to cancel] ", | |||
| 285 | ec.count); | |||
| 286 | fflush(stdoutstdout); | |||
| 287 | ||||
| 288 | if (!fgets(answer, sizeof(answer), stdinstdin) || | |||
| 289 | sscanf(answer, "%zu", &choice) != 1 || | |||
| 290 | choice < 1 || choice > ec.count) { | |||
| 291 | printf("cancelled\n"); | |||
| 292 | ret = 0; | |||
| 293 | goto out; | |||
| 294 | } | |||
| 295 | ||||
| 296 | ret = libnvmf_exclusion_remove(ctx, cfg.name, ec.entries[choice - 1]); | |||
| 297 | if (ret) | |||
| 298 | nvme_show_error("remove failed: %s", libnvme_strerror(-ret))nvme_show_message(1, "remove failed: %s", libnvme_strerror(-ret )); | |||
| 299 | ||||
| 300 | out: | |||
| 301 | for (i = 0; i < ec.count; i++) | |||
| 302 | free(ec.entries[i]); | |||
| 303 | free(ec.entries); | |||
| 304 | return ret; | |||
| 305 | } | |||
| 306 | ||||
| 307 | static int run_editor(const char *path) | |||
| 308 | { | |||
| 309 | const char *editor = getenv("EDITOR"); | |||
| 310 | pid_t pid; | |||
| 311 | int status; | |||
| 312 | ||||
| 313 | if (!editor || !*editor) | |||
| 314 | editor = "vi"; | |||
| 315 | ||||
| 316 | pid = fork(); | |||
| 317 | if (pid < 0) | |||
| 318 | return -errno(*__errno_location ()); | |||
| 319 | if (pid == 0) { | |||
| 320 | char cmd[PATH_MAX4096 + 64]; | |||
| 321 | ||||
| 322 | /* | |||
| 323 | * Pass the path as $1 and reference it in the command so the | |||
| 324 | * editor actually opens the file, while still allowing $EDITOR | |||
| 325 | * to carry arguments (word-split by the shell). | |||
| 326 | */ | |||
| 327 | snprintf(cmd, sizeof(cmd), "%s \"$1\"", editor); | |||
| 328 | execl("/bin/sh", "sh", "-c", cmd, "sh", path, (char *)NULL((void*)0)); | |||
| 329 | _exit(127); | |||
| 330 | } | |||
| 331 | if (waitpid(pid, &status, 0) < 0) | |||
| 332 | return -errno(*__errno_location ()); | |||
| 333 | return WIFEXITED(status)(((status) & 0x7f) == 0) && WEXITSTATUS(status)(((status) & 0xff00) >> 8) == 0 ? 0 : -EIO5; | |||
| 334 | } | |||
| 335 | ||||
| 336 | /* | |||
| 337 | * Validate a file: every "exclusion = ..." line must be a valid entry and | |||
| 338 | * must sit inside the [exclusions] section. Mirrors libnvme's write-side | |||
| 339 | * validation but reports each offense with its line number. | |||
| 340 | * Returns 0 if the file is valid, -EINVAL and prints errors otherwise. | |||
| 341 | */ | |||
| 342 | static int validate_conf_file(struct libnvme_global_ctx *ctx, const char *path) | |||
| 343 | { | |||
| 344 | FILE *f; | |||
| 345 | char line[4096]; | |||
| 346 | unsigned lineno = 0; | |||
| 347 | bool_Bool in_excl = false0; | |||
| 348 | int errors = 0; | |||
| 349 | ||||
| 350 | f = fopen(path, "r"); | |||
| 351 | if (!f) | |||
| 352 | return -errno(*__errno_location ()); | |||
| 353 | ||||
| 354 | while (fgets(line, sizeof(line), f)) { | |||
| 355 | char *s, *eq, *key, *val; | |||
| 356 | ||||
| 357 | lineno++; | |||
| 358 | s = line; | |||
| 359 | while (*s == ' ' || *s == '\t') | |||
| 360 | s++; | |||
| 361 | if (!*s || *s == '#' || *s == '\n') | |||
| 362 | continue; | |||
| 363 | ||||
| 364 | if (*s == '[') { | |||
| 365 | char *end = strchr(s, ']')_Generic (0 ? (s) : (void *) 1, const void *: (const char *) ( strchr (s, ']')), default: strchr (s, ']')); | |||
| 366 | ||||
| 367 | if (!end) { | |||
| 368 | nvme_show_error(nvme_show_message(1, "line %u: malformed section header\n", lineno ) | |||
| 369 | "line %u: malformed section header\n",nvme_show_message(1, "line %u: malformed section header\n", lineno ) | |||
| 370 | lineno)nvme_show_message(1, "line %u: malformed section header\n", lineno ); | |||
| 371 | errors++; | |||
| 372 | in_excl = false0; | |||
| 373 | continue; | |||
| 374 | } | |||
| 375 | *end = '\0'; | |||
| 376 | s++; | |||
| 377 | while (*s == ' ' || *s == '\t') | |||
| 378 | s++; | |||
| 379 | while (end > s && (end[-1] == ' ' || end[-1] == '\t')) | |||
| 380 | *--end = '\0'; | |||
| 381 | in_excl = !strcmp(s, "exclusions"); | |||
| 382 | continue; | |||
| 383 | } | |||
| 384 | ||||
| 385 | eq = strchr(s, '=')_Generic (0 ? (s) : (void *) 1, const void *: (const char *) ( strchr (s, '=')), default: strchr (s, '=')); | |||
| 386 | if (!eq) | |||
| 387 | continue; | |||
| 388 | *eq = '\0'; | |||
| 389 | key = s; | |||
| 390 | while (*key == ' ' || *key == '\t') | |||
| 391 | key++; | |||
| 392 | char *ke = key + strlen(key); | |||
| 393 | while (ke > key && (ke[-1] == ' ' || ke[-1] == '\t')) | |||
| 394 | ke--; | |||
| 395 | *ke = '\0'; | |||
| 396 | ||||
| 397 | if (strcmp(key, "exclusion")) | |||
| 398 | continue; | |||
| 399 | ||||
| 400 | if (!in_excl) { | |||
| 401 | nvme_show_error(nvme_show_message(1, "line %u: entry outside the [exclusions] section\n" , lineno) | |||
| 402 | "line %u: entry outside the [exclusions] section\n",nvme_show_message(1, "line %u: entry outside the [exclusions] section\n" , lineno) | |||
| 403 | lineno)nvme_show_message(1, "line %u: entry outside the [exclusions] section\n" , lineno); | |||
| 404 | errors++; | |||
| 405 | continue; | |||
| 406 | } | |||
| 407 | ||||
| 408 | val = eq + 1; | |||
| 409 | while (*val == ' ' || *val == '\t') | |||
| 410 | val++; | |||
| 411 | char *ve = val + strlen(val); | |||
| 412 | while (ve > val && (ve[-1] == '\n' || ve[-1] == ' ' || ve[-1] == '\t')) | |||
| 413 | ve--; | |||
| 414 | *ve = '\0'; | |||
| 415 | ||||
| 416 | /* Pure check — no filesystem side effects. */ | |||
| 417 | if (!libnvmf_exclusion_entry_valid(ctx, val)) { | |||
| 418 | nvme_show_error("line %u: invalid entry: %s",nvme_show_message(1, "line %u: invalid entry: %s", lineno, val ) | |||
| 419 | lineno, val)nvme_show_message(1, "line %u: invalid entry: %s", lineno, val ); | |||
| 420 | errors++; | |||
| 421 | } | |||
| 422 | } | |||
| 423 | fclose(f); | |||
| 424 | return errors ? -EINVAL22 : 0; | |||
| 425 | } | |||
| 426 | ||||
| 427 | /* | |||
| 428 | * Read a whole file into a newly allocated, NUL-terminated string (caller | |||
| 429 | * frees). Returns NULL on error (errno set). | |||
| 430 | */ | |||
| 431 | static char *read_file(const char *path) | |||
| 432 | { | |||
| 433 | FILE *f = fopen(path, "r"); | |||
| 434 | long sz; | |||
| 435 | char *buf; | |||
| 436 | size_t n; | |||
| 437 | ||||
| 438 | if (!f
| |||
| 439 | return NULL((void*)0); | |||
| 440 | if (fseek(f, 0, SEEK_END2) < 0) { | |||
| 441 | fclose(f); | |||
| 442 | return NULL((void*)0); | |||
| 443 | } | |||
| 444 | sz = ftell(f); | |||
| 445 | if (sz
| |||
| 446 | fclose(f); | |||
| 447 | return NULL((void*)0); | |||
| 448 | } | |||
| 449 | rewind(f); | |||
| 450 | ||||
| 451 | buf = malloc(sz + 1); | |||
| 452 | if (!buf) { | |||
| 453 | fclose(f); | |||
| 454 | return NULL((void*)0); | |||
| 455 | } | |||
| 456 | n = fread(buf, 1, sz, f); | |||
| 457 | if (ferror(f)) { | |||
| 458 | free(buf); | |||
| 459 | fclose(f); | |||
| 460 | return NULL((void*)0); | |||
| 461 | } | |||
| 462 | buf[n] = '\0'; | |||
| 463 | fclose(f); | |||
| 464 | return buf; | |||
| 465 | } | |||
| 466 | ||||
| 467 | /* | |||
| 468 | * excl_edit() - the "nvme exclusion edit" command. | |||
| 469 | * | |||
| 470 | * Opens an exclusion list in the user's $EDITOR for hand-editing, in the same | |||
| 471 | * spirit as "visudo" or "crontab -e". It is a read-modify-write cycle with | |||
| 472 | * optimistic concurrency: | |||
| 473 | * | |||
| 474 | * 1. Read the current list through libnvme (libnvmf_exclusion_read), which | |||
| 475 | * also returns an opaque @version token snapshotting the on-disk content. | |||
| 476 | * 2. Copy the text to a private scratch file under $TMPDIR (0600). The | |||
| 477 | * editor needs a real path, but we never let it touch the live file in | |||
| 478 | * the exclusion directory. | |||
| 479 | * 3. run_editor() forks $EDITOR on the scratch file and waits for it to exit. | |||
| 480 | * 4. Validate the result; if it has bad entries, offer to re-open the editor | |||
| 481 | * (the re_edit loop) so the user can fix them without losing work. | |||
| 482 | * 5. Write the edited text back by list name (libnvmf_exclusion_write), | |||
| 483 | * passing @version. If the list changed on disk in the meantime the | |||
| 484 | * write fails with -ESTALE and nothing is clobbered. | |||
| 485 | * | |||
| 486 | * Whenever the changes are not saved (validation given up on, or -ESTALE), the | |||
| 487 | * scratch file is deliberately left in place and its path printed, so the | |||
| 488 | * user's edits are recoverable. Requires root. | |||
| 489 | */ | |||
| 490 | static int excl_edit(int argc, char **argv, struct command *acmd, | |||
| 491 | struct plugin *plugin) | |||
| 492 | { | |||
| 493 | const char *desc = "Interactively edit an NVMeoF exclusion list."; | |||
| 494 | const char *name_help = "exclusion list name (omit for the default list)"; | |||
| 495 | __cleanup_nvme_global_ctx__attribute__((cleanup(cleanup_nvme_global_ctx))) struct libnvme_global_ctx *ctx = NULL((void*)0); | |||
| 496 | __cleanup_free__attribute__((cleanup(shr_freep))) char *text = NULL((void*)0); | |||
| 497 | const char *tmpdir; | |||
| 498 | char tmp_path[PATH_MAX4096]; | |||
| 499 | uint64_t version; | |||
| 500 | int fd, ret; | |||
| 501 | ||||
| 502 | struct config { | |||
| 503 | char *name; | |||
| 504 | } cfg = { 0 }; | |||
| 505 | ||||
| 506 | NVME_ARGS(opts,nvme_args.supported_output_formats = (NORMAL | JSON | BINARY) ; struct argconfig_commandline_options opts[] = { {"", 0, ((void *)0), CFG_GROUP_SEPARATOR, ((void*)0), 0, "Options", 0, ((void *)0)}, {"name", 'N', "NAME", CFG_STRING, &cfg.name, 1, name_help , 0, }, {"", 0, ((void*)0), CFG_GROUP_SEPARATOR, ((void*)0), 0 , "Global options", 0, ((void*)0)}, {"verbose", 'v', "NUM", CFG_INCREMENT , &nvme_args.verbose, 0, "Increase output verbosity", 0, } , {"quiet", 0, ((void*)0), CFG_FLAG, &nvme_args.quiet, 0, "suppress output messages, overwrites verbose mode", 0, }, { "output-format", 'o', "FMT", CFG_STRING, &nvme_args.output_format , 1, "Output format: normal|json|binary", 0, }, {"timeout", 0 , "NUM", CFG_POSITIVE, &nvme_args.timeout, 1, "timeout value, in milliseconds" , 0, }, {"dry-run", 0, ((void*)0), CFG_FLAG, &nvme_args.dry_run , 0, "show command instead of executing", 0, }, {"no-retries" , 0, ((void*)0), CFG_FLAG, &nvme_args.no_retries, 0, "disable retry logic on errors" , 0, }, {"no-ioctl-probing", 0, ((void*)0), CFG_FLAG, &nvme_args .no_ioctl_probing, 0, "disable 64-bit IOCTL support probing", 0, }, {"output-format-version", 0, "NUM", CFG_POSITIVE, & nvme_args.output_format_ver, 1, "output format version: 1|2", 0, }, {"human-readable", 'H', ((void*)0), CFG_FLAG, &nvme_args .verbose, 0, ((void*)0), 0, ((void*)0), 1}, {"set-options", 0 , "KEY=VALUE", CFG_STRING, &nvme_args.set_options, 1, "set a libnvme library option (key=value[,key=value,...]);" , 0, }, { ((void*)0) } } | |||
| 507 | OPT_STRING("name", 'N', "NAME", &cfg.name, name_help))nvme_args.supported_output_formats = (NORMAL | JSON | BINARY) ; struct argconfig_commandline_options opts[] = { {"", 0, ((void *)0), CFG_GROUP_SEPARATOR, ((void*)0), 0, "Options", 0, ((void *)0)}, {"name", 'N', "NAME", CFG_STRING, &cfg.name, 1, name_help , 0, }, {"", 0, ((void*)0), CFG_GROUP_SEPARATOR, ((void*)0), 0 , "Global options", 0, ((void*)0)}, {"verbose", 'v', "NUM", CFG_INCREMENT , &nvme_args.verbose, 0, "Increase output verbosity", 0, } , {"quiet", 0, ((void*)0), CFG_FLAG, &nvme_args.quiet, 0, "suppress output messages, overwrites verbose mode", 0, }, { "output-format", 'o', "FMT", CFG_STRING, &nvme_args.output_format , 1, "Output format: normal|json|binary", 0, }, {"timeout", 0 , "NUM", CFG_POSITIVE, &nvme_args.timeout, 1, "timeout value, in milliseconds" , 0, }, {"dry-run", 0, ((void*)0), CFG_FLAG, &nvme_args.dry_run , 0, "show command instead of executing", 0, }, {"no-retries" , 0, ((void*)0), CFG_FLAG, &nvme_args.no_retries, 0, "disable retry logic on errors" , 0, }, {"no-ioctl-probing", 0, ((void*)0), CFG_FLAG, &nvme_args .no_ioctl_probing, 0, "disable 64-bit IOCTL support probing", 0, }, {"output-format-version", 0, "NUM", CFG_POSITIVE, & nvme_args.output_format_ver, 1, "output format version: 1|2", 0, }, {"human-readable", 'H', ((void*)0), CFG_FLAG, &nvme_args .verbose, 0, ((void*)0), 0, ((void*)0), 1}, {"set-options", 0 , "KEY=VALUE", CFG_STRING, &nvme_args.set_options, 1, "set a libnvme library option (key=value[,key=value,...]);" , 0, }, { ((void*)0) } }; | |||
| 508 | ||||
| 509 | ret = parse_args(argc, argv, desc, opts); | |||
| 510 | if (ret) | |||
| ||||
| 511 | return ret; | |||
| 512 | ||||
| 513 | ret = require_root(); | |||
| 514 | if (ret
| |||
| 515 | return ret; | |||
| 516 | ||||
| 517 | ret = nvme_create_global_ctx(&ctx); | |||
| 518 | if (ret) | |||
| 519 | return ret; | |||
| 520 | ||||
| 521 | /* | |||
| 522 | * Read the current list through libnvme: the plugin never needs to know | |||
| 523 | * where exclusion lists live. A missing list reads as empty so it can | |||
| 524 | * be created here. @version is an opaque token used at save time to | |||
| 525 | * detect a concurrent editor. | |||
| 526 | */ | |||
| 527 | ret = libnvmf_exclusion_read(ctx, cfg.name, &text, &version); | |||
| 528 | if (ret) { | |||
| 529 | nvme_show_error("cannot read '%s': %s",nvme_show_message(1, "cannot read '%s': %s", cfg.name, libnvme_strerror (-ret)) | |||
| 530 | cfg.name, libnvme_strerror(-ret))nvme_show_message(1, "cannot read '%s': %s", cfg.name, libnvme_strerror (-ret)); | |||
| 531 | return ret; | |||
| 532 | } | |||
| 533 | ||||
| 534 | /* | |||
| 535 | * Edit a private scratch copy under $TMPDIR -- NOT in the exclusion dir. | |||
| 536 | * The editor needs a real path, but the result is installed by name via | |||
| 537 | * libnvmf_exclusion_write(). mkstemp() creates the copy 0600. | |||
| 538 | */ | |||
| 539 | tmpdir = getenv("TMPDIR"); | |||
| 540 | if (!tmpdir
| |||
| 541 | tmpdir = "/tmp"; | |||
| 542 | snprintf(tmp_path, sizeof(tmp_path), "%s/nvme-excl-%s.XXXXXX", | |||
| 543 | tmpdir, cfg.name); | |||
| 544 | ||||
| 545 | fd = mkstemp(tmp_path); | |||
| 546 | if (fd
| |||
| 547 | ret = -errno(*__errno_location ()); | |||
| 548 | nvme_show_error("cannot create temp file: %s",nvme_show_message(1, "cannot create temp file: %s", libnvme_strerror (-ret)) | |||
| 549 | libnvme_strerror(-ret))nvme_show_message(1, "cannot create temp file: %s", libnvme_strerror (-ret)); | |||
| 550 | return ret; | |||
| 551 | } | |||
| 552 | { | |||
| 553 | FILE *dst = fdopen(fd, "w"); | |||
| 554 | ||||
| 555 | if (!dst
| |||
| 556 | ret = -errno(*__errno_location ()); | |||
| 557 | close(fd); | |||
| 558 | unlink(tmp_path); | |||
| 559 | return ret; | |||
| 560 | } | |||
| 561 | fputs(text, dst); | |||
| 562 | fclose(dst); | |||
| 563 | } | |||
| 564 | ||||
| 565 | re_edit: | |||
| 566 | ret = run_editor(tmp_path); | |||
| 567 | if (ret) { | |||
| 568 | nvme_show_error("editor failed")nvme_show_message(1, "editor failed"); | |||
| 569 | unlink(tmp_path); | |||
| 570 | return ret; | |||
| 571 | } | |||
| 572 | ||||
| 573 | ret = validate_conf_file(ctx, tmp_path); | |||
| 574 | if (ret) { | |||
| 575 | char ans[8]; | |||
| 576 | ||||
| 577 | nvme_show_error("File has errors. Re-edit? [y/N] ")nvme_show_message(1, "File has errors. Re-edit? [y/N] "); | |||
| 578 | fflush(stderrstderr); | |||
| 579 | if (fgets(ans, sizeof(ans), stdinstdin) && | |||
| 580 | (ans[0] == 'y' || ans[0] == 'Y')) | |||
| 581 | goto re_edit; | |||
| 582 | ||||
| 583 | nvme_show_error(nvme_show_message(1, "Discarding changes -- your edits are kept at %s\n" , tmp_path) | |||
| 584 | "Discarding changes -- your edits are kept at %s\n",nvme_show_message(1, "Discarding changes -- your edits are kept at %s\n" , tmp_path) | |||
| 585 | tmp_path)nvme_show_message(1, "Discarding changes -- your edits are kept at %s\n" , tmp_path); | |||
| 586 | return -EINVAL22; | |||
| 587 | } | |||
| 588 | ||||
| 589 | { | |||
| 590 | __cleanup_free__attribute__((cleanup(shr_freep))) char *edited = read_file(tmp_path); | |||
| 591 | ||||
| 592 | if (!edited
| |||
| 593 | ret = errno(*__errno_location ()) ? -errno(*__errno_location ()) : -EIO5; | |||
| ||||
| 594 | nvme_show_error("cannot read back temp file: %s",nvme_show_message(1, "cannot read back temp file: %s", libnvme_strerror (-ret)) | |||
| 595 | libnvme_strerror(-ret))nvme_show_message(1, "cannot read back temp file: %s", libnvme_strerror (-ret)); | |||
| 596 | unlink(tmp_path); | |||
| 597 | return ret; | |||
| 598 | } | |||
| 599 | ret = libnvmf_exclusion_write(ctx, cfg.name, edited, version); | |||
| 600 | } | |||
| 601 | ||||
| 602 | if (ret == -ESTALE116) { | |||
| 603 | nvme_show_error(nvme_show_message(1, "the list changed on disk since you opened it; your " "edits were NOT saved -- kept at %s\n", tmp_path) | |||
| 604 | "the list changed on disk since you opened it; your "nvme_show_message(1, "the list changed on disk since you opened it; your " "edits were NOT saved -- kept at %s\n", tmp_path) | |||
| 605 | "edits were NOT saved -- kept at %s\n", tmp_path)nvme_show_message(1, "the list changed on disk since you opened it; your " "edits were NOT saved -- kept at %s\n", tmp_path); | |||
| 606 | return ret; | |||
| 607 | } | |||
| 608 | if (ret) { | |||
| 609 | nvme_show_error("cannot save: %s -- your edits are kept at %s",nvme_show_message(1, "cannot save: %s -- your edits are kept at %s" , libnvme_strerror(-ret), tmp_path) | |||
| 610 | libnvme_strerror(-ret), tmp_path)nvme_show_message(1, "cannot save: %s -- your edits are kept at %s" , libnvme_strerror(-ret), tmp_path); | |||
| 611 | return ret; | |||
| 612 | } | |||
| 613 | ||||
| 614 | unlink(tmp_path); | |||
| 615 | return 0; | |||
| 616 | } |