1 /*
2  * Copyright (C) 1996-2021 The Squid Software Foundation and contributors
3  *
4  * Squid software is distributed under GPLv2+ license and includes
5  * contributions from numerous individuals and organizations.
6  * Please see the COPYING and CONTRIBUTORS files for details.
7  */
8 
9 #include "squid.h"
10 
11 #include "CommandLine.h"
12 #include "sbuf/SBuf.h"
13 
14 static void
ResetGetopt(const bool allowStderrWarnings)15 ResetGetopt(const bool allowStderrWarnings)
16 {
17     opterr = allowStderrWarnings;
18     // Resetting optind to zero instead of conventional '1' has an
19     // advantage, since it also resets getopt(3) global state.
20     // getopt(3) always skips argv[0], even if optind is zero
21     optind = 0;
22 }
23 
CommandLine(int argC,char * argV[],const char * shortRules,const RawLongOption * longRules)24 CommandLine::CommandLine(int argC, char *argV[], const char *shortRules, const RawLongOption *longRules):
25     argv_(),
26     shortOptions_(shortRules ? xstrdup(shortRules) : ""),
27     longOptions_()
28 {
29     assert(argC > 0); // C++ main() requirement that makes our arg0() safe
30     assert(shortRules);
31 
32     /* copy argV items */
33     argv_.reserve(argC+1);
34     for (int i = 0; i < argC; ++i)
35         argv_.push_back(xstrdup(argV[i]));
36     argv_.push_back(nullptr); // POSIX argv "must be terminated by a null pointer"
37 
38     /* copy grammar rules for the long options */
39     if (longRules) {
40         for (auto longOption = longRules; longOption->name; ++longOption)
41             longOptions_.emplace_back(*longOption);
42         longOptions_.emplace_back();
43     }
44 }
45 
CommandLine(const CommandLine & them)46 CommandLine::CommandLine(const CommandLine &them):
47     CommandLine(them.argc(), them.argv(), them.shortOptions_, them.longOptions())
48 {
49 }
50 
51 CommandLine &
operator =(const CommandLine & them)52 CommandLine::operator =(const CommandLine &them)
53 {
54     // cannot just swap(*this, them): std::swap(T,T) may call this assignment op
55     CommandLine tmp(them);
56     std::swap(argv_, tmp.argv_);
57     std::swap(shortOptions_, tmp.shortOptions_);
58     std::swap(longOptions_, tmp.longOptions_);
59     return *this;
60 }
61 
~CommandLine()62 CommandLine::~CommandLine()
63 {
64     for (auto arg: argv_)
65         xfree(arg);
66 
67     xfree(shortOptions_);
68 }
69 
70 bool
hasOption(const int optIdToFind,const char ** optValue) const71 CommandLine::hasOption(const int optIdToFind, const char **optValue) const
72 {
73     ResetGetopt(false); // avoid duped warnings; forEachOption() will complain
74     int optId = 0;
75     while (nextOption(optId)) {
76         if (optId == optIdToFind) {
77             if (optValue) {
78                 // do not need to copy the optarg string because it is a pointer into the original
79                 // argv array (https://www.gnu.org/software/libc/manual/html_node/Using-Getopt.html)
80                 *optValue = optarg;
81             }
82             return true;
83         }
84     }
85     return false;
86 }
87 
88 void
forEachOption(Visitor visitor) const89 CommandLine::forEachOption(Visitor visitor) const
90 {
91     ResetGetopt(true);
92     int optId = 0;
93     while (nextOption(optId))
94         visitor(optId, optarg);
95 }
96 
97 /// extracts the next option (if any)
98 /// \returns whether the option was extracted
99 /// throws on unknown option or missing required argument
100 bool
nextOption(int & optId) const101 CommandLine::nextOption(int &optId) const
102 {
103     optId = getopt_long(argc(), argv(), shortOptions_, longOptions(), nullptr);
104     if ((optId == ':' && shortOptions_[0] == ':') || optId == '?') {
105         assert(optind > 0 && static_cast<unsigned int>(optind) < argv_.size());
106         SBuf errMsg;
107         errMsg.Printf("'%s': %s", argv_[optind - 1],  optId == '?' ?
108                       "unrecognized option or missing required argument" : "missing required argument");
109         throw TexcHere(errMsg);
110     }
111     return optId != -1;
112 }
113 
114 void
resetArg0(const char * programName)115 CommandLine::resetArg0(const char *programName)
116 {
117     assert(programName);
118     xfree(argv_[0]);
119     argv_[0] = xstrdup(programName);
120 }
121 
122 void
pushFrontOption(const char * name,const char * value)123 CommandLine::pushFrontOption(const char *name, const char *value)
124 {
125     assert(name);
126     argv_.insert(argv_.begin() + 1, xstrdup(name));
127     if (value)
128         argv_.insert(argv_.begin() + 2, xstrdup(value));
129 }
130 
LongOption()131 LongOption::LongOption() :
132     option({nullptr, 0, nullptr, 0})
133 {
134 }
135 
LongOption(const RawLongOption & opt)136 LongOption::LongOption(const RawLongOption &opt) :
137     option({nullptr, 0, nullptr, 0})
138 {
139     copy(opt);
140 }
141 
LongOption(const LongOption & opt)142 LongOption::LongOption(const LongOption &opt):
143     LongOption(static_cast<const RawLongOption &>(opt))
144 {
145 }
146 
~LongOption()147 LongOption::~LongOption()
148 {
149     xfree(name);
150 }
151 
152 LongOption &
operator =(const LongOption & opt)153 LongOption::operator =(const LongOption &opt)
154 {
155     if (this != &opt)
156         copy(static_cast<const RawLongOption &>(opt));
157     return *this;
158 }
159 
160 void
copy(const RawLongOption & opt)161 LongOption::copy(const RawLongOption &opt)
162 {
163     xfree(name);
164     name = opt.name ? xstrdup(opt.name) : nullptr;
165     has_arg = opt.has_arg;
166     flag = opt.flag;
167     val = opt.val;
168 }
169 
170