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
must_arg(tOptions * opts,char * arg_txt,tOptState * pOS,char ** opt_txt,uint32_t * opt_idx)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
maybe_arg(tOptions * opts,char * arg_txt,tOptState * pOS,char ** opt_txt,uint32_t * opt_idx)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
short_opt_ck(tOptions * opts,char * arg_txt,tOptState * pOS,char ** opt_txt,uint32_t * opt_idx)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
optionSort(tOptions * opts)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