1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Implementation of cmd-csop.h.
3 *@ TODO - better commandline parser that can dive into subcommands could
4 *@ TODO get rid of a lot of ERR_SYNOPSIS cruft.
5 *@ TODO - _CSOP -> _CCSOP
6 *
7 * Copyright (c) 2017 - 2020 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
8 * SPDX-License-Identifier: ISC
9 *
10 * Permission to use, copy, modify, and/or distribute this software for any
11 * purpose with or without fee is hereby granted, provided that the above
12 * copyright notice and this permission notice appear in all copies.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
15 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
16 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
17 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
18 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
19 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
20 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
21 */
22 #undef su_FILE
23 #define su_FILE cmd_csop
24 #define mx_SOURCE
25 #define mx_SOURCE_CMD_CSOP
26
27 #ifndef mx_HAVE_AMALGAMATION
28 # include "mx/nail.h"
29 #endif
30
31 su_EMPTY_FILE()
32 #ifdef mx_HAVE_CMD_CSOP
33
34 #include <su/cs.h>
35 #include <su/icodec.h>
36 #include <su/mem.h>
37
38 #include "mx/cmd.h"
39
40 #include "mx/cmd-csop.h"
41 #include "su/code-in.h"
42
43 enum a_csop_cmd{
44 a_CSOP_CMD_LENGTH,
45 a_CSOP_CMD_HASH32,
46 a_CSOP_CMD_HASH,
47 a_CSOP_CMD_FIND,
48 a_CSOP_CMD_IFIND, /* v15compat */
49 a_CSOP_CMD_SUBSTRING,
50 a_CSOP_CMD_TRIM,
51 a_CSOP_CMD_TRIM_FRONT,
52 a_CSOP_CMD_TRIM_END,
53 a_CSOP_CMD__MAX
54 };
55
56 enum a_csop_err{
57 a_CSOP_ERR_NONE,
58 a_CSOP_ERR_SYNOPSIS,
59 a_CSOP_ERR_SUBCMD,
60 a_CSOP_ERR_MOD_NOT_ALLOWED,
61 a_CSOP_ERR_NUM_RANGE,
62 a_CSOP_ERR_STR_OVERFLOW,
63 a_CSOP_ERR_STR_NODATA,
64 a_CSOP_ERR_STR_GENERIC
65 };
66 enum {a_CSOP_ERR__MAX = a_CSOP_ERR_STR_GENERIC};
67
68 CTA(S(uz,a_CSOP_CMD__MAX | a_CSOP_ERR__MAX) <= 0x7Fu, "Bit range excess");
69
70 enum a_csop_flags{
71 a_CSOP_NONE,
72 a_CSOP_ERR = 1u<<0, /* There was an error */
73 a_CSOP_SOFTOVERFLOW = 1u<<1,
74 a_CSOP_ISNUM = 1u<<2,
75 a_CSOP_ISDECIMAL = 1u<<3, /* Print only decimal result */
76 a_CSOP_MOD_CASE = 1u<<4, /* Case-insensitive / XY */
77 a_CSOP_MOD_MASK = a_CSOP_MOD_CASE,
78
79 a_CSOP__FMASK = 0x1FFu,
80 a_CSOP__FSHIFT = 9u,
81 a_CSOP__FCMDMASK = 0xFE00u,
82 a_CSOP__TMP = 1u<<30
83 };
84 /* .csc_cmderr=8-bit, and so a_csop_subcmd can store CMD+MOD flags in 16-bit */
85 CTA(((S(u32,a_CSOP_CMD__MAX | a_CSOP_ERR__MAX) << a_CSOP__FSHIFT) &
86 ~a_CSOP__FCMDMASK) == 0, "Bit ranges overlap");
87
88 struct a_csop_ctx{
89 u32 csc_flags;
90 u8 csc_cmderr; /* On input, a_vexpr_cmd, on output (maybe) a_vexpr_err */
91 u8 csc__pad[3];
92 char const **csc_argv;
93 char const *csc_cmd_name;
94 char const *csc_varname; /* VPUT support */
95 char const *csc_varres;
96 char const *csc_arg; /* The current arg (_ERR: which caused failure) */
97 s64 csc_lhv;
98 char csc_iencbuf[2+1/* BASE# prefix*/ + su_IENC_BUFFER_SIZE + 1];
99 };
100
101 struct a_csop_subcmd{
102 u16 css_mpv;
103 char css_name[14];
104 };
105
106 static struct a_csop_subcmd const a_csop_subcmds[] = {
107 #undef a_X
108 #define a_X(C,F) (S(u16,C) << a_CSOP__FSHIFT) | F
109
110 {a_X(a_CSOP_CMD_LENGTH, 0), "length"},
111 {a_X(a_CSOP_CMD_HASH, a_CSOP_MOD_CASE), "hash"},
112 {a_X(a_CSOP_CMD_HASH32, a_CSOP_MOD_CASE), "hash32"},
113 {a_X(a_CSOP_CMD_FIND, a_CSOP_MOD_CASE), "find"},
114 {a_X(a_CSOP_CMD_IFIND, 0), "ifind"},
115 {a_X(a_CSOP_CMD_SUBSTRING, 0), "substring"},
116 {a_X(a_CSOP_CMD_TRIM, 0), "trim"},
117 {a_X(a_CSOP_CMD_TRIM_FRONT, 0), "trim-front\0"},
118 {a_X(a_CSOP_CMD_TRIM_END, 0), "trim-end"}
119
120 #undef a_X
121 };
122
123 /* Entered with .vc_flags=NONE(|MOD_CASE)? */
124 static void a_csop_cmd(struct a_csop_ctx *cscp);
125
126 static void
a_csop_cmd(struct a_csop_ctx * cscp)127 a_csop_cmd(struct a_csop_ctx *cscp){
128 uz i;
129 NYD2_IN;
130
131 switch(cscp->csc_cmderr){
132 default:
133 case a_CSOP_CMD_LENGTH:
134 cscp->csc_flags |= a_CSOP_ISNUM | a_CSOP_ISDECIMAL;
135 if(cscp->csc_argv[0] == NIL || cscp->csc_argv[1] != NIL){
136 cscp->csc_flags |= a_CSOP_ERR;
137 cscp->csc_cmderr = a_CSOP_ERR_SYNOPSIS;
138 break;
139 }
140 cscp->csc_arg = cscp->csc_argv[0];
141
142 i = su_cs_len(cscp->csc_arg);
143 if(UCMP(64, i, >, S64_MAX)){
144 cscp->csc_flags |= a_CSOP_ERR;
145 cscp->csc_cmderr = a_CSOP_ERR_STR_OVERFLOW;
146 break;
147 }
148 cscp->csc_lhv = S(s64,i);
149 break;
150
151 case a_CSOP_CMD_HASH32:
152 case a_CSOP_CMD_HASH:
153 cscp->csc_flags |= a_CSOP_ISNUM | a_CSOP_ISDECIMAL;
154 if(cscp->csc_argv[0] == NIL || cscp->csc_argv[1] != NIL){
155 cscp->csc_flags |= a_CSOP_ERR;
156 cscp->csc_cmderr = a_CSOP_ERR_SYNOPSIS;
157 break;
158 }
159 cscp->csc_arg = cscp->csc_argv[0];
160
161 i = ((cscp->csc_flags & a_CSOP_MOD_CASE)
162 ? su_cs_toolbox_case.tb_hash : su_cs_toolbox.tb_hash
163 )(cscp->csc_arg);
164 if(cscp->csc_cmderr == a_CSOP_CMD_HASH32)
165 i = S(u32,i & U32_MAX);
166 cscp->csc_lhv = S(s64,i);
167 break;
168
169 case a_CSOP_CMD_IFIND:
170 n_OBSOLETE(_("csop: ifind: simply use find?[case] instead, please"));
171 cscp->csc_flags |= a_CSOP_MOD_CASE;
172 /* FALLTHRU */
173 case a_CSOP_CMD_FIND:
174 cscp->csc_flags |= a_CSOP_ISNUM | a_CSOP_ISDECIMAL;
175 if(cscp->csc_argv[0] == NIL || cscp->csc_argv[1] == NIL ||
176 cscp->csc_argv[2] != NIL){
177 cscp->csc_flags |= a_CSOP_ERR;
178 cscp->csc_cmderr = a_CSOP_ERR_SYNOPSIS;
179 break;
180 }
181 cscp->csc_arg = cscp->csc_argv[1];
182
183 cscp->csc_varres = ((cscp->csc_flags & a_CSOP_MOD_CASE)
184 ? su_cs_find_case : su_cs_find)(cscp->csc_argv[0], cscp->csc_arg);
185 if(cscp->csc_varres == NIL){
186 cscp->csc_flags |= a_CSOP_ERR;
187 cscp->csc_cmderr = a_CSOP_ERR_STR_NODATA;
188 break;
189 }
190 i = P2UZ(cscp->csc_varres - cscp->csc_argv[0]);
191 if(UCMP(64, i, >, S64_MAX)){
192 cscp->csc_flags |= a_CSOP_ERR;
193 cscp->csc_cmderr = a_CSOP_ERR_STR_OVERFLOW;
194 break;
195 }
196 cscp->csc_lhv = S(s64,i);
197 break;
198
199 case a_CSOP_CMD_SUBSTRING:
200 if(cscp->csc_argv[0] == NIL || (cscp->csc_argv[1] != NIL &&
201 (cscp->csc_argv[2] != NIL && cscp->csc_argv[3] != NIL))){
202 cscp->csc_flags |= a_CSOP_ERR;
203 cscp->csc_cmderr = a_CSOP_ERR_SYNOPSIS;
204 break;
205 }
206 cscp->csc_varres =
207 cscp->csc_arg = cscp->csc_argv[0];
208
209 i = su_cs_len(cscp->csc_arg);
210
211 /* Offset */
212 if(cscp->csc_argv[1] == NIL || cscp->csc_argv[1][0] == '\0')
213 cscp->csc_lhv = 0;
214 else if((su_idec_s64_cp(&cscp->csc_lhv, cscp->csc_argv[1], 0, NIL
215 ) & (su_IDEC_STATE_EMASK | su_IDEC_STATE_CONSUMED)
216 ) != su_IDEC_STATE_CONSUMED){
217 cscp->csc_flags |= a_CSOP_ERR;
218 cscp->csc_cmderr = a_CSOP_ERR_NUM_RANGE;
219 break;
220 }else if(cscp->csc_lhv < 0){
221 if(UCMP(64, i, <, -cscp->csc_lhv)){
222 cscp->csc_flags |= a_CSOP_ERR;
223 cscp->csc_cmderr = a_CSOP_ERR_NUM_RANGE;
224 goto jesubstring_off;
225 }
226 cscp->csc_lhv += i;
227 }
228
229 if(LIKELY(UCMP(64, i, >=, cscp->csc_lhv))){
230 i -= cscp->csc_lhv;
231 cscp->csc_varres += cscp->csc_lhv;
232 }else{
233 jesubstring_off:
234 if(n_poption & n_PO_D_V)
235 n_err(_("vexpr: substring: offset argument too large: %s\n"),
236 n_shexp_quote_cp(cscp->csc_arg, FAL0));
237 cscp->csc_flags |= a_CSOP_SOFTOVERFLOW;
238 }
239
240 /* Length */
241 if(cscp->csc_argv[2] != NIL){
242 if(cscp->csc_argv[2][0] == '\0')
243 cscp->csc_lhv = 0;
244 else if((su_idec_s64_cp(&cscp->csc_lhv, cscp->csc_argv[2], 0, NIL
245 ) & (su_IDEC_STATE_EMASK | su_IDEC_STATE_CONSUMED)
246 ) != su_IDEC_STATE_CONSUMED){
247 cscp->csc_flags |= a_CSOP_ERR;
248 cscp->csc_cmderr = a_CSOP_ERR_NUM_RANGE;
249 break;
250 }else if(cscp->csc_lhv < 0){
251 if(UCMP(64, i, <, -cscp->csc_lhv)){
252 goto jesubstring_len;
253 }
254 cscp->csc_lhv += i;
255 }
256
257 if(UCMP(64, i, >=, cscp->csc_lhv)){
258 if(UCMP(64, i, !=, cscp->csc_lhv))
259 cscp->csc_varres =
260 savestrbuf(cscp->csc_varres, S(uz,cscp->csc_lhv));
261 }else{
262 jesubstring_len:
263 if(n_poption & n_PO_D_V)
264 n_err(_("vexpr: substring: length argument too large: %s\n"),
265 n_shexp_quote_cp(cscp->csc_arg, FAL0));
266 cscp->csc_flags |= a_CSOP_SOFTOVERFLOW;
267 }
268 }
269 break;
270
271 case a_CSOP_CMD_TRIM:{
272 struct str trim;
273 enum n_str_trim_flags stf;
274
275 stf = n_STR_TRIM_BOTH;
276 if(0){
277 case a_CSOP_CMD_TRIM_FRONT:
278 stf = n_STR_TRIM_FRONT;
279 }else if(0){
280 case a_CSOP_CMD_TRIM_END:
281 stf = n_STR_TRIM_END;
282 }
283
284 if(cscp->csc_argv[0] == NIL || cscp->csc_argv[1] != NIL){
285 cscp->csc_flags |= a_CSOP_ERR;
286 cscp->csc_cmderr = a_CSOP_ERR_SYNOPSIS;
287 break;
288 }
289 cscp->csc_arg = cscp->csc_argv[0];
290
291 trim.l = su_cs_len(trim.s = UNCONST(char*,cscp->csc_arg));
292 (void)n_str_trim(&trim, stf);
293 cscp->csc_varres = savestrbuf(trim.s, trim.l);
294 }break;
295 }
296
297 NYD2_OU;
298 }
299
300 int
c_csop(void * vp)301 c_csop(void *vp){
302 struct a_csop_ctx csc;
303 char const *cp;
304 u32 f;
305 uz i, j;
306 NYD_IN;
307
308 DVL( su_mem_set(&csc, 0xAA, sizeof csc); )
309 csc.csc_flags = a_CSOP_ERR;
310 csc.csc_cmderr = a_CSOP_ERR_SUBCMD;
311 csc.csc_argv = S(char const**,vp);
312 csc.csc_varname = (n_pstate & n_PS_ARGMOD_VPUT) ? *csc.csc_argv++ : NIL;
313 csc.csc_arg =
314 csc.csc_cmd_name = *csc.csc_argv++;
315 csc.csc_varres = su_empty;
316
317 if((cp = su_cs_find_c(csc.csc_cmd_name, '?')) != NIL){
318 j = P2UZ(cp - csc.csc_cmd_name);
319 if(cp[1] != '\0' && !su_cs_starts_with_case("case", &cp[1])){
320 n_err(_("csop: invalid modifier used: %s\n"),
321 n_shexp_quote_cp(csc.csc_cmd_name, FAL0));
322 f = a_CSOP_ERR;
323 goto jleave;
324 }
325 f = a_CSOP_MOD_CASE;
326 }else{
327 f = a_CSOP_NONE;
328 if(*csc.csc_cmd_name == '@'){ /* v15compat */
329 n_OBSOLETE2(_("csop: please use ? modifier suffix, "
330 "not @ prefix"), n_shexp_quote_cp(csc.csc_cmd_name, FAL0));
331 ++csc.csc_cmd_name;
332 f = a_CSOP_MOD_CASE;
333 }
334 j = su_cs_len(csc.csc_cmd_name);
335 }
336
337 for(i = 0; i < NELEM(a_csop_subcmds); ++i){
338 if(su_cs_starts_with_case_n(a_csop_subcmds[i].css_name,
339 csc.csc_cmd_name, j)){
340 csc.csc_cmd_name = a_csop_subcmds[i].css_name;
341 i = a_csop_subcmds[i].css_mpv;
342
343 if(UNLIKELY(f & a_CSOP_MOD_MASK)){
344 /*u32 f2;
345
346 f2 = f & a_CSOP_MOD_MASK;*/
347
348 if(UNLIKELY(!(i & a_CSOP_MOD_MASK))){
349 csc.csc_cmderr = a_CSOP_ERR_MOD_NOT_ALLOWED;
350 break;
351 /*
352 }else if(UNLIKELY(f2 != a_CSOP_MOD_MASK &&
353 f2 != (i & a_CSOP_MOD_MASK))){
354 csc.csc_cmderr = a_CSOP_ERR_MOD_NOT_SUPPORTED;
355 break;
356 */
357 }
358 }
359
360 csc.csc_arg = csc.csc_cmd_name;
361 csc.csc_flags = f;
362 i = (i & a_CSOP__FCMDMASK) >> a_CSOP__FSHIFT;
363 csc.csc_cmderr = S(u8,i);
364 a_csop_cmd(&csc);
365 break;
366 }
367 }
368 f = csc.csc_flags;
369
370 if(LIKELY(!(f & a_CSOP_ERR))){
371 n_pstate_err_no = (f & a_CSOP_SOFTOVERFLOW)
372 ? su_ERR_OVERFLOW : su_ERR_NONE;
373 }else switch(csc.csc_cmderr){
374 case a_CSOP_ERR_NONE:
375 ASSERT(0);
376 break;
377 case a_CSOP_ERR_SYNOPSIS:
378 mx_cmd_print_synopsis(mx_cmd_firstfit("csop"), NIL);
379 n_pstate_err_no = su_ERR_INVAL;
380 goto jenum;
381 case a_CSOP_ERR_SUBCMD:
382 n_err(_("csop: invalid subcommand: %s\n"),
383 n_shexp_quote_cp(csc.csc_arg, FAL0));
384 n_pstate_err_no = su_ERR_INVAL;
385 goto jenum;
386 case a_CSOP_ERR_MOD_NOT_ALLOWED:
387 n_err(_("csop: modifiers not allowed for subcommand: %s\n"),
388 n_shexp_quote_cp(csc.csc_arg, FAL0));
389 n_pstate_err_no = su_ERR_INVAL;
390 goto jenum;
391 case a_CSOP_ERR_NUM_RANGE:
392 n_err(_("csop: numeric argument invalid or out of range: %s\n"),
393 n_shexp_quote_cp(csc.csc_arg, FAL0));
394 n_pstate_err_no = su_ERR_RANGE;
395 goto jenum;
396 default:
397 jenum:
398 f = a_CSOP_ERR | a_CSOP_ISNUM;
399 csc.csc_lhv = -1;
400 break;
401 case a_CSOP_ERR_STR_OVERFLOW:
402 n_err(_("csop: string length or offset overflows datatype\n"));
403 n_pstate_err_no = su_ERR_OVERFLOW;
404 goto jestr;
405 case a_CSOP_ERR_STR_NODATA:
406 n_pstate_err_no = su_ERR_NODATA;
407 /* FALLTHRU*/
408 case a_CSOP_ERR_STR_GENERIC:
409 jestr:
410 csc.csc_varres = su_empty;
411 f = a_CSOP_ERR;
412 break;
413 }
414
415 /* Generate the variable value content.
416 * Anticipate in our handling below! (Avoid needless work) */
417 if(f & a_CSOP_ISNUM){
418 cp = su_ienc(csc.csc_iencbuf, csc.csc_lhv, 10, su_IENC_MODE_SIGNED_TYPE);
419 if(cp != NIL)
420 csc.csc_varres = cp;
421 else{
422 f |= a_CSOP_ERR;
423 csc.csc_varres = su_empty;
424 }
425 }
426
427 if(csc.csc_varname == NIL){
428 /* If there was no error and we are printing a numeric result, print some
429 * more bases for the fun of it */
430 if(csc.csc_varres != NIL &&
431 fprintf(n_stdout, "%s\n", csc.csc_varres) < 0){
432 n_pstate_err_no = su_err_no();
433 f |= a_CSOP_ERR;
434 }
435 }else if(!n_var_vset(csc.csc_varname, S(up,csc.csc_varres))){
436 n_pstate_err_no = su_ERR_NOTSUP;
437 f |= a_CSOP_ERR;
438 }
439
440 jleave:
441 NYD_OU;
442 return (f & a_CSOP_ERR) ? 1 : 0;
443 }
444
445 #include "su/code-ou.h"
446 #endif /* mx_HAVE_CMD_CSOP */
447 /* s-it-mode */
448