xref: /freebsd/contrib/ntp/sntp/libopts/sort.c (revision 2e3507c25e42292b45a5482e116d278f5515d04d)
1 
2 /*
3  * \file sort.c
4  *
5  *  This module implements argument sorting.
6  *
7  * @addtogroup autoopts
8  * @{
9  */
10 /*
11  *  This file is part of AutoOpts, a companion to AutoGen.
12  *  AutoOpts is free software.
13  *  AutoOpts is Copyright (C) 1992-2018 by Bruce Korb - all rights reserved
14  *
15  *  AutoOpts is available under any one of two licenses.  The license
16  *  in use must be one of these two and the choice is under the control
17  *  of the user of the license.
18  *
19  *   The GNU Lesser General Public License, version 3 or later
20  *      See the files "COPYING.lgplv3" and "COPYING.gplv3"
21  *
22  *   The Modified Berkeley Software Distribution License
23  *      See the file "COPYING.mbsd"
24  *
25  *  These files have the following sha256 sums:
26  *
27  *  8584710e9b04216a394078dc156b781d0b47e1729104d666658aecef8ee32e95  COPYING.gplv3
28  *  4379e7444a0e2ce2b12dd6f5a52a27a4d02d39d247901d3285c88cf0d37f477b  COPYING.lgplv3
29  *  13aa749a5b0a454917a944ed8fffc530b784f5ead522b1aacaf4ec8aa55a6239  COPYING.mbsd
30  */
31 
32 /*
33  *  "must_arg" and "maybe_arg" are really similar.  The biggest
34  *  difference is that "may" will consume the next argument only if it
35  *  does not start with a hyphen and "must" will consume it, hyphen or not.
36  */
37 static tSuccess
38 must_arg(tOptions * opts, char * arg_txt, tOptState * pOS,
39          char ** opt_txt, uint32_t * opt_idx)
40 {
41     /*
42      *  An option argument is required.  Long options can either have
43      *  a separate command line argument, or an argument attached by
44      *  the '=' character.  Figure out which.
45      */
46     switch (pOS->optType) {
47     case TOPT_SHORT:
48         /*
49          *  See if an arg string follows the flag character.  If not,
50          *  the next arg must be the option argument.
51          */
52         if (*arg_txt != NUL)
53             return SUCCESS;
54         break;
55 
56     case TOPT_LONG:
57         /*
58          *  See if an arg string has already been assigned (glued on
59          *  with an `=' character).  If not, the next is the opt arg.
60          */
61         if (pOS->pzOptArg != NULL)
62             return SUCCESS;
63         break;
64 
65     default:
66         return FAILURE;
67     }
68     if (opts->curOptIdx >= opts->origArgCt)
69         return FAILURE;
70 
71     opt_txt[ (*opt_idx)++ ] = opts->origArgVect[ (opts->curOptIdx)++ ];
72     return SUCCESS;
73 }
74 
75 static tSuccess
76 maybe_arg(tOptions * opts, char * arg_txt, tOptState * pOS,
77           char ** opt_txt, uint32_t * opt_idx)
78 {
79     /*
80      *  An option argument is optional.
81      */
82     switch (pOS->optType) {
83     case TOPT_SHORT:
84         /*
85          *  IF nothing is glued on after the current flag character,
86          *  THEN see if there is another argument.  If so and if it
87          *  does *NOT* start with a hyphen, then it is the option arg.
88          */
89         if (*arg_txt != NUL)
90             return SUCCESS;
91         break;
92 
93     case TOPT_LONG:
94         /*
95          *  Look for an argument if we don't already have one (glued on
96          *  with a `=' character)
97          */
98         if (pOS->pzOptArg != NULL)
99             return SUCCESS;
100         break;
101 
102     default:
103         return FAILURE;
104     }
105     if (opts->curOptIdx >= opts->origArgCt)
106         return PROBLEM;
107 
108     arg_txt = opts->origArgVect[ opts->curOptIdx ];
109     if (*arg_txt != '-')
110         opt_txt[ (*opt_idx)++ ] = opts->origArgVect[ (opts->curOptIdx)++ ];
111     return SUCCESS;
112 }
113 
114 /*
115  *  Process a string of short options glued together.  If the last one
116  *  does or may take an argument, the do the argument processing and leave.
117  */
118 static tSuccess
119 short_opt_ck(tOptions * opts, char * arg_txt, tOptState * pOS,
120              char ** opt_txt, uint32_t * opt_idx)
121 {
122     while (*arg_txt != NUL) {
123         if (FAILED(opt_find_short(opts, (uint8_t)*arg_txt, pOS)))
124             return FAILURE;
125 
126         /*
127          *  See if we can have an arg.
128          */
129         if (OPTST_GET_ARGTYPE(pOS->pOD->fOptState) == OPARG_TYPE_NONE) {
130             arg_txt++;
131 
132         } else if (pOS->pOD->fOptState & OPTST_ARG_OPTIONAL) {
133             /*
134              *  Take an argument if it is not attached and it does not
135              *  start with a hyphen.
136              */
137             if (arg_txt[1] != NUL)
138                 return SUCCESS;
139 
140             arg_txt = opts->origArgVect[ opts->curOptIdx ];
141             if (*arg_txt != '-')
142                 opt_txt[ (*opt_idx)++ ] =
143                     opts->origArgVect[ (opts->curOptIdx)++ ];
144             return SUCCESS;
145 
146         } else {
147             /*
148              *  IF we need another argument, be sure it is there and
149              *  take it.
150              */
151             if (arg_txt[1] == NUL) {
152                 if (opts->curOptIdx >= opts->origArgCt)
153                     return FAILURE;
154                 opt_txt[ (*opt_idx)++ ] =
155                     opts->origArgVect[ (opts->curOptIdx)++ ];
156             }
157             return SUCCESS;
158         }
159     }
160     return SUCCESS;
161 }
162 
163 /*
164  *  If the program wants sorted options (separated operands and options),
165  *  then this routine will to the trick.
166  */
167 static void
168 optionSort(tOptions * opts)
169 {
170     char **  opt_txt;
171     char **  ppzOpds;
172     uint32_t optsIdx = 0;
173     uint32_t opdsIdx = 0;
174 
175     tOptState os = OPTSTATE_INITIALIZER(DEFINED);
176 
177     /*
178      *  Disable for POSIX conformance, or if there are no operands.
179      */
180     if (  (getenv("POSIXLY_CORRECT") != NULL)
181        || NAMED_OPTS(opts))
182         return;
183 
184     /*
185      *  Make sure we can allocate two full-sized arg vectors.
186      */
187     opt_txt = malloc(opts->origArgCt * sizeof(char *));
188     if (opt_txt == NULL)
189         goto exit_no_mem;
190 
191     ppzOpds = malloc(opts->origArgCt * sizeof(char *));
192     if (ppzOpds == NULL) {
193         free(opt_txt);
194         goto exit_no_mem;
195     }
196 
197     opts->curOptIdx = 1;
198     opts->pzCurOpt  = NULL;
199 
200     /*
201      *  Now, process all the options from our current position onward.
202      *  (This allows interspersed options and arguments for the few
203      *  non-standard programs that require it.)
204      */
205     for (;;) {
206         char * arg_txt;
207         tSuccess res;
208 
209         /*
210          *  If we're out of arguments, we're done.  Join the option and
211          *  operand lists into the original argument vector.
212          */
213         if (opts->curOptIdx >= opts->origArgCt) {
214             errno = 0;
215             goto joinLists;
216         }
217 
218         arg_txt = opts->origArgVect[ opts->curOptIdx ];
219         if (*arg_txt != '-') {
220             ppzOpds[ opdsIdx++ ] = opts->origArgVect[ (opts->curOptIdx)++ ];
221             continue;
222         }
223 
224         switch (arg_txt[1]) {
225         case NUL:
226             /*
227              *  A single hyphen is an operand.
228              */
229             ppzOpds[ opdsIdx++ ] = opts->origArgVect[ (opts->curOptIdx)++ ];
230             continue;
231 
232         case '-':
233             /*
234              *  Two consecutive hypens.  Put them on the options list and then
235              *  _always_ force the remainder of the arguments to be operands.
236              */
237             if (arg_txt[2] == NUL) {
238                 opt_txt[ optsIdx++ ] =
239                     opts->origArgVect[ (opts->curOptIdx)++ ];
240                 goto restOperands;
241             }
242             res = opt_find_long(opts, arg_txt+2, &os);
243             break;
244 
245         default:
246             /*
247              *  If short options are not allowed, then do long
248              *  option processing.  Otherwise the character must be a
249              *  short (i.e. single character) option.
250              */
251             if ((opts->fOptSet & OPTPROC_SHORTOPT) == 0) {
252                 res = opt_find_long(opts, arg_txt+1, &os);
253             } else {
254                 res = opt_find_short(opts, (uint8_t)arg_txt[1], &os);
255             }
256             break;
257         }
258         if (FAILED(res)) {
259             errno = EINVAL;
260             goto freeTemps;
261         }
262 
263         /*
264          *  We've found an option.  Add the argument to the option list.
265          *  Next, we have to see if we need to pull another argument to be
266          *  used as the option argument.
267          */
268         opt_txt[ optsIdx++ ] = opts->origArgVect[ (opts->curOptIdx)++ ];
269 
270         if (OPTST_GET_ARGTYPE(os.pOD->fOptState) == OPARG_TYPE_NONE) {
271             /*
272              *  No option argument.  If we have a short option here,
273              *  then scan for short options until we get to the end
274              *  of the argument string.
275              */
276             if (  (os.optType == TOPT_SHORT)
277                && FAILED(short_opt_ck(opts, arg_txt+2, &os, opt_txt,
278                                       &optsIdx)) )  {
279                 errno = EINVAL;
280                 goto freeTemps;
281             }
282 
283         } else if (os.pOD->fOptState & OPTST_ARG_OPTIONAL) {
284             switch (maybe_arg(opts, arg_txt+2, &os, opt_txt, &optsIdx)) {
285             case FAILURE: errno = EIO; goto freeTemps;
286             case PROBLEM: errno = 0;   goto joinLists;
287             }
288 
289         } else {
290             switch (must_arg(opts, arg_txt+2, &os, opt_txt, &optsIdx)) {
291             case PROBLEM:
292             case FAILURE: errno = EIO; goto freeTemps;
293             }
294         }
295     } /* for (;;) */
296 
297  restOperands:
298     while (opts->curOptIdx < opts->origArgCt)
299         ppzOpds[ opdsIdx++ ] = opts->origArgVect[ (opts->curOptIdx)++ ];
300 
301  joinLists:
302     if (optsIdx > 0)
303         memcpy(opts->origArgVect + 1, opt_txt,
304                (size_t)optsIdx * sizeof(char *));
305     if (opdsIdx > 0)
306         memcpy(opts->origArgVect + 1 + optsIdx, ppzOpds,
307                (size_t)opdsIdx * sizeof(char *));
308 
309  freeTemps:
310     free(opt_txt);
311     free(ppzOpds);
312     return;
313 
314  exit_no_mem:
315     errno = ENOMEM;
316     return;
317 }
318 
319 /** @}
320  *
321  * Local Variables:
322  * mode: C
323  * c-file-style: "stroustrup"
324  * indent-tabs-mode: nil
325  * End:
326  * end of autoopts/sort.c */
327