]> git.ipfire.org Git - thirdparty/snapper.git/commitdiff
- improved error reporting in getopt wrapper (part of bsc#1150156)
authorArvin Schnell <aschnell@suse.de>
Fri, 28 Aug 2020 09:13:09 +0000 (11:13 +0200)
committerArvin Schnell <aschnell@suse.de>
Fri, 28 Aug 2020 09:13:09 +0000 (11:13 +0200)
19 files changed:
client/Command/GetConfig.cc
client/Command/GetConfig/Options.cc
client/Command/ListConfigs.cc
client/Command/ListConfigs/Options.cc
client/Command/ListSnapshots.cc
client/Command/ListSnapshots/Options.cc
client/GlobalOptions.cc
client/Options.h
client/installation-helper.cc
client/misc.cc
client/misc.h
client/mksubvolume.cc
client/snapper.cc
client/systemd-helper.cc
client/utils/GetOpts.cc
client/utils/GetOpts.h
package/snapper.changes
testsuite/Makefile.am
testsuite/getopts.cc [new file with mode: 0644]

index 1c7434986e96bc52ad3e4dec3b9899e946238526..aecd211b63383291d391ee2a6c4171b126e42a55 100644 (file)
@@ -64,7 +64,7 @@ namespace snapper
 
        void Command::GetConfig::run()
        {
-           if (_options_parser.hasArgs())
+           if (_options_parser.has_args())
            {
                cerr << _("Command 'get-config' does not take arguments.") << endl;
                exit(EXIT_FAILURE);
index 7294d43c5e7470e977b87fea8c897bc21f7efda7..fae5d74c515e88847008d266afe75da200b3c70f 100644 (file)
@@ -32,9 +32,8 @@ namespace snapper
        namespace
        {
 
-           const option OPTIONS[] = {
-               { "columns",    required_argument,      0,      0},
-               { 0, 0, 0, 0 }
+           const vector<Option> OPTIONS = {
+               Option("columns",       required_argument)
            };
 
        }
index 80830ead7dcb8c25575d4a30c1f89bc9494a4944..35a8b7517f94ae1d5c1ca4ad5e918f76e31ce3ce 100644 (file)
@@ -64,7 +64,7 @@ namespace snapper
 
        void Command::ListConfigs::run()
        {
-           if (_options_parser.hasArgs())
+           if (_options_parser.has_args())
            {
                cerr << _("Command 'list-configs' does not take arguments.") << endl;
                exit(EXIT_FAILURE);
index 5d2c5fdcf10a5ac3f70a92e3f098b5789309553f..5d76527c3d6f43cb1ef5b49201436de09304c2ef 100644 (file)
@@ -34,9 +34,8 @@ namespace snapper
        namespace
        {
 
-           const option OPTIONS[] = {
-               { "columns",    required_argument,      0,      0},
-               { 0, 0, 0, 0 }
+           const vector<Option> OPTIONS = {
+               Option("columns",       required_argument)
            };
 
        }
index 58d11988879ea59110ffe65569f5aa0c8987ff3a..8acf3750d098e4d960345cda953b5d66bd36efa6 100644 (file)
@@ -64,7 +64,7 @@ namespace snapper
 
        void Command::ListSnapshots::run()
        {
-           if (_options_parser.hasArgs())
+           if (_options_parser.has_args())
            {
                cerr << _("Command 'list' does not take arguments.") << endl;
                exit(EXIT_FAILURE);
index ab1ea5388e5f071df146d9eb78fc68be8e003069..f9b4e69b9b597eb997d1c814c7b36feb58e200f3 100644 (file)
@@ -35,12 +35,11 @@ namespace snapper
 
        namespace
        {
-           const option OPTIONS[] = {
-               { "type",               required_argument,      0,      't' },
-               { "disable-used-space", no_argument,            0,      0 },
-               { "all-configs",        no_argument,            0,      'a' },
-               { "columns",            required_argument,      0,      0},
-               { 0, 0, 0, 0 }
+           const vector<Option> OPTIONS = {
+               Option("type",                  required_argument,      't'),
+               Option("disable-used-space",    no_argument),
+               Option("all-configs",           no_argument,            'a'),
+               Option("columns",               required_argument)
            };
        }
 
index 5265f0e7504086fd3a97b6dfe911610b583d0765..e53b55f83cc065f4430b72d40786c2e24424a51c 100644 (file)
@@ -40,23 +40,22 @@ namespace snapper
 
            const string DEFAULT_ROOT = "/";
 
-           const option OPTIONS[] = {
-               { "quiet",              no_argument,            0,      'q' },
-               { "verbose",            no_argument,            0,      'v' },
-               { "utc",                no_argument,            0,      0 },
-               { "iso",                no_argument,            0,      0 },
-               { "table-style",        required_argument,      0,      't' },
-               { "machine-readable",   required_argument,      0,      0 },
-               { "csvout",             no_argument,            0,      0 },
-               { "jsonout",            no_argument,            0,      0 },
-               { "separator",          required_argument,      0,      0 },
-               { "config",             required_argument,      0,      'c' },
-               { "no-dbus",            no_argument,            0,      0 },
-               { "root",               required_argument,      0,      'r' },
-               { "ambit",              required_argument,      0,      'a' },
-               { "version",            no_argument,            0,      0 },
-               { "help",               no_argument,            0,      'h' },
-               { 0, 0, 0, 0 }
+           const vector<Option> OPTIONS = {
+               Option("quiet",                 no_argument,            'q'),
+               Option("verbose",               no_argument,            'v'),
+               Option("utc",                   no_argument),
+               Option("iso",                   no_argument),
+               Option("table-style",           required_argument,      't'),
+               Option("machine-readable",      required_argument),
+               Option("csvout",                no_argument),
+               Option("jsonout",               no_argument),
+               Option("separator",             required_argument),
+               Option("config",                required_argument,      'c'),
+               Option("no-dbus",               no_argument),
+               Option("root",                  required_argument,      'r'),
+               Option("ambit",                 required_argument,      'a'),
+               Option("version",               no_argument),
+               Option("help",                  no_argument,            'h')
            };
 
        }
@@ -115,8 +114,7 @@ namespace snapper
        {
            if (has_option("root") && !has_option("no-dbus"))
            {
-               string error =_("root argument can be used only together with no-dbus.\n"
-                               "Try 'snapper --help' for more information.");
+               string error =_("root argument can be used only together with no-dbus.");
 
                SN_THROW(OptionsException(error));
            }
index 7386d6e194c165d570c0952b987b93fcf0a364dd..1d42b9f07c3f94e56f084ad378b90bd3626e2c09 100644 (file)
@@ -35,12 +35,6 @@ namespace snapper
     using std::vector;
 
 
-    struct OptionsException : public Exception
-    {
-        explicit OptionsException(const string& msg) : Exception(msg) {}
-    };
-
-
     namespace cli
     {
 
@@ -61,7 +55,7 @@ namespace snapper
 
            GetOpts& _parser;
 
-           GetOpts::parsed_opts _options;
+           ParsedOpts _options;
 
        };
 
index da22de24c1b809eff3770789bde911923fcc2015..735755c04a171d6a458cb827a6b631796135ea15 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * Copyright (c) 2015 Novell, Inc.
- * Copyright (c) 2018 SUSE LLC
+ * Copyright (c) [2018-2020] SUSE LLC
  *
  * All Rights Reserved.
  *
@@ -278,19 +278,6 @@ main(int argc, char** argv)
     setLogDo(&log_do);
     setLogQuery(&log_query);
 
-    const struct option options[] = {
-       { "step",                       required_argument,      0,      0 },
-       { "device",                     required_argument,      0,      0 },
-       { "root-prefix",                required_argument,      0,      0 },
-       { "default-subvolume-name",     required_argument,      0,      0 },
-       { "snapshot-type",              required_argument,      0,      0 },
-       { "pre-num",                    required_argument,      0,      0 },
-       { "description",                required_argument,      0,      0 },
-       { "cleanup",                    required_argument,      0,      0 },
-       { "userdata",                   required_argument,      0,      0 },
-       { 0, 0, 0, 0 }
-    };
-
     string step;
     string device;
     string root_prefix = "/";
@@ -301,40 +288,59 @@ main(int argc, char** argv)
     string cleanup;
     map<string, string> userdata;
 
-    GetOpts getopts;
+    try
+    {
+       const vector<Option> options = {
+           Option("step",                      required_argument),
+           Option("device",                    required_argument),
+           Option("root-prefix",               required_argument),
+           Option("default-subvolume-name",    required_argument),
+           Option("snapshot-type",             required_argument),
+           Option("pre-num",                   required_argument),
+           Option("description",               required_argument),
+           Option("cleanup",                   required_argument),
+           Option("userdata",                  required_argument)
+       };
 
-    getopts.init(argc, argv);
+       GetOpts get_opts(argc, argv);
 
-    GetOpts::parsed_opts opts = getopts.parse(options);
+       ParsedOpts opts = get_opts.parse(options);
 
-    GetOpts::parsed_opts::const_iterator opt;
+       ParsedOpts::const_iterator opt;
 
-    if ((opt = opts.find("step")) != opts.end())
-       step = opt->second;
+       if ((opt = opts.find("step")) != opts.end())
+           step = opt->second;
 
-    if ((opt = opts.find("device")) != opts.end())
-       device = opt->second;
+       if ((opt = opts.find("device")) != opts.end())
+           device = opt->second;
 
-    if ((opt = opts.find("root-prefix")) != opts.end())
-       root_prefix = opt->second;
+       if ((opt = opts.find("root-prefix")) != opts.end())
+           root_prefix = opt->second;
 
-    if ((opt = opts.find("default-subvolume-name")) != opts.end())
-       default_subvolume_name = opt->second;
+       if ((opt = opts.find("default-subvolume-name")) != opts.end())
+           default_subvolume_name = opt->second;
 
-    if ((opt = opts.find("snapshot-type")) != opts.end())
-       snapshot_type = opt->second;
+       if ((opt = opts.find("snapshot-type")) != opts.end())
+           snapshot_type = opt->second;
 
-    if ((opt = opts.find("pre-num")) != opts.end())
-       pre_num = read_num(opt->second);
+       if ((opt = opts.find("pre-num")) != opts.end())
+           pre_num = read_num(opt->second);
 
-    if ((opt = opts.find("description")) != opts.end())
-       description = opt->second;
+       if ((opt = opts.find("description")) != opts.end())
+           description = opt->second;
 
-    if ((opt = opts.find("cleanup")) != opts.end())
-       cleanup = opt->second;
+       if ((opt = opts.find("cleanup")) != opts.end())
+           cleanup = opt->second;
 
-    if ((opt = opts.find("userdata")) != opts.end())
-       userdata = read_userdata(opt->second);
+       if ((opt = opts.find("userdata")) != opts.end())
+           userdata = read_userdata(opt->second);
+    }
+    catch (const OptionsException& e)
+    {
+       SN_CAUGHT(e);
+       cerr << e.what() << endl;
+       return EXIT_FAILURE;
+    }
 
     if (step == "1")
        step1(device, description, cleanup, userdata);
index 843479f2e330f73f386992db32062a44a3819440..638e6cbf3c0c85e4bc1e031fa98cdcb157aecf94 100644 (file)
@@ -115,9 +115,9 @@ show_userdata(const map<string, string>& userdata)
 
 
 map<string, string>
-read_configdata(const list<string>& l, const map<string, string>& old)
+read_configdata(const vector<string>& v, const map<string, string>& old)
 {
-    if (l.empty())
+    if (v.empty())
     {
        cerr << _("Empty configdata.") << endl;
        exit(EXIT_FAILURE);
@@ -125,7 +125,7 @@ read_configdata(const list<string>& l, const map<string, string>& old)
 
     map<string, string> configdata = old;
 
-    for (list<string>::const_iterator it = l.begin(); it != l.end(); ++it)
+    for (vector<string>::const_iterator it = v.begin(); it != v.end(); ++it)
     {
        string::size_type pos = it->find("=");
        if (pos == string::npos)
index 0e2af7e444c268b57670dbc8244af67f45effceb..6ec0ab01534a5000020284fe396896a0573a1634 100644 (file)
@@ -39,7 +39,7 @@ string
 show_userdata(const map<string, string>& userdata);
 
 map<string, string>
-read_configdata(const list<string>& l, const map<string, string>& old = map<string, string>());
+read_configdata(const vector<string>& v, const map<string, string>& old = map<string, string>());
 
 string
 username(uid_t uid);
index 7807c43b07d6ba6a1c84329b04427bacdad56b05..7b70643504ee3ec2180b125a3cd0c68484a196a3 100644 (file)
@@ -531,30 +531,34 @@ main(int argc, char** argv)
 {
     setlocale(LC_ALL, "");
 
-    const struct option options[] = {
-       { "nocow",              no_argument,            0,      0 },
-       { "verbose",            no_argument,            0,      'v' },
-       { 0, 0, 0, 0 }
-    };
-
-    GetOpts getopts;
-
-    getopts.init(argc, argv);
+    try
+    {
+       const vector<Option> options = {
+           Option("nocow",             no_argument),
+           Option("verbose",           no_argument,    'v'),
+       };
 
-    GetOpts::parsed_opts opts = getopts.parse(options);
+       GetOpts get_opts(argc, argv);
 
-    GetOpts::parsed_opts::const_iterator opt;
+       ParsedOpts opts = get_opts.parse(options);
 
-    if ((opt = opts.find("nocow")) != opts.end())
-        set_nocow = true;
+       if (opts.has_option("nocow"))
+           set_nocow = true;
 
-    if ((opt = opts.find("verbose")) != opts.end())
-        verbose = true;
+       if (opts.has_option("verbose"))
+           verbose = true;
 
-    if (getopts.numArgs() != 1)
-       usage();
+       if (get_opts.num_args() != 1)
+           usage();
 
-    target = getopts.popArg();
+       target = get_opts.pop_arg();
+    }
+    catch (const OptionsException& e)
+    {
+        SN_CAUGHT(e);
+        cerr << e.what() << endl;
+        usage();
+    }
 
     try
     {
index 53ded4ae94ac4057d32056001851e95c9e606ce7..dc0d2d2f4b6bddd2b26893521caa64318d216e96 100644 (file)
@@ -63,8 +63,8 @@ using namespace std;
 
 struct Cmd
 {
-    typedef void (*cmd_func_t)(cli::GlobalOptions& global_options, ProxySnappers* snappers,
-       ProxySnapper* snapper);
+    typedef void (*cmd_func_t)(cli::GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers,
+                              ProxySnapper* snapper);
 
     typedef void (*help_func_t)();
 
@@ -87,21 +87,18 @@ struct Cmd
 };
 
 
-GetOpts getopts;
-
-
 struct MyFiles : public Files
 {
 
     MyFiles(const Files& files) : Files(files) {}
 
-    void bulk_process(FILE* file, std::function<void(File& file)> callback);
+    void bulk_process(FILE* file, GetOpts& get_opts, std::function<void(File& file)> callback);
 
 };
 
 
 void
-MyFiles::bulk_process(FILE* file, std::function<void(File& file)> callback)
+MyFiles::bulk_process(FILE* file, GetOpts& get_opts, std::function<void(File& file)> callback)
 {
     if (file)
     {
@@ -137,16 +134,16 @@ MyFiles::bulk_process(FILE* file, std::function<void(File& file)> callback)
     }
     else
     {
-       if (getopts.numArgs() == 0)
+       if (get_opts.num_args() == 0)
        {
            for (Files::iterator it = begin(); it != end(); ++it)
                callback(*it);
        }
        else
        {
-           while (getopts.numArgs() > 0)
+           while (get_opts.num_args() > 0)
            {
-               string name = getopts.popArg();
+               string name = get_opts.pop_arg();
 
                Files::iterator it = findAbsolutePath(name);
                if (it == end())
@@ -170,9 +167,9 @@ help_list_configs()
 
 
 void
-command_list_configs(cli::GlobalOptions& global_options, ProxySnappers* snappers, ProxySnapper*)
+command_list_configs(cli::GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper*)
 {
-    cli::Command::ListConfigs command(global_options, getopts, *snappers);
+    cli::Command::ListConfigs command(global_options, get_opts, *snappers);
 
     command.run();
 }
@@ -192,22 +189,21 @@ help_create_config()
 
 
 void
-command_create_config(cli::GlobalOptions& global_options, ProxySnappers* snappers, ProxySnapper*)
+command_create_config(cli::GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper*)
 {
-    const struct option options[] = {
-       { "fstype",             required_argument,      0,      'f' },
-       { "template",           required_argument,      0,      't' },
-       { 0, 0, 0, 0 }
+    const vector<Option> options = {
+       Option("fstype",        required_argument,      'f'),
+       Option("template",      required_argument,      't')
     };
 
-    GetOpts::parsed_opts opts = getopts.parse("create-config", options);
-    if (getopts.numArgs() != 1)
+    ParsedOpts opts = get_opts.parse("create-config", options);
+    if (get_opts.num_args() != 1)
     {
        cerr << _("Command 'create-config' needs one argument.") << endl;
        exit(EXIT_FAILURE);
     }
 
-    string subvolume = realpath(getopts.popArg());
+    string subvolume = realpath(get_opts.pop_arg());
     if (subvolume.empty())
     {
        cerr << _("Invalid subvolume.") << endl;
@@ -217,7 +213,7 @@ command_create_config(cli::GlobalOptions& global_options, ProxySnappers* snapper
     string fstype = "";
     string template_name = "default";
 
-    GetOpts::parsed_opts::const_iterator opt;
+    ParsedOpts::const_iterator opt;
 
     if ((opt = opts.find("fstype")) != opts.end())
        fstype = opt->second;
@@ -245,10 +241,10 @@ help_delete_config()
 
 
 void
-command_delete_config(cli::GlobalOptions& global_options, ProxySnappers* snappers, ProxySnapper*)
+command_delete_config(cli::GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper*)
 {
-    getopts.parse("delete-config", GetOpts::no_options);
-    if (getopts.hasArgs())
+    get_opts.parse("delete-config", GetOpts::no_options);
+    if (get_opts.has_args())
     {
        cerr << _("Command 'delete-config' does not take arguments.") << endl;
        exit(EXIT_FAILURE);
@@ -266,9 +262,9 @@ help_get_config()
 
 
 void
-command_get_config(cli::GlobalOptions& global_options, ProxySnappers* snappers, ProxySnapper*)
+command_get_config(cli::GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper*)
 {
-    cli::Command::GetConfig command(global_options, getopts, *snappers);
+    cli::Command::GetConfig command(global_options, get_opts, *snappers);
 
     command.run();
 }
@@ -284,16 +280,16 @@ help_set_config()
 
 
 void
-command_set_config(cli::GlobalOptions& global_options, ProxySnappers* snappers, ProxySnapper* snapper)
+command_set_config(cli::GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper* snapper)
 {
-    getopts.parse("set-config", GetOpts::no_options);
-    if (!getopts.hasArgs())
+    get_opts.parse("set-config", GetOpts::no_options);
+    if (!get_opts.has_args())
     {
        cerr << _("Command 'set-config' needs at least one argument.") << endl;
        exit(EXIT_FAILURE);
     }
 
-    ProxyConfig config(read_configdata(getopts.getArgs()));
+    ProxyConfig config(read_configdata(get_opts.get_args()));
 
     snapper->setConfig(config);
 }
@@ -307,9 +303,9 @@ help_list()
 
 
 void
-command_list(cli::GlobalOptions& global_options, ProxySnappers* snappers, ProxySnapper*)
+command_list(cli::GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper*)
 {
-    cli::Command::ListSnapshots command(global_options, getopts, *snappers);
+    cli::Command::ListSnapshots command(global_options, get_opts, *snappers);
 
     command.run();
 }
@@ -337,24 +333,23 @@ help_create()
 
 
 void
-command_create(cli::GlobalOptions& global_options, ProxySnappers* snappers, ProxySnapper* snapper)
+command_create(cli::GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper* snapper)
 {
-    const struct option options[] = {
-       { "type",               required_argument,      0,      't' },
-       { "pre-number",         required_argument,      0,      0 },
-       { "print-number",       no_argument,            0,      'p' },
-       { "description",        required_argument,      0,      'd' },
-       { "cleanup-algorithm",  required_argument,      0,      'c' },
-       { "userdata",           required_argument,      0,      'u' },
-       { "command",            required_argument,      0,      0 },
-       { "read-only",          no_argument,            0,      0 },
-       { "read-write",         no_argument,            0,      0 },
-       { "from",               required_argument,      0,      0 },
-       { 0, 0, 0, 0 }
+    const vector<Option> options = {
+       Option("type",                  required_argument,      't'),
+       Option("pre-number",            required_argument),
+       Option("print-number",          no_argument,            'p'),
+       Option("description",           required_argument,      'd'),
+       Option("cleanup-algorithm",     required_argument,      'c'),
+       Option("userdata",              required_argument,      'u'),
+       Option("command",               required_argument),
+       Option("read-only",             no_argument),
+       Option("read-write",            no_argument),
+       Option("from",                  required_argument)
     };
 
-    GetOpts::parsed_opts opts = getopts.parse("create", options);
-    if (getopts.hasArgs())
+    ParsedOpts opts = get_opts.parse("create", options);
+    if (get_opts.has_args())
     {
        cerr << _("Command 'create' does not take arguments.") << endl;
        exit(EXIT_FAILURE);
@@ -372,7 +367,7 @@ command_create(cli::GlobalOptions& global_options, ProxySnappers* snappers, Prox
     string command;
     ProxySnapshots::const_iterator parent = snapshots.getCurrent();
 
-    GetOpts::parsed_opts::const_iterator opt;
+    ParsedOpts::const_iterator opt;
 
     if ((opt = opts.find("type")) != opts.end())
     {
@@ -491,17 +486,16 @@ help_modify()
 
 
 void
-command_modify(cli::GlobalOptions& global_options, ProxySnappers* snappers, ProxySnapper* snapper)
+command_modify(cli::GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper* snapper)
 {
-    const struct option options[] = {
-       { "description",        required_argument,      0,      'd' },
-       { "cleanup-algorithm",  required_argument,      0,      'c' },
-       { "userdata",           required_argument,      0,      'u' },
-       { 0, 0, 0, 0 }
+    const vector<Option> options = {
+       Option("description",           required_argument,      'd'),
+       Option("cleanup-algorithm",     required_argument,      'c'),
+       Option("userdata",              required_argument,      'u')
     };
 
-    GetOpts::parsed_opts opts = getopts.parse("modify", options);
-    if (!getopts.hasArgs())
+    ParsedOpts opts = get_opts.parse("modify", options);
+    if (!get_opts.has_args())
     {
        cerr << _("Command 'modify' needs at least one argument.") << endl;
        exit(EXIT_FAILURE);
@@ -509,13 +503,13 @@ command_modify(cli::GlobalOptions& global_options, ProxySnappers* snappers, Prox
 
     ProxySnapshots& snapshots = snapper->getSnapshots();
 
-    while (getopts.hasArgs())
+    while (get_opts.has_args())
     {
-       ProxySnapshots::iterator snapshot = snapshots.findNum(getopts.popArg());
+       ProxySnapshots::iterator snapshot = snapshots.findNum(get_opts.pop_arg());
 
        SMD smd = snapshot->getSmd();
 
-       GetOpts::parsed_opts::const_iterator opt;
+       ParsedOpts::const_iterator opt;
 
        if ((opt = opts.find("description")) != opts.end())
            smd.description = opt->second;
@@ -575,15 +569,14 @@ filter_undeletables(ProxySnapshots& snapshots, vector<ProxySnapshots::iterator>&
 
 
 void
-command_delete(cli::GlobalOptions& global_options, ProxySnappers* snappers, ProxySnapper* snapper)
+command_delete(cli::GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper* snapper)
 {
-    const struct option options[] = {
-       { "sync",               no_argument,            0,      's' },
-       { 0, 0, 0, 0 }
+    const vector<Option> options = {
+       Option("sync",  no_argument,    's')
     };
 
-    GetOpts::parsed_opts opts = getopts.parse("delete", options);
-    if (!getopts.hasArgs())
+    ParsedOpts opts = get_opts.parse("delete", options);
+    if (!get_opts.has_args())
     {
        cerr << _("Command 'delete' needs at least one argument.") << endl;
        exit(EXIT_FAILURE);
@@ -591,7 +584,7 @@ command_delete(cli::GlobalOptions& global_options, ProxySnappers* snappers, Prox
 
     bool sync = false;
 
-    GetOpts::parsed_opts::const_iterator opt;
+    ParsedOpts::const_iterator opt;
 
     if ((opt = opts.find("sync")) != opts.end())
        sync = true;
@@ -600,9 +593,9 @@ command_delete(cli::GlobalOptions& global_options, ProxySnappers* snappers, Prox
 
     vector<ProxySnapshots::iterator> nums;
 
-    while (getopts.hasArgs())
+    while (get_opts.has_args())
     {
-       string arg = getopts.popArg();
+       string arg = get_opts.pop_arg();
 
        if (arg.find_first_of("-") == string::npos)
        {
@@ -649,10 +642,10 @@ help_mount()
 
 
 void
-command_mount(cli::GlobalOptions& global_options, ProxySnappers* snappers, ProxySnapper* snapper)
+command_mount(cli::GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper* snapper)
 {
-    getopts.parse("mount", GetOpts::no_options);
-    if (!getopts.hasArgs())
+    get_opts.parse("mount", GetOpts::no_options);
+    if (!get_opts.has_args())
     {
        cerr << _("Command 'mount' needs at least one argument.") << endl;
        exit(EXIT_FAILURE);
@@ -660,9 +653,9 @@ command_mount(cli::GlobalOptions& global_options, ProxySnappers* snappers, Proxy
 
     const ProxySnapshots& snapshots = snapper->getSnapshots();
 
-    while (getopts.hasArgs())
+    while (get_opts.has_args())
     {
-       ProxySnapshots::const_iterator snapshot = snapshots.findNum(getopts.popArg());
+       ProxySnapshots::const_iterator snapshot = snapshots.findNum(get_opts.pop_arg());
        snapshot->mountFilesystemSnapshot(true);
     }
 }
@@ -678,10 +671,10 @@ help_umount()
 
 
 void
-command_umount(cli::GlobalOptions& global_options, ProxySnappers* snappers, ProxySnapper* snapper)
+command_umount(cli::GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper* snapper)
 {
-    getopts.parse("umount", GetOpts::no_options);
-    if (!getopts.hasArgs())
+    get_opts.parse("umount", GetOpts::no_options);
+    if (!get_opts.has_args())
     {
        cerr << _("Command 'umount' needs at least one argument.") << endl;
        exit(EXIT_FAILURE);
@@ -689,9 +682,9 @@ command_umount(cli::GlobalOptions& global_options, ProxySnappers* snappers, Prox
 
     const ProxySnapshots& snapshots = snapper->getSnapshots();
 
-    while (getopts.hasArgs())
+    while (get_opts.has_args())
     {
-       ProxySnapshots::const_iterator snapshot = snapshots.findNum(getopts.popArg());
+       ProxySnapshots::const_iterator snapshot = snapshots.findNum(get_opts.pop_arg());
        snapshot->umountFilesystemSnapshot(true);
     }
 }
@@ -710,19 +703,18 @@ help_status()
 
 
 void
-command_status(cli::GlobalOptions& global_options, ProxySnappers* snappers, ProxySnapper* snapper)
+command_status(cli::GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper* snapper)
 {
-    const struct option options[] = {
-       { "output",             required_argument,      0,      'o' },
-       { 0, 0, 0, 0 }
+    const vector<Option> options = {
+       Option("output",        required_argument,      'o')
     };
 
-    GetOpts::parsed_opts opts = getopts.parse("status", options);
-    if (getopts.numArgs() != 1)
+    ParsedOpts opts = get_opts.parse("status", options);
+    if (get_opts.num_args() != 1)
     {
        cerr << _("Command 'status' needs one argument.") << endl;
 
-       if (getopts.numArgs() == 2)
+       if (get_opts.num_args() == 2)
        {
            cerr << _("Maybe you forgot the delimiter '..' between the snapshot numbers.") << endl
                 << _("See 'man snapper' for further instructions.") << endl;
@@ -731,12 +723,12 @@ command_status(cli::GlobalOptions& global_options, ProxySnappers* snappers, Prox
        exit(EXIT_FAILURE);
     }
 
-    GetOpts::parsed_opts::const_iterator opt;
+    ParsedOpts::const_iterator opt;
 
     ProxySnapshots& snapshots = snapper->getSnapshots();
 
     pair<ProxySnapshots::const_iterator, ProxySnapshots::const_iterator> range =
-       snapshots.findNums(getopts.popArg());
+       snapshots.findNums(get_opts.pop_arg());
 
     ProxyComparison comparison = snapper->createComparison(*range.first, *range.second, false);
 
@@ -778,17 +770,16 @@ help_diff()
 
 
 void
-command_diff(cli::GlobalOptions& global_options, ProxySnappers* snappers, ProxySnapper* snapper)
+command_diff(cli::GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper* snapper)
 {
-    const struct option options[] = {
-       { "input",              required_argument,      0,      'i' },
-       { "diff-cmd",           required_argument,      0,      0 },
-       { "extensions",         required_argument,      0,      'x' },
-       { 0, 0, 0, 0 }
+    const vector<Option> options = {
+       Option("input",         required_argument,      'i'),
+       Option("diff-cmd",      required_argument),
+       Option("extensions",    required_argument,      'x'),
     };
 
-    GetOpts::parsed_opts opts = getopts.parse("diff", options);
-    if (getopts.numArgs() < 1)
+    ParsedOpts opts = get_opts.parse("diff", options);
+    if (get_opts.num_args() < 1)
     {
        cerr << _("Command 'diff' needs at least one argument.") << endl;
        exit(EXIT_FAILURE);
@@ -797,7 +788,7 @@ command_diff(cli::GlobalOptions& global_options, ProxySnappers* snappers, ProxyS
     FILE* file = NULL;
     Differ differ;
 
-    GetOpts::parsed_opts::const_iterator opt;
+    ParsedOpts::const_iterator opt;
 
     if ((opt = opts.find("input")) != opts.end())
     {
@@ -818,13 +809,13 @@ command_diff(cli::GlobalOptions& global_options, ProxySnappers* snappers, ProxyS
     ProxySnapshots& snapshots = snapper->getSnapshots();
 
     pair<ProxySnapshots::const_iterator, ProxySnapshots::const_iterator> range =
-       snapshots.findNums(getopts.popArg());
+       snapshots.findNums(get_opts.pop_arg());
 
     ProxyComparison comparison = snapper->createComparison(*range.first, *range.second, true);
 
     MyFiles files(comparison.getFiles());
 
-    files.bulk_process(file, [differ](const File& file) {
+    files.bulk_process(file, get_opts, [differ](const File& file) {
        differ.run(file.getAbsolutePath(LOC_PRE), file.getAbsolutePath(LOC_POST));
     });
 }
@@ -843,15 +834,14 @@ help_undo()
 
 
 void
-command_undo(cli::GlobalOptions& global_options, ProxySnappers* snappers, ProxySnapper* snapper)
+command_undo(cli::GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper* snapper)
 {
-    const struct option options[] = {
-       { "input",              required_argument,      0,      'i' },
-       { 0, 0, 0, 0 }
+    const vector<Option> options = {
+       Option("input",         required_argument,      'i')
     };
 
-    GetOpts::parsed_opts opts = getopts.parse("undochange", options);
-    if (getopts.numArgs() < 1)
+    ParsedOpts opts = get_opts.parse("undochange", options);
+    if (get_opts.num_args() < 1)
     {
        cerr << _("Command 'undochange' needs at least one argument.") << endl;
        exit(EXIT_FAILURE);
@@ -860,11 +850,11 @@ command_undo(cli::GlobalOptions& global_options, ProxySnappers* snappers, ProxyS
     ProxySnapshots& snapshots = snapper->getSnapshots();
 
     pair<ProxySnapshots::const_iterator, ProxySnapshots::const_iterator> range =
-       snapshots.findNums(getopts.popArg());
+       snapshots.findNums(get_opts.pop_arg());
 
     FILE* file = NULL;
 
-    GetOpts::parsed_opts::const_iterator opt;
+    ParsedOpts::const_iterator opt;
 
     if ((opt = opts.find("input")) != opts.end())
     {
@@ -886,7 +876,7 @@ command_undo(cli::GlobalOptions& global_options, ProxySnappers* snappers, ProxyS
 
     MyFiles files(comparison.getFiles());
 
-    files.bulk_process(file, [](File& file) {
+    files.bulk_process(file, get_opts, [](File& file) {
        file.setUndo(true);
     });
 
@@ -997,18 +987,17 @@ help_rollback()
 
 
 void
-command_rollback(cli::GlobalOptions& global_options, ProxySnappers* snappers, ProxySnapper* snapper)
+command_rollback(cli::GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper* snapper)
 {
-    const struct option options[] = {
-       { "print-number",       no_argument,            0,      'p' },
-       { "description",        required_argument,      0,      'd' },
-       { "cleanup-algorithm",  required_argument,      0,      'c' },
-       { "userdata",           required_argument,      0,      'u' },
-       { 0, 0, 0, 0 }
+    const vector<Option> options = {
+       Option("print-number",          no_argument,            'p'),
+       Option("description",           required_argument,      'd'),
+       Option("cleanup-algorithm",     required_argument,      'c'),
+       Option("userdata",              required_argument,      'u')
     };
 
-    GetOpts::parsed_opts opts = getopts.parse("rollback", options);
-    if (getopts.hasArgs() && getopts.numArgs() != 1)
+    ParsedOpts opts = get_opts.parse("rollback", options);
+    if (get_opts.has_args() && get_opts.num_args() != 1)
     {
        cerr << _("Command 'rollback' takes either one or no argument.") << endl;
        exit(EXIT_FAILURE);
@@ -1027,7 +1016,7 @@ command_rollback(cli::GlobalOptions& global_options, ProxySnappers* snappers, Pr
     SCD scd2;
     scd2.description = default_description2;
 
-    GetOpts::parsed_opts::const_iterator opt;
+    ParsedOpts::const_iterator opt;
 
     if ((opt = opts.find("print-number")) != opts.end())
        print_number = true;
@@ -1094,7 +1083,7 @@ command_rollback(cli::GlobalOptions& global_options, ProxySnappers* snappers, Pr
            ProxySnapshots::const_iterator snapshot1 = snapshots.end();
            ProxySnapshots::const_iterator snapshot2 = snapshots.end();
 
-           if (getopts.numArgs() == 0)
+           if (get_opts.num_args() == 0)
            {
                if (!global_options.quiet())
                    cout << _("Creating read-only snapshot of default subvolume.") << flush;
@@ -1120,7 +1109,7 @@ command_rollback(cli::GlobalOptions& global_options, ProxySnappers* snappers, Pr
            }
            else
            {
-               ProxySnapshots::const_iterator tmp = snapshots.findNum(getopts.popArg());
+               ProxySnapshots::const_iterator tmp = snapshots.findNum(get_opts.pop_arg());
 
                if (!global_options.quiet())
                    cout << _("Creating read-only snapshot of current system.") << flush;
@@ -1175,13 +1164,13 @@ command_rollback(cli::GlobalOptions& global_options, ProxySnappers* snappers, Pr
 
            ProxySnapshots::iterator snapshot = snapshots.end();
 
-           if (getopts.numArgs() == 0)
+           if (get_opts.num_args() == 0)
            {
                snapshot = snapshots.getActive();
            }
            else
            {
-               snapshot = snapshots.findNum(getopts.popArg());
+               snapshot = snapshots.findNum(get_opts.pop_arg());
            }
 
            if (previous_default == snapshot)
@@ -1226,10 +1215,10 @@ help_setup_quota()
 
 
 void
-command_setup_quota(cli::GlobalOptions& global_options, ProxySnappers* snappers, ProxySnapper* snapper)
+command_setup_quota(cli::GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper* snapper)
 {
-    GetOpts::parsed_opts opts = getopts.parse("setup-quota", GetOpts::no_options);
-    if (getopts.numArgs() != 0)
+    ParsedOpts opts = get_opts.parse("setup-quota", GetOpts::no_options);
+    if (get_opts.num_args() != 0)
     {
        cerr << _("Command 'setup-quota' does not take arguments.") << endl;
        exit(EXIT_FAILURE);
@@ -1249,16 +1238,16 @@ help_cleanup()
 
 
 void
-command_cleanup(cli::GlobalOptions& global_options, ProxySnappers* snappers, ProxySnapper* snapper)
+command_cleanup(cli::GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper* snapper)
 {
-    GetOpts::parsed_opts opts = getopts.parse("cleanup", GetOpts::no_options);
-    if (getopts.numArgs() != 1)
+    ParsedOpts opts = get_opts.parse("cleanup", GetOpts::no_options);
+    if (get_opts.num_args() != 1)
     {
        cerr << _("Command 'cleanup' needs one arguments.") << endl;
        exit(EXIT_FAILURE);
     }
 
-    string cleanup = getopts.popArg();
+    string cleanup = get_opts.pop_arg();
 
     if (cleanup == "number")
     {
@@ -1287,10 +1276,10 @@ help_debug()
 
 
 void
-command_debug(cli::GlobalOptions& global_options, ProxySnappers* snappers, ProxySnapper*)
+command_debug(cli::GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper*)
 {
-    getopts.parse("debug", GetOpts::no_options);
-    if (getopts.hasArgs())
+    get_opts.parse("debug", GetOpts::no_options);
+    if (get_opts.has_args())
     {
        cerr << _("Command 'debug' does not take arguments.") << endl;
        exit(EXIT_FAILURE);
@@ -1331,10 +1320,10 @@ print_xa_diff(const string loc_pre, const string loc_post)
 }
 
 void
-command_xa_diff(cli::GlobalOptions& global_options, ProxySnappers* snappers, ProxySnapper* snapper)
+command_xa_diff(cli::GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper* snapper)
 {
-    GetOpts::parsed_opts opts = getopts.parse("xadiff", GetOpts::no_options);
-    if (getopts.numArgs() < 1)
+    ParsedOpts opts = get_opts.parse("xadiff", GetOpts::no_options);
+    if (get_opts.num_args() < 1)
     {
         cerr << _("Command 'xadiff' needs at least one argument.") << endl;
         exit(EXIT_FAILURE);
@@ -1343,13 +1332,13 @@ command_xa_diff(cli::GlobalOptions& global_options, ProxySnappers* snappers, Pro
     ProxySnapshots& snapshots = snapper->getSnapshots();
 
     pair<ProxySnapshots::const_iterator, ProxySnapshots::const_iterator> range =
-       snapshots.findNums(getopts.popArg());
+       snapshots.findNums(get_opts.pop_arg());
 
     ProxyComparison comparison = snapper->createComparison(*range.first, *range.second, true);
 
     MyFiles files(comparison.getFiles());
 
-    if (getopts.numArgs() == 0)
+    if (get_opts.num_args() == 0)
     {
        for (Files::const_iterator it1 = files.begin(); it1 != files.end(); ++it1)
             if (it1->getPreToPostStatus() & XATTRS)
@@ -1357,9 +1346,9 @@ command_xa_diff(cli::GlobalOptions& global_options, ProxySnappers* snappers, Pro
     }
     else
     {
-        while (getopts.numArgs() > 0)
+        while (get_opts.num_args() > 0)
         {
-            string name = getopts.popArg();
+            string name = get_opts.pop_arg();
 
             Files::const_iterator it1 = files.findAbsolutePath(name);
             if (it1 == files.end())
@@ -1402,10 +1391,10 @@ usage()
 void help() __attribute__ ((__noreturn__));
 
 void
-help(const list<Cmd>& cmds)
+help(const vector<Cmd>& cmds, GetOpts& get_opts)
 {
-    getopts.parse("help", GetOpts::no_options);
-    if (getopts.hasArgs())
+    get_opts.parse("help", GetOpts::no_options);
+    if (get_opts.has_args())
     {
        cerr << _("Command 'help' does not take arguments.") << endl;
        exit(EXIT_FAILURE);
@@ -1416,8 +1405,8 @@ help(const list<Cmd>& cmds)
 
     cout << cli::GlobalOptions::help_text() << endl;
 
-    for (list<Cmd>::const_iterator cmd = cmds.begin(); cmd != cmds.end(); ++cmd)
-       (*cmd->help_func)();
+    for (const Cmd& cmd : cmds)
+       (*cmd.help_func)();
 
     exit(EXIT_SUCCESS);
 }
@@ -1438,7 +1427,7 @@ main(int argc, char** argv)
     setLogDo(&log_do);
     setLogQuery(&log_query);
 
-    const list<Cmd> cmds = {
+    const vector<Cmd> cmds = {
        Cmd("list-configs", command_list_configs, help_list_configs, false),
        Cmd("create-config", command_create_config, help_create_config, false),
        Cmd("delete-config", command_delete_config, help_delete_config, false),
@@ -1466,9 +1455,9 @@ main(int argc, char** argv)
 
     try
     {
-       getopts.init(argc, argv);
+       GetOpts get_opts(argc, argv);
 
-       cli::GlobalOptions global_options(getopts);
+       cli::GlobalOptions global_options(get_opts);
 
        if (global_options.version())
        {
@@ -1479,19 +1468,19 @@ main(int argc, char** argv)
 
        if (global_options.help())
        {
-           help(cmds);
+           help(cmds, get_opts);
        }
 
-       if (!getopts.hasArgs())
+       if (!get_opts.has_args())
        {
            cerr << _("No command provided.") << endl
                 << _("Try 'snapper --help' for more information.") << endl;
            exit(EXIT_FAILURE);
        }
 
-       const char* command = getopts.popArg();
+       const char* command = get_opts.pop_arg();
 
-       list<Cmd>::const_iterator cmd = cmds.begin();
+       vector<Cmd>::const_iterator cmd = cmds.begin();
        while (cmd != cmds.end() && (cmd->name != command && !contains(cmd->aliases, command)))
            ++cmd;
 
@@ -1508,9 +1497,9 @@ main(int argc, char** argv)
                                   ProxySnappers::createDbus());
 
            if (cmd->needs_snapper)
-               (*cmd->cmd_func)(global_options, &snappers, snappers.getSnapper(global_options.config()));
+               (*cmd->cmd_func)(global_options, get_opts, &snappers, snappers.getSnapper(global_options.config()));
            else
-               (*cmd->cmd_func)(global_options, &snappers, nullptr);
+               (*cmd->cmd_func)(global_options, get_opts, &snappers, nullptr);
        }
        catch (const DBus::ErrorException& e)
        {
@@ -1613,7 +1602,8 @@ main(int argc, char** argv)
        catch (const OptionsException& e)
        {
            SN_CAUGHT(e);
-           cerr << e.what() << endl;
+           cerr << e.what() << endl
+                << _("Try 'snapper --help' for more information.") << endl;
            exit(EXIT_FAILURE);
        }
        catch (const Exception& e)
@@ -1626,7 +1616,8 @@ main(int argc, char** argv)
     catch (const OptionsException& e)
     {
        SN_CAUGHT(e);
-       cerr << e.what() << endl;
+       cerr << e.what() << endl
+            << _("Try 'snapper --help' for more information.") << endl;
        exit(EXIT_FAILURE);
     }
 
index f83f9996561e49625fefb479d878abbdae0435b0..c34f28e406ecc2584f8358f76186455be9ce8ed4 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * Copyright (c) [2014-2015] Novell, Inc.
- * Copyright (c) [2016,2018] SUSE LLC
+ * Copyright (c) [2016-2020] SUSE LLC
  *
  * All Rights Reserved.
  *
@@ -156,34 +156,39 @@ main(int argc, char** argv)
 {
     setlocale(LC_ALL, "");
 
-    const struct option options[] = {
-       { "timeline",           no_argument,            0,      0 },
-       { "cleanup",            no_argument,            0,      0 },
-       { "userdata",           required_argument,      0,      'u' },
-       { 0, 0, 0, 0 }
-    };
-
     bool do_timeline = false;
     bool do_cleanup = false;
-
     map<string, string> userdata;
 
-    GetOpts getopts;
+    try
+    {
+       const vector<Option> options = {
+           Option("timeline",          no_argument),
+           Option("cleanup",           no_argument),
+           Option("userdata",          required_argument,      'u')
+       };
 
-    getopts.init(argc, argv);
+       GetOpts get_opts(argc, argv);
 
-    GetOpts::parsed_opts opts = getopts.parse(options);
+       ParsedOpts opts = get_opts.parse(options);
 
-    GetOpts::parsed_opts::const_iterator opt;
+       ParsedOpts::const_iterator opt;
 
-    if (opts.find("timeline") != opts.end())
-       do_timeline = true;
+       if (opts.has_option("timeline"))
+           do_timeline = true;
 
-    if (opts.find("cleanup") != opts.end())
-       do_cleanup = true;
+       if (opts.has_option("cleanup"))
+           do_cleanup = true;
 
-    if ((opt = opts.find("userdata")) != opts.end())
-       userdata = read_userdata(opt->second);
+       if ((opt = opts.find("userdata")) != opts.end())
+           userdata = read_userdata(opt->second);
+    }
+    catch (const OptionsException& e)
+    {
+        SN_CAUGHT(e);
+        cerr << e.what() << endl;
+        exit(EXIT_FAILURE);
+    }
 
     bool ok = true;
 
index 0fdee9c616d96473355b12ef999947e8418e5424..707c930988e27bde502fe2e3ce6d3f56fa1408a8 100644 (file)
-
-#include <iostream>
-#include <boost/algorithm/string.hpp>
+/*
+ * Copyright (c) [2011-2015] Novell, Inc.
+ * Copyright (c) [2016-2020] SUSE LLC
+ *
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License as published
+ * by the Free Software Foundation.
+ *
+ * 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, contact Novell, Inc.
+ *
+ * To contact Novell about this file by physical or electronic mail, you may
+ * find current contact information at www.novell.com.
+ */
+
+
+#include <algorithm>
 
 #include <snapper/AppUtil.h>
 
 #include "GetOpts.h"
 #include "text.h"
 
-using namespace std;
-using namespace snapper;
-
-
-// based on getopt.cc from zypper, thanks jkupec
 
+namespace snapper
+{
 
-const struct option GetOpts::no_options[1] = {
-    { 0, 0, 0, 0 }
-};
+    using namespace std;
 
 
-void
-GetOpts::init(int new_argc, char** new_argv)
-{
-    argc = new_argc;
-    argv = new_argv;
-}
+    const struct vector<Option> GetOpts::no_options = {};
 
 
-GetOpts::parsed_opts
-GetOpts::parse(const struct option* longopts)
-{
-    return parse(NULL, longopts);
-}
+    GetOpts::GetOpts(int argc, char** argv)
+       : argc(argc), argv(argv)
+    {
+       opterr = 0;             // we report errors on our own
+    }
 
 
-GetOpts::parsed_opts
-GetOpts::parse(const char* command, const struct option* longopts)
-{
-    parsed_opts result;
-    opterr = 0;                        // we report errors on our own
+    ParsedOpts
+    GetOpts::parse(const vector<Option>& options)
+    {
+       return parse(nullptr, options);
+    }
 
-    string optstring = make_optstring(longopts);
-    short2long_t short2long = make_short2long(longopts);
 
-    while (true)
+    ParsedOpts
+    GetOpts::parse(const char* command, const vector<Option>& options)
     {
-       int option_index = 0;
-       int c = getopt_long(argc, argv, optstring.c_str(), longopts, &option_index);
+       string optstring = make_optstring(options);
+       vector<struct option> longopts = make_longopts(options);
+
+       map<string, string> result;
 
-       switch (c)
+       while (true)
        {
-           case -1:
-               return result;
-
-           case '?':
-               if (!command)
-                   cerr << sformat(_("Unknown global option '%s'."), argv[optind - 1]) << endl;
-               else
-                   cerr << sformat(_("Unknown option '%s' for command '%s'."), argv[optind - 1], command) << endl;
-               cerr << _("Try 'snapper --help' for more information.") << endl;
-               exit(EXIT_FAILURE);
-
-           case ':':
-               if (!command)
-                   cerr << sformat(_("Missing argument for global option '%s'."), argv[optind - 1]) << endl;
-               else
-                   cerr << sformat(_("Missing argument for command option '%s'."), argv[optind - 1]) << endl;
-               cerr << _("Try 'snapper --help' for more information.") << endl;
-               exit(EXIT_FAILURE);
-
-           default:
-               const char* opt = c ? short2long[c] : longopts[option_index].name;
-               result[opt] = optarg ? optarg : "";
-               break;
+           int option_index = 0;
+           int c = getopt_long(argc, argv, optstring.c_str(), &longopts[0], &option_index);
+
+           switch (c)
+           {
+               case -1:
+               {
+                   return result;
+               }
+
+               case '?':
+               {
+                   string opt;
+                   if (optopt != 0)
+                       opt = string("-") + (char)(optopt);
+                   else
+                       opt = argv[optind - 1];
+
+                   string msg;
+                   if (!command)
+                       msg = sformat(_("Unknown global option '%s'."), opt.c_str());
+                   else
+                       msg = sformat(_("Unknown option '%s' for command '%s'."), opt.c_str(), command);
+
+                   SN_THROW(OptionsException(msg));
+                   break;
+               }
+
+               case ':':
+               {
+                   string opt;
+                   if (optopt != 0)
+                   {
+                       vector<Option>::const_iterator it = find(options, optopt);
+                       if (it == options.end())
+                           SN_THROW(Exception("option not found"));
+
+                       opt = string("--") + it->name;
+                   }
+                   else
+                   {
+                       opt = argv[optind - 1];
+                   }
+
+                   string msg;
+                   if (!command)
+                       msg = sformat(_("Missing argument for global option '%s'."), opt.c_str());
+                   else
+                       msg = sformat(_("Missing argument for command option '%s'."), opt.c_str());
+
+                   SN_THROW(OptionsException(msg));
+                   break;
+               }
+
+               default:
+               {
+                   vector<Option>::const_iterator it = c ? find(options, c) : options.begin() + option_index;
+                   if (it == options.end())
+                       SN_THROW(Exception("option not found"));
+
+                   result[it->name] = optarg ? optarg : "";
+                   break;
+               }
+           }
        }
     }
-}
 
 
-string
-GetOpts::make_optstring(const struct option* longopts) const
-{
-    // '+' - do not permute, stop at the 1st nonoption, which is the command or an argument
-    // ':' - return ':' to indicate missing arg, not '?'
-    string optstring = "+:";
+    vector<Option>::const_iterator
+    GetOpts::find(const vector<Option>& options, char c) const
+    {
+       return find_if(options.begin(), options.end(), [c](const Option& option)
+           { return option.c == c; }
+       );
+    }
 
-    for (; longopts && longopts->name; ++longopts)
+
+    string
+    GetOpts::make_optstring(const vector<Option>& options) const
     {
-       if (!longopts->flag && longopts->val)
+       // '+' - do not permute, stop at the 1st non-option, which is the command or an argument
+       // ':' - return ':' to indicate missing arg, not '?'
+       string optstring = "+:";
+
+       for (const Option& option : options)
        {
-           optstring += (char) longopts->val;
-           if (longopts->has_arg == required_argument)
-               optstring += ':';
-           else if (longopts->has_arg == optional_argument)
-               optstring += "::";
+           if (option.c == 0)
+               continue;
+
+           optstring += option.c;
+
+           switch (option.has_arg)
+           {
+               case no_argument:
+                   break;
+
+               case required_argument:
+                   optstring += ':';
+                   break;
+           }
        }
+
+       return optstring;
     }
 
-    return optstring;
-}
 
+    vector<struct option>
+    GetOpts::make_longopts(const vector<Option> options) const
+    {
+       vector<struct option> ret;
 
-GetOpts::short2long_t
-GetOpts::make_short2long(const struct option* longopts) const
-{
-    short2long_t result;
+       for (const Option& option : options)
+           ret.push_back({ option.name, option.has_arg, nullptr, option.c });
 
-    for (; longopts && longopts->name; ++longopts)
-    {
-       if (!longopts->flag && longopts->val)
-       {
-           result[longopts->val] = longopts->name;
-       }
+       ret.push_back({ nullptr, 0, nullptr, 0 });
+
+       return ret;
     }
 
-    return result;
 }
index 56de143e4a44bb36070c9229cdf3911e4daecbed..c681d3d2e735a2bbb4ef15fc79cf386c07c9df04 100644 (file)
-#ifndef GET_OPTS_H
-#define GET_OPTS_H
+/*
+ * Copyright (c) [2011-2015] Novell, Inc.
+ * Copyright (c) [2016-2020] SUSE LLC
+ *
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License as published
+ * by the Free Software Foundation.
+ *
+ * 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, contact Novell, Inc.
+ *
+ * To contact Novell about this file by physical or electronic mail, you may
+ * find current contact information at www.novell.com.
+ */
+
+
+#ifndef SNAPPER_GET_OPTS_H
+#define SNAPPER_GET_OPTS_H
 
 #include <getopt.h>
 #include <string>
+#include <vector>
 #include <map>
-#include <list>
 
+#include <snapper/Exception.h>
 
-class GetOpts
+
+namespace snapper
 {
-public:
 
-    static const struct option no_options[1];
+    using namespace std;
+
+
+    struct OptionsException : public Exception
+    {
+       explicit OptionsException(const string& msg) : Exception(msg) {}
+    };
+
+
+    struct Option
+    {
+       Option(const char* name, int has_arg, char c = 0)
+           : name(name), has_arg(has_arg), c(c)
+       {
+       }
+
+       const char* name;
+       int has_arg;
+       char c;
+    };
+
+
+    class ParsedOpts
+    {
+    public:
+
+       ParsedOpts() = default;         // TODO remove
+
+       ParsedOpts(const map<string, string>& args) : args(args) {}
+
+       using const_iterator = map<string, string>::const_iterator;
+
+       bool has_option(const string& name) const { return find(name) != end(); }
+
+       const_iterator find(const string& name) const { return args.find(name); }
+
+       const_iterator end() const { return args.end(); }
+
+    private:
+
+       map<string, string> args;       // TODO make const
+
+    };
+
+
+    class GetOpts
+    {
+    public:
 
-    typedef std::map<std::string, std::string> parsed_opts;
+       static const vector<Option> no_options;
 
-    void init(int argc, char** argv);
+       GetOpts(int argc, char** argv);
 
-    // longopts.flag must be NULL
-    parsed_opts parse(const struct option* longopts);
-    parsed_opts parse(const char* command, const struct option* longopts);
+       ParsedOpts parse(const vector<Option>& options);
+       ParsedOpts parse(const char* command, const vector<Option>& options);
 
-    bool hasArgs() const { return argc - optind > 0; }
+       bool has_args() const { return argc - optind > 0; }
 
-    int numArgs() const { return argc - optind; }
+       int num_args() const { return argc - optind; }
 
-    const char* popArg() { return argv[optind++]; }
+       const char* pop_arg() { return argv[optind++]; }
 
-    std::list<std::string> getArgs() const {
-       return std::list<std::string>(&argv[optind], &argv[argc]);
-    }
+       vector<string> get_args() const { return vector<string>(&argv[optind], &argv[argc]); }
 
-private:
+    private:
 
-    int argc;
-    char** argv;
+       int argc;
+       char** argv;
 
-    std::string make_optstring(const struct option* longopts) const;
+       string make_optstring(const vector<Option>& options) const;
+       vector<struct option> make_longopts(const vector<Option> options) const;
 
-    typedef std::map<int, const char*> short2long_t;
+       vector<Option>::const_iterator find(const vector<Option>& options, char c) const;
 
-    short2long_t make_short2long(const struct option* longopts) const;
+    };
 
-};
+}
 
 #endif
index 2543b8f67331a0a01e5fde1d2411e75563625e76..5b30f076efe146e54c059397e0f2ca84d2356b9f 100644 (file)
@@ -1,3 +1,8 @@
+-------------------------------------------------------------------
+Fri Aug 28 11:06:23 CEST 2020 - aschnell@suse.com
+
+- improved error reporting in getopt wrapper (part of bsc#1150156)
+
 -------------------------------------------------------------------
 Thu Aug 27 12:04:44 CEST 2020 - aschnell@suse.com
 
index 431f87cbf0d6ebcb33a9cdf26f4b5821c00d7f13..0ff300c6add5ffeb15025604b2c713cadc36e780 100644 (file)
@@ -8,7 +8,7 @@ LDADD = ../snapper/libsnapper.la ../dbus/libdbus.la -lboost_unit_test_framework
 
 check_PROGRAMS = sysconfig-get1.test dirname1.test basename1.test                  \
        equal-date.test dbus-escape.test cmp-lt.test humanstring.test table.test    \
-       csv-formatter.test json-formatter.test
+       csv-formatter.test json-formatter.test getopts.test
 
 if ENABLE_BTRFS_QUOTA
 check_PROGRAMS += qgroup1.test
@@ -29,3 +29,5 @@ table_test_LDADD = -lboost_unit_test_framework ../client/utils/libutils.la
 csv_formatter_test_LDADD = -lboost_unit_test_framework ../client/utils/libutils.la
 
 json_formatter_test_LDADD = -lboost_unit_test_framework ../client/utils/libutils.la
+
+getopts_test_LDADD = -lboost_unit_test_framework ../client/utils/libutils.la
diff --git a/testsuite/getopts.cc b/testsuite/getopts.cc
new file mode 100644 (file)
index 0000000..9ca7329
--- /dev/null
@@ -0,0 +1,214 @@
+
+#define BOOST_TEST_DYN_LINK
+#define BOOST_TEST_MODULE getopt
+
+#include <boost/test/unit_test.hpp>
+
+#include "../client/utils/GetOpts.h"
+
+using namespace snapper;
+
+
+class Args
+{
+
+public:
+
+    Args(std::initializer_list<string> init)
+    {
+       optind = 0;
+
+       for (const string& s : init)
+           tmp.push_back(strdup(s.c_str()));
+       tmp.push_back(nullptr);
+    }
+
+    ~Args()
+    {
+       for (char* s : tmp)
+           free(s);
+    }
+
+    int argc() const { return tmp.size() - 1; }
+    char** argv() { return &tmp.front(); }
+
+    bool all_used() const { return (size_t)(optind) == tmp.size() - 1; }
+
+private:
+
+    vector<char*> tmp;
+
+};
+
+
+const vector<Option> global_opts = {
+    Option("verbose",          no_argument,            'v'),
+    Option("table-style",      required_argument,      't')
+};
+
+
+const vector<Option> create_opts = {
+    Option("type",             required_argument,      't'),
+    Option("print-number",     no_argument,            'p'),
+};
+
+
+BOOST_AUTO_TEST_CASE(good1)
+{
+    Args args({ "getopt", "--verbose" });
+    GetOpts get_opts(args.argc(), args.argv());
+
+    ParsedOpts parsed_global_opts = get_opts.parse(global_opts);
+
+    BOOST_CHECK(parsed_global_opts.has_option("verbose"));
+    BOOST_CHECK(!parsed_global_opts.has_option("read-my-mind"));
+
+    BOOST_CHECK(!get_opts.has_args());
+
+    BOOST_CHECK(args.all_used());
+}
+
+
+BOOST_AUTO_TEST_CASE(good2)
+{
+    Args args({ "getopt", "-v", "create", "--type", "pre" });
+    GetOpts get_opts(args.argc(), args.argv());
+
+    // parse global options
+
+    ParsedOpts parsed_global_opts = get_opts.parse(global_opts);
+
+    BOOST_CHECK(parsed_global_opts.has_option("verbose"));
+    BOOST_CHECK(!parsed_global_opts.has_option("dry-run"));
+
+    // check that command is there
+
+    BOOST_CHECK(get_opts.has_args());
+    BOOST_CHECK_EQUAL(get_opts.pop_arg(), "create");
+
+    // parse create options
+
+    ParsedOpts parsed_create_opts = get_opts.parse("create", create_opts);
+
+    BOOST_CHECK(parsed_create_opts.has_option("type"));
+
+    ParsedOpts::const_iterator it = parsed_create_opts.find("type");
+    BOOST_CHECK(it != parsed_create_opts.end());
+    BOOST_CHECK(it->second == "pre");
+
+    // check that no further command is there
+
+    BOOST_CHECK(!get_opts.has_args());
+
+    BOOST_CHECK(args.all_used());
+}
+
+
+BOOST_AUTO_TEST_CASE(good3)
+{
+    Args args({ "getopt", "create", "-tpre" });
+    GetOpts get_opts(args.argc(), args.argv());
+
+    // parse global options
+
+    ParsedOpts parsed_global_opts = get_opts.parse(global_opts);
+
+    // check that command is there
+
+    BOOST_CHECK(get_opts.has_args());
+    BOOST_CHECK_EQUAL(get_opts.pop_arg(), "create");
+
+    // parse create options
+
+    ParsedOpts parsed_create_opts = get_opts.parse("create", create_opts);
+
+    BOOST_CHECK(parsed_create_opts.has_option("type"));
+
+    ParsedOpts::const_iterator it = parsed_create_opts.find("type");
+    BOOST_CHECK(it != parsed_create_opts.end());
+    BOOST_CHECK(it->second == "pre");
+
+    // check that no further command is there
+
+    BOOST_CHECK(!get_opts.has_args());
+
+    BOOST_CHECK(args.all_used());
+}
+
+
+BOOST_AUTO_TEST_CASE(error1)
+{
+    Args args({ "getopt", "--table-style" });
+    GetOpts get_opts(args.argc(), args.argv());
+
+    BOOST_CHECK_EXCEPTION(get_opts.parse(global_opts), OptionsException, [](const exception& e) {
+       return strcmp(e.what(), "Missing argument for global option '--table-style'.") == 0;
+    });
+}
+
+
+BOOST_AUTO_TEST_CASE(error2)
+{
+    Args args({ "getopt", "create", "--type" });
+    GetOpts get_opts(args.argc(), args.argv());
+
+    get_opts.parse(global_opts);
+    get_opts.pop_arg();
+
+    BOOST_CHECK_EXCEPTION(get_opts.parse("create", create_opts), OptionsException, [](const exception& e) {
+       return strcmp(e.what(), "Missing argument for command option '--type'.") == 0;
+    });
+}
+
+
+BOOST_AUTO_TEST_CASE(error3)
+{
+    Args args({ "getopt", "--read-my-mind" });
+    GetOpts get_opts(args.argc(), args.argv());
+
+    BOOST_CHECK_EXCEPTION(get_opts.parse(global_opts), OptionsException, [](const exception& e) {
+       return strcmp(e.what(), "Unknown global option '--read-my-mind'.") == 0;
+    });
+}
+
+
+BOOST_AUTO_TEST_CASE(error4)
+{
+    Args args({ "getopt", "create", "--read-my-mind" });
+    GetOpts get_opts(args.argc(), args.argv());
+
+    get_opts.parse(global_opts);
+    get_opts.pop_arg();
+
+    BOOST_CHECK_EXCEPTION(get_opts.parse("create", create_opts), OptionsException, [](const exception& e) {
+       return strcmp(e.what(), "Unknown option '--read-my-mind' for command 'create'.") == 0;
+    });
+}
+
+
+BOOST_AUTO_TEST_CASE(error5)
+{
+    Args args({ "getopt", "create", "-px" });
+    GetOpts get_opts(args.argc(), args.argv());
+
+    get_opts.parse(global_opts);
+    get_opts.pop_arg();
+
+    BOOST_CHECK_EXCEPTION(get_opts.parse("create", create_opts), OptionsException, [](const exception& e) {
+       return strcmp(e.what(), "Unknown option '-x' for command 'create'.") == 0;
+    });
+}
+
+
+BOOST_AUTO_TEST_CASE(error6)
+{
+    Args args({ "getopt", "create", "-pt" });
+    GetOpts get_opts(args.argc(), args.argv());
+
+    get_opts.parse(global_opts);
+    get_opts.pop_arg();
+
+    BOOST_CHECK_EXCEPTION(get_opts.parse("create", create_opts), OptionsException, [](const exception& e) {
+       return strcmp(e.what(), "Missing argument for command option '--type'.") == 0;
+    });
+}