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