]> git.saurik.com Git - apt.git/blame - apt-pkg/contrib/cmndline.cc
testcases runable as root
[apt.git] / apt-pkg / contrib / cmndline.cc
CommitLineData
08e8f724
AL
1// -*- mode: cpp; mode: fold -*-
2// Description /*{{{*/
bac2e715 3// $Id: cmndline.cc,v 1.15 2003/02/10 01:40:58 doogie Exp $
08e8f724
AL
4/* ######################################################################
5
6 Command Line Class - Sophisticated command line parser
7
7da2b375
AL
8 This source is placed in the Public Domain, do with it what you will
9 It was originally written by Jason Gunthorpe <jgg@debian.org>.
10
08e8f724
AL
11 ##################################################################### */
12 /*}}}*/
13// Include files /*{{{*/
ea542140
DK
14#include<config.h>
15
472ff00e 16#include <apt-pkg/configuration.h>
08e8f724
AL
17#include <apt-pkg/cmndline.h>
18#include <apt-pkg/error.h>
cdcc6d34 19#include <apt-pkg/strutl.h>
b2e465d6 20
453b82a3
DK
21#include <stddef.h>
22#include <stdlib.h>
23#include <string.h>
24#include <string>
25
ea542140 26#include <apti18n.h>
08e8f724 27 /*}}}*/
584e4558 28using namespace std;
08e8f724
AL
29
30// CommandLine::CommandLine - Constructor /*{{{*/
31// ---------------------------------------------------------------------
32/* */
33CommandLine::CommandLine(Args *AList,Configuration *Conf) : ArgList(AList),
34 Conf(Conf), FileList(0)
35{
e1b74f61
AL
36}
37 /*}}}*/
38// CommandLine::~CommandLine - Destructor /*{{{*/
39// ---------------------------------------------------------------------
40/* */
41CommandLine::~CommandLine()
42{
43 delete [] FileList;
08e8f724
AL
44}
45 /*}}}*/
b9179170
MV
46// CommandLine::GetCommand - return the first non-option word /*{{{*/
47char const * CommandLine::GetCommand(Dispatch const * const Map,
48 unsigned int const argc, char const * const * const argv)
49{
c4b91cbe
DK
50 // if there is a -- on the line there must be the word we search for either
51 // before it (as -- marks the end of the options) or right after it (as we can't
52 // decide if the command is actually an option, given that in theory, you could
53 // have parameters named like commands)
b9179170
MV
54 for (size_t i = 1; i < argc; ++i)
55 {
56 if (strcmp(argv[i], "--") != 0)
57 continue;
c4b91cbe
DK
58 // check if command is before --
59 for (size_t k = 1; k < i; ++k)
b9179170 60 for (size_t j = 0; Map[j].Match != NULL; ++j)
c4b91cbe 61 if (strcmp(argv[k], Map[j].Match) == 0)
b9179170 62 return Map[j].Match;
c4b91cbe
DK
63 // see if the next token after -- is the command
64 ++i;
65 if (i < argc)
b9179170
MV
66 for (size_t j = 0; Map[j].Match != NULL; ++j)
67 if (strcmp(argv[i], Map[j].Match) == 0)
68 return Map[j].Match;
c4b91cbe 69 // we found a --, but not a command
b9179170
MV
70 return NULL;
71 }
72 // no --, so search for the first word matching a command
73 // FIXME: How like is it that an option parameter will be also a valid Match ?
74 for (size_t i = 1; i < argc; ++i)
75 {
76 if (*(argv[i]) == '-')
77 continue;
78 for (size_t j = 0; Map[j].Match != NULL; ++j)
79 if (strcmp(argv[i], Map[j].Match) == 0)
80 return Map[j].Match;
81 }
82 return NULL;
83}
84 /*}}}*/
08e8f724
AL
85// CommandLine::Parse - Main action member /*{{{*/
86// ---------------------------------------------------------------------
87/* */
88bool CommandLine::Parse(int argc,const char **argv)
89{
e1b74f61 90 delete [] FileList;
08e8f724
AL
91 FileList = new const char *[argc];
92 const char **Files = FileList;
93 int I;
94 for (I = 1; I != argc; I++)
95 {
96 const char *Opt = argv[I];
97
98 // It is not an option
99 if (*Opt != '-')
100 {
101 *Files++ = Opt;
102 continue;
103 }
104
105 Opt++;
106
107 // Double dash signifies the end of option processing
108 if (*Opt == '-' && Opt[1] == 0)
343bd48e
AL
109 {
110 I++;
08e8f724 111 break;
343bd48e 112 }
08e8f724
AL
113
114 // Single dash is a short option
115 if (*Opt != '-')
116 {
117 // Iterate over each letter
118 while (*Opt != 0)
119 {
120 // Search for the option
121 Args *A;
122 for (A = ArgList; A->end() == false && A->ShortOpt != *Opt; A++);
123 if (A->end() == true)
b2e465d6 124 return _error->Error(_("Command line option '%c' [from %s] is not known."),*Opt,argv[I]);
08e8f724
AL
125
126 if (HandleOpt(I,argc,argv,Opt,A) == false)
127 return false;
128 if (*Opt != 0)
129 Opt++;
130 }
131 continue;
132 }
133
134 Opt++;
135
136 // Match up to a = against the list
08e8f724 137 Args *A;
404528bd 138 const char *OptEnd = strchrnul(Opt, '=');
ae2be086
DH
139 for (A = ArgList; A->end() == false &&
140 (A->LongOpt == 0 || stringcasecmp(Opt,OptEnd,A->LongOpt) != 0);
141 ++A);
08e8f724
AL
142
143 // Failed, look for a word after the first - (no-foo)
0d47bd08 144 bool PreceedMatch = false;
08e8f724
AL
145 if (A->end() == true)
146 {
404528bd
DK
147 Opt = (const char*) memchr(Opt, '-', OptEnd - Opt);
148 if (Opt == NULL)
b2e465d6 149 return _error->Error(_("Command line option %s is not understood"),argv[I]);
08e8f724
AL
150 Opt++;
151
152 for (A = ArgList; A->end() == false &&
7a6d9076
DK
153 (A->LongOpt == 0 || stringcasecmp(Opt,OptEnd,A->LongOpt) != 0);
154 ++A);
08e8f724
AL
155
156 // Failed again..
157 if (A->end() == true && OptEnd - Opt != 1)
b2e465d6 158 return _error->Error(_("Command line option %s is not understood"),argv[I]);
0d47bd08 159
08e8f724 160 // The option could be a single letter option prefixed by a no-..
08e8f724 161 if (A->end() == true)
0d47bd08
AL
162 {
163 for (A = ArgList; A->end() == false && A->ShortOpt != *Opt; A++);
164
165 if (A->end() == true)
b2e465d6 166 return _error->Error(_("Command line option %s is not understood"),argv[I]);
0d47bd08 167 }
e1b74f61
AL
168
169 // The option is not boolean
170 if (A->IsBoolean() == false)
b2e465d6 171 return _error->Error(_("Command line option %s is not boolean"),argv[I]);
0d47bd08 172 PreceedMatch = true;
08e8f724
AL
173 }
174
175 // Deal with it.
176 OptEnd--;
0d47bd08 177 if (HandleOpt(I,argc,argv,OptEnd,A,PreceedMatch) == false)
08e8f724
AL
178 return false;
179 }
180
181 // Copy any remaining file names over
182 for (; I != argc; I++)
183 *Files++ = argv[I];
184 *Files = 0;
2bb25574
DK
185
186 SaveInConfig(argc, argv);
187
08e8f724
AL
188 return true;
189}
190 /*}}}*/
191// CommandLine::HandleOpt - Handle a single option including all flags /*{{{*/
192// ---------------------------------------------------------------------
193/* This is a helper function for parser, it looks at a given argument
194 and looks for specific patterns in the string, it gets tokanized
195 -ruffly- like -*[yes|true|enable]-(o|longopt)[=][ ][argument] */
196bool CommandLine::HandleOpt(int &I,int argc,const char *argv[],
0d47bd08 197 const char *&Opt,Args *A,bool PreceedMatch)
08e8f724
AL
198{
199 const char *Argument = 0;
200 bool CertainArg = false;
201 int IncI = 0;
202
203 /* Determine the possible location of an option or 0 if their is
204 no option */
205 if (Opt[1] == 0 || (Opt[1] == '=' && Opt[2] == 0))
206 {
207 if (I + 1 < argc && argv[I+1][0] != '-')
208 Argument = argv[I+1];
209
210 // Equals was specified but we fell off the end!
211 if (Opt[1] == '=' && Argument == 0)
b2e465d6 212 return _error->Error(_("Option %s requires an argument."),argv[I]);
08e8f724
AL
213 if (Opt[1] == '=')
214 CertainArg = true;
215
216 IncI = 1;
217 }
218 else
219 {
220 if (Opt[1] == '=')
221 {
222 CertainArg = true;
223 Argument = Opt + 2;
224 }
225 else
226 Argument = Opt + 1;
227 }
0d47bd08 228
08e8f724
AL
229 // Option is an argument set
230 if ((A->Flags & HasArg) == HasArg)
231 {
232 if (Argument == 0)
b2e465d6 233 return _error->Error(_("Option %s requires an argument."),argv[I]);
08e8f724
AL
234 Opt += strlen(Opt);
235 I += IncI;
236
237 // Parse a configuration file
238 if ((A->Flags & ConfigFile) == ConfigFile)
239 return ReadConfigFile(*Conf,Argument);
e1b74f61 240
0da8987a 241 // Arbitrary item specification
e1b74f61
AL
242 if ((A->Flags & ArbItem) == ArbItem)
243 {
404528bd
DK
244 const char *J = strchr(Argument, '=');
245 if (J == NULL)
bac2e715 246 return _error->Error(_("Option %s: Configuration item specification must have an =<val>."),argv[I]);
e1b74f61 247
7e798dd7
AL
248 // = is trailing
249 if (J[1] == 0)
250 {
251 if (I+1 >= argc)
bac2e715 252 return _error->Error(_("Option %s: Configuration item specification must have an =<val>."),argv[I]);
7e798dd7
AL
253 Conf->Set(string(Argument,J-Argument),string(argv[I++ +1]));
254 }
255 else
256 Conf->Set(string(Argument,J-Argument),string(J+1));
e1b74f61
AL
257
258 return true;
259 }
08e8f724 260
404528bd 261 const char *I = strchrnul(A->ConfName, ' ');
7f25bdff
AL
262 if (*I == ' ')
263 Conf->Set(string(A->ConfName,0,I-A->ConfName),string(I+1) + Argument);
264 else
265 Conf->Set(A->ConfName,string(I) + Argument);
266
08e8f724
AL
267 return true;
268 }
269
270 // Option is an integer level
271 if ((A->Flags & IntLevel) == IntLevel)
272 {
273 // There might be an argument
274 if (Argument != 0)
275 {
276 char *EndPtr;
277 unsigned long Value = strtol(Argument,&EndPtr,10);
278
279 // Conversion failed and the argument was specified with an =s
280 if (EndPtr == Argument && CertainArg == true)
b2e465d6 281 return _error->Error(_("Option %s requires an integer argument, not '%s'"),argv[I],Argument);
08e8f724
AL
282
283 // Conversion was ok, set the value and return
9435cc9b 284 if (EndPtr != 0 && EndPtr != Argument && *EndPtr == 0)
08e8f724
AL
285 {
286 Conf->Set(A->ConfName,Value);
287 Opt += strlen(Opt);
288 I += IncI;
289 return true;
290 }
291 }
292
293 // Increase the level
294 Conf->Set(A->ConfName,Conf->FindI(A->ConfName)+1);
295 return true;
296 }
297
298 // Option is a boolean
299 int Sense = -1; // -1 is unspecified, 0 is yes 1 is no
300
301 // Look for an argument.
302 while (1)
303 {
1e3f4083 304 // Look at preceding text
08e8f724
AL
305 char Buffer[300];
306 if (Argument == 0)
307 {
0d47bd08
AL
308 if (PreceedMatch == false)
309 break;
310
08e8f724 311 if (strlen(argv[I]) >= sizeof(Buffer))
b2e465d6 312 return _error->Error(_("Option '%s' is too long"),argv[I]);
0d47bd08
AL
313
314 // Skip the leading dash
08e8f724
AL
315 const char *J = argv[I];
316 for (; *J != 0 && *J == '-'; J++);
404528bd
DK
317
318 const char *JEnd = strchr(J, '-');
319 if (JEnd != NULL)
08e8f724
AL
320 {
321 strncpy(Buffer,J,JEnd - J);
322 Buffer[JEnd - J] = 0;
323 Argument = Buffer;
324 CertainArg = true;
325 }
326 else
327 break;
328 }
329
3b5421b4
AL
330 // Check for boolean
331 Sense = StringToBool(Argument);
332 if (Sense >= 0)
08e8f724 333 {
08e8f724
AL
334 // Eat the argument
335 if (Argument != Buffer)
336 {
337 Opt += strlen(Opt);
338 I += IncI;
339 }
340 break;
341 }
342
08e8f724 343 if (CertainArg == true)
b2e465d6 344 return _error->Error(_("Sense %s is not understood, try true or false."),Argument);
08e8f724
AL
345
346 Argument = 0;
347 }
348
349 // Indeterminate sense depends on the flag
350 if (Sense == -1)
351 {
352 if ((A->Flags & InvBoolean) == InvBoolean)
353 Sense = 0;
354 else
355 Sense = 1;
356 }
357
358 Conf->Set(A->ConfName,Sense);
359 return true;
360}
361 /*}}}*/
bc4af0b9 362// CommandLine::FileSize - Count the number of filenames /*{{{*/
e1b74f61
AL
363// ---------------------------------------------------------------------
364/* */
365unsigned int CommandLine::FileSize() const
366{
367 unsigned int Count = 0;
368 for (const char **I = FileList; I != 0 && *I != 0; I++)
369 Count++;
370 return Count;
371}
372 /*}}}*/
bc4af0b9
AL
373// CommandLine::DispatchArg - Do something with the first arg /*{{{*/
374// ---------------------------------------------------------------------
375/* */
b0b4efb9 376bool CommandLine::DispatchArg(Dispatch *Map,bool NoMatch)
bc4af0b9
AL
377{
378 int I;
379 for (I = 0; Map[I].Match != 0; I++)
380 {
381 if (strcmp(FileList[0],Map[I].Match) == 0)
382 {
383 bool Res = Map[I].Handler(*this);
384 if (Res == false && _error->PendingError() == false)
385 _error->Error("Handler silently failed");
386 return Res;
387 }
388 }
389
390 // No matching name
391 if (Map[I].Match == 0)
b0b4efb9
AL
392 {
393 if (NoMatch == true)
b2e465d6 394 _error->Error(_("Invalid operation %s"),FileList[0]);
b0b4efb9
AL
395 }
396
bc4af0b9
AL
397 return false;
398}
399 /*}}}*/
2bb25574
DK
400// CommandLine::SaveInConfig - for output later in a logfile or so /*{{{*/
401// ---------------------------------------------------------------------
402/* We save the commandline here to have it around later for e.g. logging.
403 It feels a bit like a hack here and isn't bulletproof, but it is better
404 than nothing after all. */
405void CommandLine::SaveInConfig(unsigned int const &argc, char const * const * const argv)
406{
093e9f5d 407 char cmdline[100 + argc * 50];
70e0c168 408 memset(cmdline, 0, sizeof(cmdline));
2bb25574
DK
409 unsigned int length = 0;
410 bool lastWasOption = false;
411 bool closeQuote = false;
093e9f5d 412 for (unsigned int i = 0; i < argc && length < sizeof(cmdline); ++i, ++length)
2bb25574
DK
413 {
414 for (unsigned int j = 0; argv[i][j] != '\0' && length < sizeof(cmdline)-1; ++j, ++length)
415 {
416 cmdline[length] = argv[i][j];
417 if (lastWasOption == true && argv[i][j] == '=')
418 {
419 // That is possibly an option: Quote it if it includes spaces,
420 // the benefit is that this will eliminate also most false positives
404528bd
DK
421 const char* c = strchr(&argv[i][j+1], ' ');
422 if (c == NULL) continue;
2bb25574
DK
423 cmdline[++length] = '"';
424 closeQuote = true;
425 }
426 }
427 if (closeQuote == true)
428 cmdline[length++] = '"';
429 // Problem: detects also --hello
430 if (cmdline[length-1] == 'o')
431 lastWasOption = true;
432 cmdline[length] = ' ';
433 }
434 cmdline[--length] = '\0';
435 _config->Set("CommandLine::AsString", cmdline);
436}
437 /*}}}*/
b9179170
MV
438CommandLine::Args CommandLine::MakeArgs(char ShortOpt, char const *LongOpt, char const *ConfName, unsigned long Flags)/*{{{*/
439{
440 /* In theory, this should be a constructor for CommandLine::Args instead,
441 but this breaks compatibility as gcc thinks this is a c++11 initializer_list */
442 CommandLine::Args arg;
443 arg.ShortOpt = ShortOpt;
444 arg.LongOpt = LongOpt;
445 arg.ConfName = ConfName;
446 arg.Flags = Flags;
447 return arg;
448}
449 /*}}}*/