1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Auxiliary functions that don't fit anywhere else.
3 *
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2020 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
6 * SPDX-License-Identifier: BSD-3-Clause
7 */
8 /*
9 * Copyright (c) 1980, 1993
10 * The Regents of the University of California. All rights reserved.
11 *
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions
14 * are met:
15 * 1. Redistributions of source code must retain the above copyright
16 * notice, this list of conditions and the following disclaimer.
17 * 2. Redistributions in binary form must reproduce the above copyright
18 * notice, this list of conditions and the following disclaimer in the
19 * documentation and/or other materials provided with the distribution.
20 * 3. Neither the name of the University nor the names of its contributors
21 * may be used to endorse or promote products derived from this software
22 * without specific prior written permission.
23 *
24 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34 * SUCH DAMAGE.
35 */
36 #undef su_FILE
37 #define su_FILE auxlily
38 #define mx_SOURCE
39
40 #ifndef mx_HAVE_AMALGAMATION
41 # include "mx/nail.h"
42 #endif
43
44 #include <sys/utsname.h>
45
46 #ifdef mx_HAVE_NET
47 # ifdef mx_HAVE_GETADDRINFO
48 # include <sys/socket.h>
49 # endif
50
51 # include <netdb.h>
52 #endif
53
54 #ifdef mx_HAVE_IDNA
55 # if mx_HAVE_IDNA == n_IDNA_IMPL_LIBIDN2
56 # include <idn2.h>
57 # elif mx_HAVE_IDNA == n_IDNA_IMPL_LIBIDN
58 # include <idna.h>
59 # include <idn-free.h>
60 # elif mx_HAVE_IDNA == n_IDNA_IMPL_IDNKIT
61 # include <idn/api.h>
62 # endif
63 #endif
64
65 #include <su/cs.h>
66 #include <su/cs-dict.h>
67 #include <su/icodec.h>
68 #include <su/mem.h>
69 #include <su/sort.h>
70
71 #include "mx/child.h"
72 #include "mx/cmd-filetype.h"
73 #include "mx/colour.h"
74 #include "mx/file-streams.h"
75 #include "mx/termios.h"
76 #include "mx/tty.h"
77
78 #ifdef mx_HAVE_ERRORS
79 # include "mx/cmd.h"
80 #endif
81 #ifdef mx_HAVE_IDNA
82 # include "mx/iconv.h"
83 #endif
84
85 /* TODO fake */
86 #include "su/code-in.h"
87
88 /* The difference in between mx_HAVE_ERRORS and not, is size of queue only */
89 struct a_aux_err_node{
90 struct a_aux_err_node *ae_next;
91 u32 ae_cnt;
92 boole ae_done;
93 u8 ae_pad[3];
94 uz ae_dumped_till;
95 struct n_string ae_str;
96 };
97
98 /* Error ring, for `errors' */
99 static struct a_aux_err_node *a_aux_err_head;
100 static struct a_aux_err_node *a_aux_err_tail;
101
102 /* Get our $PAGER; if env_addon is not NULL it is checked whether we know about
103 * some environment variable that supports colour+ and set *env_addon to that,
104 * e.g., "LESS=FRSXi" */
105 static char const *a_aux_pager_get(char const **env_addon);
106
107 static char const *
a_aux_pager_get(char const ** env_addon)108 a_aux_pager_get(char const **env_addon){
109 char const *rv;
110 NYD_IN;
111
112 rv = ok_vlook(PAGER);
113
114 if(env_addon != NIL){
115 *env_addon = NIL;
116 /* Update the manual upon any changes:
117 * *colour-pager*, $PAGER */
118 if(su_cs_find(rv, "less") != NIL){
119 if(getenv("LESS") == NIL)
120 *env_addon = "LESS=RI";
121 }else if(su_cs_find(rv, "lv") != NIL){
122 if(getenv("LV") == NIL)
123 *env_addon = "LV=-c";
124 }
125 }
126 NYD_OU;
127 return rv;
128 }
129
130 FL uz
n_screensize(void)131 n_screensize(void){
132 char const *cp;
133 uz rv;
134 NYD2_IN;
135
136 if((cp = ok_vlook(screen)) != NIL){
137 su_idec_uz_cp(&rv, cp, 0, NIL);
138 if(rv == 0)
139 rv = mx_termios_dimen.tiosd_height;
140 }else
141 rv = mx_termios_dimen.tiosd_height;
142
143 if(rv > 2)
144 rv -= 2;
145 NYD2_OU;
146 return rv;
147 }
148
149 FL FILE *
mx_pager_open(void)150 mx_pager_open(void){
151 char const *env_add[2], *pager;
152 FILE *rv;
153 NYD_IN;
154
155 ASSERT(n_psonce & n_PSO_INTERACTIVE);
156
157 pager = a_aux_pager_get(env_add + 0);
158 env_add[1] = NIL;
159
160 if((rv = mx_fs_pipe_open(pager, "w", NIL, env_add, mx_CHILD_FD_PASS)
161 ) == NIL)
162 n_perr(pager, 0);
163 NYD_OU;
164 return rv;
165 }
166
167 FL boole
mx_pager_close(FILE * fp)168 mx_pager_close(FILE *fp){
169 boole rv;
170 NYD_IN;
171
172 rv = mx_fs_pipe_close(fp, TRU1);
173 NYD_OU;
174 return rv;
175 }
176
177 FL void
page_or_print(FILE * fp,uz lines)178 page_or_print(FILE *fp, uz lines)
179 {
180 int c;
181 char const *cp;
182 NYD_IN;
183
184 fflush_rewind(fp);
185
186 if (n_go_may_yield_control() && (cp = ok_vlook(crt)) != NULL) {
187 uz rows;
188
189 if(*cp == '\0')
190 rows = mx_termios_dimen.tiosd_height;
191 else
192 su_idec_uz_cp(&rows, cp, 0, NULL);
193 /* Avoid overflow later on */
194 if(rows == UZ_MAX)
195 --rows;
196
197 if (rows > 0 && lines == 0) {
198 while ((c = getc(fp)) != EOF)
199 if (c == '\n' && ++lines >= rows)
200 break;
201 really_rewind(fp);
202 }
203
204 /* Take account for the follow-up prompt */
205 if(lines + 1 >= rows){
206 struct mx_child_ctx cc;
207 char const *env_addon[2];
208
209 mx_child_ctx_setup(&cc);
210 cc.cc_flags = mx_CHILD_RUN_WAIT_LIFE;
211 cc.cc_fds[mx_CHILD_FD_IN] = fileno(fp);
212 cc.cc_cmd = a_aux_pager_get(&env_addon[0]);
213 env_addon[1] = NIL;
214 cc.cc_env_addon = env_addon;
215 mx_child_run(&cc);
216 goto jleave;
217 }
218 }
219
220 while ((c = getc(fp)) != EOF)
221 putc(c, n_stdout);
222 jleave:
223 NYD_OU;
224 }
225
226 FL enum protocol
which_protocol(char const * name,boole check_stat,boole try_hooks,char const ** adjusted_or_nil)227 which_protocol(char const *name, boole check_stat, boole try_hooks,
228 char const **adjusted_or_nil)
229 {
230 /* TODO This which_protocol() sickness should be URL::new()->protocol() */
231 char const *cp, *orig_name;
232 enum protocol rv, fixrv;
233 NYD2_IN;
234
235 rv = fixrv = PROTO_UNKNOWN;
236
237 if(name[0] == '%' && name[1] == ':')
238 name += 2;
239 orig_name = name;
240
241 for(cp = name; *cp && *cp != ':'; cp++)
242 if(!su_cs_is_alnum(*cp))
243 goto jfile;
244
245 if(cp[0] == ':' && cp[1] == '/' && cp[2] == '/'){ /* TODO lookup table */
246 boole yeshooks;
247
248 yeshooks = FAL0;
249
250 if(!su_cs_cmp_case_n(name, "file", sizeof("file") -1) ||
251 !su_cs_cmp_case_n(name, "mbox", sizeof("mbox") -1))
252 yeshooks = TRU1, rv = PROTO_FILE;
253 else if(!su_cs_cmp_case_n(name, "eml", sizeof("eml") -1))
254 yeshooks = TRU1, rv = n_PROTO_EML;
255 else if(!su_cs_cmp_case_n(name, "maildir", sizeof("maildir") -1)){
256 #ifdef mx_HAVE_MAILDIR
257 rv = PROTO_MAILDIR;
258 #else
259 n_err(_("No Maildir directory support compiled in\n"));
260 #endif
261 }else if(!su_cs_cmp_case_n(name, "pop3", sizeof("pop3") -1)){
262 #ifdef mx_HAVE_POP3
263 rv = PROTO_POP3;
264 #else
265 n_err(_("No POP3 support compiled in\n"));
266 #endif
267 }else if(!su_cs_cmp_case_n(name, "pop3s", sizeof("pop3s") -1)){
268 #if defined mx_HAVE_POP3 && defined mx_HAVE_TLS
269 rv = PROTO_POP3;
270 #else
271 n_err(_("No POP3S support compiled in\n"));
272 #endif
273 }else if(!su_cs_cmp_case_n(name, "imap", sizeof("imap") -1)){
274 #ifdef mx_HAVE_IMAP
275 rv = PROTO_IMAP;
276 #else
277 n_err(_("No IMAP support compiled in\n"));
278 #endif
279 }else if(!su_cs_cmp_case_n(name, "imaps", sizeof("imaps") -1)){
280 #if defined mx_HAVE_IMAP && defined mx_HAVE_TLS
281 rv = PROTO_IMAP;
282 #else
283 n_err(_("No IMAPS support compiled in\n"));
284 #endif
285 }
286
287 orig_name = name = &cp[3];
288
289 if(yeshooks){
290 fixrv = rv;
291 goto jcheck;
292 }
293 }else{
294 jfile:
295 rv = PROTO_FILE;
296 jcheck:
297 if(check_stat || try_hooks){
298 struct mx_filetype ft;
299 struct stat stb;
300 char *np;
301 uz i;
302
303 np = n_lofi_alloc((i = su_cs_len(name)) + 4 +1);
304 su_mem_copy(np, name, i +1);
305
306 if(!stat(name, &stb)){
307 if(S_ISDIR(stb.st_mode)
308 #ifdef mx_HAVE_MAILDIR
309 && (su_mem_copy(&np[i], "/tmp", 5),
310 !stat(np, &stb) && S_ISDIR(stb.st_mode)) &&
311 (su_mem_copy(&np[i], "/new", 5),
312 !stat(np, &stb) && S_ISDIR(stb.st_mode)) &&
313 (su_mem_copy(&np[i], "/cur", 5),
314 !stat(np, &stb) && S_ISDIR(stb.st_mode))
315 #endif
316 ){
317 rv =
318 #ifdef mx_HAVE_MAILDIR
319 PROTO_MAILDIR
320 #else
321 PROTO_UNKNOWN
322 #endif
323 ;
324 }
325 }else if(try_hooks && mx_filetype_trial(&ft, name)){
326 orig_name = savecatsep(name, '.', ft.ft_ext_dat);
327 if(fixrv != PROTO_UNKNOWN)
328 rv = fixrv;
329 }else if(fixrv == PROTO_UNKNOWN &&
330 (cp = ok_vlook(newfolders)) != NIL &&
331 !su_cs_cmp_case(cp, "maildir")){
332 rv =
333 #ifdef mx_HAVE_MAILDIR
334 PROTO_MAILDIR
335 #else
336 PROTO_UNKNOWN
337 #endif
338 ;
339 #ifndef mx_HAVE_MAILDIR
340 n_err(_("*newfolders*: no Maildir support compiled in\n"));
341 #endif
342 }
343
344 n_lofi_free(np);
345
346 if(fixrv != PROTO_UNKNOWN && fixrv != rv)
347 rv = PROTO_UNKNOWN;
348 }
349 }
350
351 if(adjusted_or_nil != NIL)
352 *adjusted_or_nil = orig_name;
353
354 NYD2_OU;
355 return rv;
356 }
357
358 FL char *
n_c_to_hex_base16(char store[3],char c)359 n_c_to_hex_base16(char store[3], char c){
360 static char const itoa16[] = "0123456789ABCDEF";
361 NYD2_IN;
362
363 store[2] = '\0';
364 store[1] = itoa16[(u8)c & 0x0F];
365 c = ((u8)c >> 4) & 0x0F;
366 store[0] = itoa16[(u8)c];
367 NYD2_OU;
368 return store;
369 }
370
371 FL s32
n_c_from_hex_base16(char const hex[2])372 n_c_from_hex_base16(char const hex[2]){
373 static u8 const atoi16[] = {
374 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, /* 0x30-0x37 */
375 0x08, 0x09, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /* 0x38-0x3F */
376 0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, /* 0x40-0x47 */
377 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /* 0x48-0x4f */
378 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /* 0x50-0x57 */
379 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /* 0x58-0x5f */
380 0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF /* 0x60-0x67 */
381 };
382 u8 i1, i2;
383 s32 rv;
384 NYD2_IN;
385
386 if ((i1 = (u8)hex[0] - '0') >= NELEM(atoi16) ||
387 (i2 = (u8)hex[1] - '0') >= NELEM(atoi16))
388 goto jerr;
389 i1 = atoi16[i1];
390 i2 = atoi16[i2];
391 if ((i1 | i2) & 0xF0u)
392 goto jerr;
393 rv = i1;
394 rv <<= 4;
395 rv += i2;
396 jleave:
397 NYD2_OU;
398 return rv;
399 jerr:
400 rv = -1;
401 goto jleave;
402 }
403
404 FL char const *
n_getdeadletter(void)405 n_getdeadletter(void){
406 char const *cp;
407 boole bla;
408 NYD_IN;
409
410 bla = FAL0;
411 jredo:
412 cp = fexpand(ok_vlook(DEAD), FEXP_NOPROTO | FEXP_LOCAL_FILE | FEXP_NSHELL);
413 if(cp == NULL || su_cs_len(cp) >= PATH_MAX){
414 if(!bla){
415 n_err(_("Failed to expand *DEAD*, setting default (%s): %s\n"),
416 VAL_DEAD, n_shexp_quote_cp((cp == NULL ? n_empty : cp), FAL0));
417 ok_vclear(DEAD);
418 bla = TRU1;
419 goto jredo;
420 }else{
421 cp = savecatsep(ok_vlook(TMPDIR), '/', VAL_DEAD_BASENAME);
422 n_err(_("Cannot expand *DEAD*, using: %s\n"), cp);
423 }
424 }
425 NYD_OU;
426 return cp;
427 }
428
429 FL char *
n_nodename(boole mayoverride)430 n_nodename(boole mayoverride){
431 static char *sys_hostname, *hostname; /* XXX free-at-exit */
432
433 struct utsname ut;
434 char *hn;
435 #ifdef mx_HAVE_NET
436 # ifdef mx_HAVE_GETADDRINFO
437 struct addrinfo hints, *res;
438 # else
439 struct hostent *hent;
440 # endif
441 #endif
442 NYD2_IN;
443
444 if(mayoverride && (hn = ok_vlook(hostname)) != NULL && *hn != '\0'){
445 ;
446 }else if(su_state_has(su_STATE_REPRODUCIBLE)){
447 hn = n_UNCONST(su_reproducible_build);
448 }else if((hn = sys_hostname) == NULL){
449 boole lofi;
450
451 lofi = FAL0;
452 uname(&ut);
453 hn = ut.nodename;
454
455 #ifdef mx_HAVE_NET
456 # ifdef mx_HAVE_GETADDRINFO
457 su_mem_set(&hints, 0, sizeof hints);
458 hints.ai_family = AF_UNSPEC;
459 hints.ai_flags = AI_CANONNAME;
460 if(getaddrinfo(hn, NULL, &hints, &res) == 0){
461 if(res->ai_canonname != NULL){
462 uz l;
463
464 l = su_cs_len(res->ai_canonname) +1;
465 hn = n_lofi_alloc(l);
466 lofi = TRU1;
467 su_mem_copy(hn, res->ai_canonname, l);
468 }
469 freeaddrinfo(res);
470 }
471 # else
472 hent = gethostbyname(hn);
473 if(hent != NULL)
474 hn = hent->h_name;
475 # endif
476 #endif /* mx_HAVE_NET */
477
478 /* Ensure it is non-empty! */
479 if(hn[0] == '\0')
480 hn = n_UNCONST(n_LOCALHOST_DEFAULT_NAME);
481
482 #ifdef mx_HAVE_IDNA
483 /* C99 */{
484 struct n_string cnv;
485
486 n_string_creat(&cnv);
487 if(!n_idna_to_ascii(&cnv, hn, UZ_MAX))
488 n_panic(_("The system hostname is invalid, "
489 "IDNA conversion failed: %s\n"),
490 n_shexp_quote_cp(hn, FAL0));
491 sys_hostname = n_string_cp(&cnv);
492 n_string_drop_ownership(&cnv);
493 /*n_string_gut(&cnv);*/
494 }
495 #else
496 sys_hostname = su_cs_dup(hn, 0);
497 #endif
498
499 if(lofi)
500 n_lofi_free(hn);
501 hn = sys_hostname;
502 }
503
504 if(hostname != NULL && hostname != sys_hostname)
505 n_free(hostname);
506 hostname = su_cs_dup(hn, 0);
507 NYD2_OU;
508 return hostname;
509 }
510
511 #ifdef mx_HAVE_IDNA
512 FL boole
n_idna_to_ascii(struct n_string * out,char const * ibuf,uz ilen)513 n_idna_to_ascii(struct n_string *out, char const *ibuf, uz ilen){
514 char *idna_utf8;
515 boole lofi, rv;
516 NYD_IN;
517
518 if(ilen == UZ_MAX)
519 ilen = su_cs_len(ibuf);
520
521 lofi = FAL0;
522
523 if((rv = (ilen == 0)))
524 goto jleave;
525 if(ibuf[ilen] != '\0'){
526 lofi = TRU1;
527 idna_utf8 = n_lofi_alloc(ilen +1);
528 su_mem_copy(idna_utf8, ibuf, ilen);
529 idna_utf8[ilen] = '\0';
530 ibuf = idna_utf8;
531 }
532 ilen = 0;
533
534 # ifndef mx_HAVE_ALWAYS_UNICODE_LOCALE
535 if(n_psonce & n_PSO_UNICODE)
536 # endif
537 idna_utf8 = n_UNCONST(ibuf);
538 # ifndef mx_HAVE_ALWAYS_UNICODE_LOCALE
539 else if((idna_utf8 = n_iconv_onetime_cp(n_ICONV_NONE, "utf-8",
540 ok_vlook(ttycharset), ibuf)) == NULL)
541 goto jleave;
542 # endif
543
544 # if mx_HAVE_IDNA == n_IDNA_IMPL_LIBIDN2
545 /* C99 */{
546 char *idna_ascii;
547 int f, rc;
548
549 f = IDN2_NONTRANSITIONAL;
550 jidn2_redo:
551 if((rc = idn2_to_ascii_8z(idna_utf8, &idna_ascii, f)) == IDN2_OK){
552 out = n_string_assign_cp(out, idna_ascii);
553 idn2_free(idna_ascii);
554 rv = TRU1;
555 ilen = out->s_len;
556 }else if(rc == IDN2_DISALLOWED && f != IDN2_TRANSITIONAL){
557 f = IDN2_TRANSITIONAL;
558 goto jidn2_redo;
559 }
560 }
561
562 # elif mx_HAVE_IDNA == n_IDNA_IMPL_LIBIDN
563 /* C99 */{
564 char *idna_ascii;
565
566 if(idna_to_ascii_8z(idna_utf8, &idna_ascii, 0) == IDNA_SUCCESS){
567 out = n_string_assign_cp(out, idna_ascii);
568 idn_free(idna_ascii);
569 rv = TRU1;
570 ilen = out->s_len;
571 }
572 }
573
574 # elif mx_HAVE_IDNA == n_IDNA_IMPL_IDNKIT
575 ilen = su_cs_len(idna_utf8);
576 jredo:
577 switch(idn_encodename(
578 /* LOCALCONV changed meaning in v2 and is no longer available for
579 * encoding. This makes sense, bu */
580 (
581 # ifdef IDN_UNICODECONV /* v2 */
582 IDN_ENCODE_APP & ~IDN_UNICODECONV
583 # else
584 IDN_DELIMMAP | IDN_LOCALMAP | IDN_NAMEPREP | IDN_IDNCONV |
585 IDN_LENCHECK | IDN_ASCCHECK
586 # endif
587 ), idna_utf8,
588 n_string_resize(n_string_trunc(out, 0), ilen)->s_dat, ilen)){
589 case idn_buffer_overflow:
590 ilen += HOST_NAME_MAX +1;
591 goto jredo;
592 case idn_success:
593 rv = TRU1;
594 ilen = su_cs_len(out->s_dat);
595 break;
596 default:
597 ilen = 0;
598 break;
599 }
600
601 # else
602 # error Unknown mx_HAVE_IDNA
603 # endif
604 jleave:
605 if(lofi)
606 n_lofi_free(n_UNCONST(ibuf));
607 out = n_string_trunc(out, ilen);
608 NYD_OU;
609 return rv;
610 }
611 #endif /* mx_HAVE_IDNA */
612
613 FL boole
n_boolify(char const * inbuf,uz inlen,boole emptyrv)614 n_boolify(char const *inbuf, uz inlen, boole emptyrv){
615 boole rv;
616 NYD2_IN;
617 ASSERT(inlen == 0 || inbuf != NULL);
618
619 if(inlen == UZ_MAX)
620 inlen = su_cs_len(inbuf);
621
622 if(inlen == 0)
623 rv = (emptyrv >= FAL0) ? (emptyrv == FAL0 ? FAL0 : TRU1) : TRU2;
624 else{
625 if((inlen == 1 && (*inbuf == '1' || *inbuf == 'y' || *inbuf == 'Y')) ||
626 !su_cs_cmp_case_n(inbuf, "true", inlen) ||
627 !su_cs_cmp_case_n(inbuf, "yes", inlen) ||
628 !su_cs_cmp_case_n(inbuf, "on", inlen))
629 rv = TRU1;
630 else if((inlen == 1 &&
631 (*inbuf == '0' || *inbuf == 'n' || *inbuf == 'N')) ||
632 !su_cs_cmp_case_n(inbuf, "false", inlen) ||
633 !su_cs_cmp_case_n(inbuf, "no", inlen) ||
634 !su_cs_cmp_case_n(inbuf, "off", inlen))
635 rv = FAL0;
636 else{
637 u64 ib;
638
639 if((su_idec(&ib, inbuf, inlen, 0, 0, NULL) & (su_IDEC_STATE_EMASK |
640 su_IDEC_STATE_CONSUMED)) != su_IDEC_STATE_CONSUMED)
641 rv = TRUM1;
642 else
643 rv = (ib != 0);
644 }
645 }
646 NYD2_OU;
647 return rv;
648 }
649
650 FL boole
n_quadify(char const * inbuf,uz inlen,char const * prompt,boole emptyrv)651 n_quadify(char const *inbuf, uz inlen, char const *prompt, boole emptyrv){
652 boole rv;
653 NYD2_IN;
654 ASSERT(inlen == 0 || inbuf != NULL);
655
656 if(inlen == UZ_MAX)
657 inlen = su_cs_len(inbuf);
658
659 if(inlen == 0)
660 rv = (emptyrv >= FAL0) ? (emptyrv == FAL0 ? FAL0 : TRU1) : TRU2;
661 else if((rv = n_boolify(inbuf, inlen, emptyrv)) < FAL0 &&
662 !su_cs_cmp_case_n(inbuf, "ask-", 4) &&
663 (rv = n_boolify(&inbuf[4], inlen - 4, emptyrv)) >= FAL0 &&
664 (n_psonce & n_PSO_INTERACTIVE) && !(n_pstate & n_PS_ROBOT))
665 rv = mx_tty_yesorno(prompt, rv);
666 NYD2_OU;
667 return rv;
668 }
669
670 FL boole
n_is_all_or_aster(char const * name)671 n_is_all_or_aster(char const *name){
672 boole rv;
673 NYD2_IN;
674
675 rv = ((name[0] == '*' && name[1] == '\0') || !su_cs_cmp_case(name, "all"));
676 NYD2_OU;
677 return rv;
678 }
679
680 FL struct n_timespec const *
n_time_now(boole force_update)681 n_time_now(boole force_update){ /* TODO event loop update IF cmd requests! */
682 static struct n_timespec ts_now;
683 NYD2_IN;
684
685 if(UNLIKELY(su_state_has(su_STATE_REPRODUCIBLE))){
686 /* Guaranteed 32-bit posnum TODO SOURCE_DATE_EPOCH should be 64-bit! */
687 (void)su_idec_s64_cp(&ts_now.ts_sec, ok_vlook(SOURCE_DATE_EPOCH),
688 0,NULL);
689 ts_now.ts_nsec = 0;
690 }else if(force_update || ts_now.ts_sec == 0){
691 #ifdef mx_HAVE_CLOCK_GETTIME
692 struct timespec ts;
693
694 clock_gettime(CLOCK_REALTIME, &ts);
695 ts_now.ts_sec = (s64)ts.tv_sec;
696 ts_now.ts_nsec = (sz)ts.tv_nsec;
697 #elif defined mx_HAVE_GETTIMEOFDAY
698 struct timeval tv;
699
700 gettimeofday(&tv, NULL);
701 ts_now.ts_sec = (s64)tv.tv_sec;
702 ts_now.ts_nsec = (sz)tv.tv_usec * 1000;
703 #else
704 ts_now.ts_sec = (s64)time(NULL);
705 ts_now.ts_nsec = 0;
706 #endif
707 }
708
709 /* Just in case.. */
710 if(UNLIKELY(ts_now.ts_sec < 0))
711 ts_now.ts_sec = 0;
712 NYD2_OU;
713 return &ts_now;
714 }
715
716 FL void
time_current_update(struct time_current * tc,boole full_update)717 time_current_update(struct time_current *tc, boole full_update){
718 NYD_IN;
719 tc->tc_time = (time_t)n_time_now(TRU1)->ts_sec;
720
721 if(full_update){
722 char *cp;
723 struct tm *tmp;
724 time_t t;
725
726 t = tc->tc_time;
727 jredo:
728 if((tmp = gmtime(&t)) == NULL){
729 t = 0;
730 goto jredo;
731 }
732 su_mem_copy(&tc->tc_gm, tmp, sizeof tc->tc_gm);
733 if((tmp = localtime(&t)) == NULL){
734 t = 0;
735 goto jredo;
736 }
737 su_mem_copy(&tc->tc_local, tmp, sizeof tc->tc_local);
738 cp = su_cs_pcopy(tc->tc_ctime, n_time_ctime((s64)tc->tc_time, tmp));
739 *cp++ = '\n';
740 *cp = '\0';
741 ASSERT(P2UZ(++cp - tc->tc_ctime) < sizeof(tc->tc_ctime));
742 }
743 NYD_OU;
744 }
745
746 FL s32
n_time_tzdiff(s64 secsepoch,struct tm const * utcp_or_nil,struct tm const * localp_or_nil)747 n_time_tzdiff(s64 secsepoch, struct tm const *utcp_or_nil,
748 struct tm const *localp_or_nil){
749 struct tm tmbuf[2], *tmx;
750 time_t t;
751 s32 rv;
752 NYD2_IN;
753 UNUSED(utcp_or_nil);
754
755 rv = 0;
756
757 if(localp_or_nil == NIL){
758 t = S(time_t,secsepoch);
759 if((tmx = localtime(&t)) == NIL)
760 goto jleave;
761 tmbuf[0] = *tmx;
762 localp_or_nil = &tmbuf[0];
763 }
764
765 #ifdef mx_HAVE_TM_GMTOFF
766 rv = localp_or_nil->tm_gmtoff;
767
768 #else
769 if(utcp_or_nil == NIL){
770 t = S(time_t,secsepoch);
771 if((tmx = gmtime(&t)) == NIL)
772 goto jleave;
773 tmbuf[1] = *tmx;
774 utcp_or_nil = &tmbuf[1];
775 }
776
777 rv = ((((localp_or_nil->tm_hour - utcp_or_nil->tm_hour) * 60) +
778 (localp_or_nil->tm_min - utcp_or_nil->tm_min)) * 60) +
779 (localp_or_nil->tm_sec - utcp_or_nil->tm_sec);
780
781 if((t = (localp_or_nil->tm_yday - utcp_or_nil->tm_yday)) != 0){
782 s32 const ds = 24 * 60 * 60;
783
784 rv += (t == 1) ? ds : -S(s32,ds);
785 }
786 #endif
787
788 jleave:
789 NYD2_OU;
790 return rv;
791 }
792
793 FL char *
n_time_ctime(s64 secsepoch,struct tm const * localtime_or_nil)794 n_time_ctime(s64 secsepoch, struct tm const *localtime_or_nil){/* TODO err*/
795 /* Problem is that secsepoch may be invalid for representation of ctime(3),
796 * which indeed is asctime(localtime(t)); musl libc says for asctime(3):
797 * ISO C requires us to use the above format string,
798 * even if it will not fit in the buffer. Thus asctime_r
799 * is _supposed_ to crash if the fields in tm are too large.
800 * We follow this behavior and crash "gracefully" to warn
801 * application developers that they may not be so lucky
802 * on other implementations (e.g. stack smashing..).
803 * So we need to do it on our own or the libc may kill us */
804 static char buf[32]; /* TODO static buffer (-> datetime_to_format()) */
805
806 s32 y, md, th, tm, ts;
807 char const *wdn, *mn;
808 struct tm const *tmp;
809 NYD_IN;
810 LCTA(FIELD_SIZEOF(struct time_current,tc_ctime) == sizeof(buf),
811 "Buffers should have equal size");
812
813 if((tmp = localtime_or_nil) == NIL){
814 time_t t;
815
816 t = (time_t)secsepoch;
817 jredo:
818 if((tmp = localtime(&t)) == NIL){
819 /* TODO error log */
820 t = 0;
821 goto jredo;
822 }
823 }
824
825 if(UNLIKELY((y = tmp->tm_year) < 0 || y >= 9999/*S32_MAX*/ - 1900)){
826 y = 1970;
827 wdn = n_weekday_names[4];
828 mn = n_month_names[0];
829 md = 1;
830 th = tm = ts = 0;
831 }else{
832 y += 1900;
833 wdn = (tmp->tm_wday >= 0 && tmp->tm_wday <= 6)
834 ? n_weekday_names[tmp->tm_wday] : n_qm;
835 mn = (tmp->tm_mon >= 0 && tmp->tm_mon <= 11)
836 ? n_month_names[tmp->tm_mon] : n_qm;
837
838 if((md = tmp->tm_mday) < 1 || md > 31)
839 md = 1;
840
841 if((th = tmp->tm_hour) < 0 || th > 23)
842 th = 0;
843 if((tm = tmp->tm_min) < 0 || tm > 59)
844 tm = 0;
845 if((ts = tmp->tm_sec) < 0 || ts > 60)
846 ts = 0;
847 }
848
849 (void)snprintf(buf, sizeof buf, "%3s %3s%3d %.2d:%.2d:%.2d %d",
850 wdn, mn, md, th, tm, ts, y);
851
852 NYD_OU;
853 return buf;
854 }
855
856 FL uz
n_msleep(uz millis,boole ignint)857 n_msleep(uz millis, boole ignint){
858 uz rv;
859 NYD2_IN;
860
861 #ifdef mx_HAVE_NANOSLEEP
862 /* C99 */{
863 struct timespec ts, trem;
864 int i;
865
866 ts.tv_sec = millis / 1000;
867 ts.tv_nsec = (millis %= 1000) * 1000 * 1000;
868
869 while((i = nanosleep(&ts, &trem)) != 0 && ignint)
870 ts = trem;
871 rv = (i == 0) ? 0
872 : (trem.tv_sec * 1000) + (trem.tv_nsec / (1000 * 1000));
873 }
874
875 #elif defined mx_HAVE_SLEEP
876 if((millis /= 1000) == 0)
877 millis = 1;
878 while((rv = sleep((unsigned int)millis)) != 0 && ignint)
879 millis = rv;
880 #else
881 # error Configuration should have detected a function for sleeping.
882 #endif
883
884 NYD2_OU;
885 return rv;
886 }
887
888 FL void
n_err(char const * format,...)889 n_err(char const *format, ...){
890 va_list ap;
891 NYD2_IN;
892
893 va_start(ap, format);
894 n_verrx(FAL0, format, ap);
895 va_end(ap);
896 NYD2_OU;
897 }
898
899 FL void
n_errx(boole allow_multiple,char const * format,...)900 n_errx(boole allow_multiple, char const *format, ...){
901 va_list ap;
902 NYD2_IN;
903
904 va_start(ap, format);
905 n_verrx(allow_multiple, format, ap);
906 va_end(ap);
907 NYD2_OU;
908 }
909
910 FL void
n_verr(char const * format,va_list ap)911 n_verr(char const *format, va_list ap){
912 NYD2_IN;
913 n_verrx(FAL0, format, ap);
914 NYD2_OU;
915 }
916
917 FL void
n_verrx(boole allow_multiple,char const * format,va_list ap)918 n_verrx(boole allow_multiple, char const *format, va_list ap){/*XXX sigcondom*/
919 mx_COLOUR( static uz c5recur; ) /* *termcap* recursion */
920 #ifdef mx_HAVE_ERRORS
921 u32 errlim;
922 #endif
923 struct str s_b, s;
924 struct a_aux_err_node *lenp, *enp;
925 sz i;
926 char const *lpref, *c5pref, *c5suff;
927 NYD2_IN;
928
929 mx_COLOUR( ++c5recur; )
930 lpref = NIL;
931 c5pref = c5suff = su_empty;
932
933 /* Fully expand the buffer (TODO use fmtenc) */
934 #undef a_X
935 #ifdef mx_HAVE_N_VA_COPY
936 # define a_X 128
937 #else
938 # define a_X MIN(LINESIZE, 1024)
939 #endif
940 mx_fs_linepool_aquire(&s_b.s, &s_b.l);
941 i = 0;
942 if(s_b.l < a_X)
943 i = s_b.l = a_X;
944 #undef a_X
945
946 for(;; s_b.l = ++i /* xxx could wrap, maybe */){
947 #ifdef mx_HAVE_N_VA_COPY
948 va_list vac;
949
950 n_va_copy(vac, ap);
951 #else
952 # define vac ap
953 #endif
954
955 if(i != 0)
956 s_b.s = su_MEM_REALLOC(s_b.s, s_b.l);
957
958 i = vsnprintf(s_b.s, s_b.l, format, vac);
959
960 #ifdef mx_HAVE_N_VA_COPY
961 va_end(vac);
962 #else
963 # undef vac
964 #endif
965
966 if(i <= 0)
967 goto jleave;
968 if(UCMP(z, i, >=, s_b.l)){
969 #ifdef mx_HAVE_N_VA_COPY
970 continue;
971 #else
972 i = S(int,su_cs_len(s_b.s));
973 #endif
974 }
975 break;
976 }
977 s = s_b;
978 s.l = S(uz,i);
979
980 /* Remove control characters but \n as we do not makeprint() XXX config */
981 /* C99 */{
982 char *ins, *curr, *max, c;
983
984 for(ins = curr = s.s, max = &ins[s.l]; curr < max; ++curr)
985 if(!su_cs_is_cntrl(c = *curr) || c == '\n')
986 *ins++ = c;
987 *ins = '\0';
988 s.l = P2UZ(ins - s.s);
989 }
990
991 /* We have the prepared error message, take it over line-by-line, possibly
992 * completing partly prepared one first */
993 if(n_pstate & n_PS_ERRORS_NEED_PRINT_ONCE){
994 n_pstate ^= n_PS_ERRORS_NEED_PRINT_ONCE;
995 allow_multiple = TRU1;
996 }
997
998 /* C99 */{
999 u32 poption_save;
1000
1001 poption_save = n_poption; /* XXX sigh */
1002 n_poption &= ~n_PO_D_V;
1003
1004 lpref = ok_vlook(log_prefix);
1005
1006 #ifdef mx_HAVE_ERRORS
1007 su_idec_u32_cp(&errlim, ok_vlook(errors_limit), 0, NIL);
1008 #endif
1009
1010 #ifdef mx_HAVE_COLOUR
1011 if(c5recur == 1 && (n_psonce & n_PSO_TTYANY)){
1012 struct str const *pref, *suff;
1013 struct mx_colour_pen *cp;
1014
1015 if((cp = mx_colour_get_pen(mx_COLOUR_GET_FORCED,
1016 mx_COLOUR_CTX_MLE, mx_COLOUR_ID_MLE_ERROR, NIL)
1017 ) != NIL && (pref = mx_colour_pen_get_cseq(cp)) != NIL &&
1018 (suff = mx_colour_get_reset_cseq(mx_COLOUR_GET_FORCED)
1019 ) != NIL){
1020 c5pref = pref->s;
1021 c5suff = suff->s;
1022 }
1023 }
1024 #endif
1025
1026 n_poption = poption_save;
1027 }
1028
1029 for(i = 0; UCMP(z, i, <, s.l);){
1030 char *cp;
1031 boole isdup;
1032
1033 lenp = enp = a_aux_err_tail;
1034 if(enp == NIL || enp->ae_done){
1035 enp = su_TCALLOC(struct a_aux_err_node, 1);
1036 enp->ae_cnt = 1;
1037 n_string_creat(&enp->ae_str);
1038
1039 if(a_aux_err_tail != NIL)
1040 a_aux_err_tail->ae_next = enp;
1041 else
1042 a_aux_err_head = enp;
1043 a_aux_err_tail = enp;
1044 }
1045
1046 /* xxx if(!n_string_book(&enp->ae_str, s.l - i))
1047 * xxx goto jleave;*/
1048
1049 /* We have completed a line? */
1050 /* C99 */{
1051 uz oi, j, k;
1052
1053 oi = S(uz,i);
1054 j = s.l - oi;
1055 k = enp->ae_str.s_len;
1056 cp = S(char*,su_mem_find(&s.s[oi], '\n', j));
1057
1058 if(cp == NIL){
1059 n_string_push_buf(&enp->ae_str, &s.s[oi], j);
1060 i = s.l;
1061 }else{
1062 j = P2UZ(cp - &s.s[oi]);
1063 i += j + 1;
1064 n_string_push_buf(&enp->ae_str, &s.s[oi], j);
1065 }
1066
1067 /* We need to write it out regardless of whether it is a complete line
1068 * or not, say (for at least `echoerrn') TODO IO errors not handled */
1069 if(cp == NIL || allow_multiple || !(n_psonce & n_PSO_INTERACTIVE)){
1070 fprintf(n_stderr, "%s%s%s%s%s",
1071 c5pref, (enp->ae_dumped_till == 0 ? lpref : su_empty),
1072 &n_string_cp(&enp->ae_str)[k], c5suff,
1073 (cp != NIL ? "\n" : su_empty));
1074 fflush(n_stderr);
1075 enp->ae_dumped_till = enp->ae_str.s_len;
1076 }
1077 }
1078
1079 if(cp == NIL)
1080 continue;
1081 enp->ae_done = TRU1;
1082
1083 /* Check whether it is identical to the last one dumped, in which case
1084 * we throw it away and only increment the counter, as syslog would.
1085 * If not, dump it out, if not already */
1086 isdup = FAL0;
1087 if(lenp != NIL){
1088 if(lenp != enp &&
1089 lenp->ae_str.s_len == enp->ae_str.s_len &&
1090 !su_mem_cmp(lenp->ae_str.s_dat, enp->ae_str.s_dat,
1091 enp->ae_str.s_len)){
1092 ++lenp->ae_cnt;
1093 isdup = TRU1;
1094 }
1095 /* Otherwise, if the last error has a count, say so, unless it would
1096 * soil and intermix display */
1097 else if(lenp->ae_cnt > 1 && !allow_multiple &&
1098 (n_psonce & n_PSO_INTERACTIVE)){
1099 fprintf(n_stderr,
1100 _("%s%s-- Last message repeated %u times --%s\n"),
1101 c5pref, lpref, lenp->ae_cnt, c5suff);
1102 fflush(n_stderr);
1103 }
1104 }
1105
1106 /* When we come here we need to write at least the/a \n! */
1107 if(!isdup && !allow_multiple && (n_psonce & n_PSO_INTERACTIVE)){
1108 fprintf(n_stderr, "%s%s%s%s\n",
1109 c5pref, (enp->ae_dumped_till == 0 ? lpref : su_empty),
1110 &n_string_cp(&enp->ae_str)[enp->ae_dumped_till], c5suff);
1111 fflush(n_stderr);
1112 }
1113
1114 if(isdup){
1115 lenp->ae_next = NIL;
1116 a_aux_err_tail = lenp;
1117 n_string_gut(&enp->ae_str);
1118 su_FREE(enp);
1119 continue;
1120 }
1121
1122 #ifdef mx_HAVE_ERRORS
1123 if(n_pstate_err_cnt < errlim){
1124 ++n_pstate_err_cnt;
1125 continue;
1126 }
1127
1128 ASSERT(a_aux_err_head != NIL);
1129 lenp = a_aux_err_head;
1130 if((a_aux_err_head = lenp->ae_next) == NIL)
1131 a_aux_err_tail = NIL;
1132 #else
1133 a_aux_err_head = a_aux_err_tail = enp;
1134 #endif
1135 if(lenp != NIL){
1136 n_string_gut(&lenp->ae_str);
1137 su_FREE(lenp);
1138 }
1139 }
1140
1141 jleave:
1142 mx_fs_linepool_release(s_b.s, s_b.l);
1143 mx_COLOUR( --c5recur; )
1144 NYD2_OU;
1145 }
1146
1147 FL void
n_err_sighdl(char const * format,...)1148 n_err_sighdl(char const *format, ...){ /* TODO sigsafe; obsolete! */
1149 va_list ap;
1150 NYD;
1151
1152 va_start(ap, format);
1153 vfprintf(n_stderr, format, ap);
1154 va_end(ap);
1155 fflush(n_stderr);
1156 }
1157
1158 FL void
n_perr(char const * msg,int errval)1159 n_perr(char const *msg, int errval){
1160 int e;
1161 char const *fmt;
1162 NYD2_IN;
1163
1164 if(msg == NULL){
1165 fmt = "%s%s\n";
1166 msg = n_empty;
1167 }else
1168 fmt = "%s: %s\n";
1169
1170 e = (errval == 0) ? su_err_no() : errval;
1171 n_errx(FAL0, fmt, msg, su_err_doc(e));
1172 if(errval == 0)
1173 su_err_set_no(e);
1174 NYD2_OU;
1175 }
1176
1177 FL void
n_alert(char const * format,...)1178 n_alert(char const *format, ...){
1179 va_list ap;
1180 NYD2_IN;
1181
1182
1183 n_err((a_aux_err_tail != NIL && !a_aux_err_tail->ae_done)
1184 ? _("\nAlert: ") : _("Alert: "));
1185
1186 va_start(ap, format);
1187 n_verrx(TRU1, format, ap);
1188 va_end(ap);
1189
1190 n_errx(TRU1, "\n");
1191 NYD2_OU;
1192 }
1193
1194 FL void
n_panic(char const * format,...)1195 n_panic(char const *format, ...){
1196 va_list ap;
1197 NYD2_IN;
1198
1199 if(a_aux_err_tail != NIL && !a_aux_err_tail->ae_done){
1200 a_aux_err_tail->ae_done = TRU1;
1201 putc('\n', n_stderr);
1202 }
1203 fprintf(n_stderr, "%sPanic: ", ok_vlook(log_prefix));
1204
1205 va_start(ap, format);
1206 vfprintf(n_stderr, format, ap);
1207 va_end(ap);
1208
1209 putc('\n', n_stderr);
1210 fflush(n_stderr);
1211 NYD2_OU;
1212 abort(); /* Was exit(n_EXIT_ERR); for a while, but no */
1213 }
1214
1215 #ifdef mx_HAVE_ERRORS
1216 FL int
c_errors(void * v)1217 c_errors(void *v){
1218 char **argv = v;
1219 struct a_aux_err_node *enp;
1220 NYD_IN;
1221
1222 if(*argv == NULL)
1223 goto jlist;
1224 if(argv[1] != NULL)
1225 goto jerr;
1226 if(!su_cs_cmp_case(*argv, "show"))
1227 goto jlist;
1228 if(!su_cs_cmp_case(*argv, "clear"))
1229 goto jclear;
1230 jerr:
1231 mx_cmd_print_synopsis(mx_cmd_firstfit("errors"), NIL);
1232 v = NIL;
1233 jleave:
1234 NYD_OU;
1235 return (v == NULL) ? !STOP : !OKAY; /* xxx 1:bad 0:good -- do some */
1236
1237 jlist:{
1238 FILE *fp;
1239 uz i;
1240
1241 if(a_aux_err_head == NIL){
1242 fprintf(n_stderr, _("The error ring is empty\n"));
1243 goto jleave;
1244 }
1245
1246 if((fp = mx_fs_tmp_open("errors", (mx_FS_O_RDWR | mx_FS_O_UNLINK |
1247 mx_FS_O_REGISTER), NIL)) == NIL)
1248 fp = n_stdout;
1249
1250 for(i = 0, enp = a_aux_err_head; enp != NIL; enp = enp->ae_next)
1251 fprintf(fp, "%4" PRIuZ "/%-3u %s\n",
1252 ++i, enp->ae_cnt, n_string_cp(&enp->ae_str));
1253
1254 if(fp != n_stdout){
1255 page_or_print(fp, 0);
1256
1257 mx_fs_close(fp);
1258 }else
1259 clearerr(fp);
1260 }
1261 /* FALLTHRU */
1262
1263 jclear:
1264 a_aux_err_tail = NIL;
1265 n_pstate_err_cnt = 0;
1266 while((enp = a_aux_err_head) != NIL){
1267 a_aux_err_head = enp->ae_next;
1268 n_string_gut(&enp->ae_str);
1269 su_FREE(enp);
1270 }
1271 goto jleave;
1272 }
1273 #endif /* mx_HAVE_ERRORS */
1274
1275 #ifdef mx_HAVE_REGEX
1276 FL char const *
n_regex_err_to_doc(const regex_t * rep,int e)1277 n_regex_err_to_doc(const regex_t *rep, int e){
1278 char *cp;
1279 uz i;
1280 NYD2_IN;
1281
1282 i = regerror(e, rep, NULL, 0) +1;
1283 cp = n_autorec_alloc(i);
1284 regerror(e, rep, cp, i);
1285 NYD2_OU;
1286 return cp;
1287 }
1288 #endif
1289
1290 FL boole
mx_unxy_dict(char const * cmdname,struct su_cs_dict * dp,void * vp)1291 mx_unxy_dict(char const *cmdname, struct su_cs_dict *dp, void *vp){
1292 char const **argv, *key;
1293 boole rv;
1294 NYD_IN;
1295
1296 rv = TRU1;
1297 key = (argv = vp)[0];
1298
1299 do{
1300 if(key[1] == '\0' && key[0] == '*'){
1301 if(dp != NIL)
1302 su_cs_dict_clear(dp);
1303 }else if(dp == NIL || !su_cs_dict_remove(dp, key)){
1304 n_err(_("No such `%s': %s\n"), cmdname, n_shexp_quote_cp(key, FAL0));
1305 rv = FAL0;
1306 }
1307 }while((key = *++argv) != NIL);
1308
1309 NYD_OU;
1310 return rv;
1311 }
1312
1313 FL boole
mx_xy_dump_dict(char const * cmdname,struct su_cs_dict * dp,struct n_strlist ** result,struct n_strlist ** tailpp_or_nil,struct n_strlist * (* ptf)(char const * cmdname,char const * key,void const * dat))1314 mx_xy_dump_dict(char const *cmdname, struct su_cs_dict *dp,
1315 struct n_strlist **result, struct n_strlist **tailpp_or_nil,
1316 struct n_strlist *(*ptf)(char const *cmdname, char const *key,
1317 void const *dat)){
1318 struct su_cs_dict_view dv;
1319 char const **cpp, **xcpp;
1320 u32 cnt;
1321 struct n_strlist *resp, *tailp;
1322 boole rv;
1323 NYD_IN;
1324
1325 rv = TRU1;
1326
1327 resp = *result;
1328 if(tailpp_or_nil != NIL)
1329 tailp = *tailpp_or_nil;
1330 else if((tailp = resp) != NIL)
1331 for(;; tailp = tailp->sl_next)
1332 if(tailp->sl_next == NIL)
1333 break;
1334
1335 if(dp == NIL || (cnt = su_cs_dict_count(dp)) == 0)
1336 goto jleave;
1337
1338 if(n_poption & n_PO_D_V)
1339 su_cs_dict_statistics(dp);
1340
1341 /* TODO we need LOFI/AUTOREC TALLOC version which check overflow!!
1342 * TODO these then could _really_ return NIL... */
1343 if(U32_MAX / sizeof(*cpp) <= cnt ||
1344 (cpp = S(char const**,n_autorec_alloc(sizeof(*cpp) * cnt))) == NIL)
1345 goto jleave;
1346
1347 xcpp = cpp;
1348 su_CS_DICT_FOREACH(dp, &dv)
1349 *xcpp++ = su_cs_dict_view_key(&dv);
1350 if(cnt > 1)
1351 /* This works even for case-insensitive keys because cs_dict will store
1352 * keys in lowercase-normalized versions, then */
1353 su_sort_shell_vpp(su_S(void const**,cpp), cnt, su_cs_toolbox.tb_compare);
1354
1355 for(xcpp = cpp; cnt > 0; ++xcpp, --cnt){
1356 struct n_strlist *slp;
1357
1358 if((slp = (*ptf)(cmdname, *xcpp, su_cs_dict_lookup(dp, *xcpp))) == NIL)
1359 continue;
1360 if(resp == NIL)
1361 resp = slp;
1362 else
1363 tailp->sl_next = slp;
1364 tailp = slp;
1365 }
1366
1367 jleave:
1368 *result = resp;
1369 if(tailpp_or_nil != NIL)
1370 *tailpp_or_nil = tailp;
1371
1372 NYD_OU;
1373 return rv;
1374 }
1375
1376 FL struct n_strlist *
mx_xy_dump_dict_gen_ptf(char const * cmdname,char const * key,void const * dat)1377 mx_xy_dump_dict_gen_ptf(char const *cmdname, char const *key, void const *dat){
1378 /* XXX real strlist + str_to_fmt() */
1379 char *cp;
1380 struct n_strlist *slp;
1381 uz kl, dl, cl;
1382 char const *kp, *dp;
1383 NYD2_IN;
1384
1385 kp = n_shexp_quote_cp(key, TRU1);
1386 dp = n_shexp_quote_cp(su_S(char const*,dat), TRU1);
1387 kl = su_cs_len(kp);
1388 dl = su_cs_len(dp);
1389 cl = su_cs_len(cmdname);
1390
1391 slp = n_STRLIST_AUTO_ALLOC(cl + 1 + kl + 1 + dl +1);
1392 slp->sl_next = NIL;
1393 cp = slp->sl_dat;
1394 su_mem_copy(cp, cmdname, cl);
1395 cp += cl;
1396 *cp++ = ' ';
1397 su_mem_copy(cp, kp, kl);
1398 cp += kl;
1399 *cp++ = ' ';
1400 su_mem_copy(cp, dp, dl);
1401 cp += dl;
1402 *cp = '\0';
1403 slp->sl_len = P2UZ(cp - slp->sl_dat);
1404
1405 NYD2_OU;
1406 return slp;
1407 }
1408
1409 FL boole
mx_page_or_print_strlist(char const * cmdname,struct n_strlist * slp,boole cnt_lines)1410 mx_page_or_print_strlist(char const *cmdname, struct n_strlist *slp,
1411 boole cnt_lines){
1412 uz lines;
1413 FILE *fp;
1414 boole rv;
1415 NYD_IN;
1416
1417 rv = TRU1;
1418
1419 if((fp = mx_fs_tmp_open(cmdname, (mx_FS_O_RDWR | mx_FS_O_UNLINK |
1420 mx_FS_O_REGISTER), NIL)) == NIL)
1421 fp = n_stdout;
1422
1423 /* Create visual result */
1424 for(lines = 0; slp != NIL; slp = slp->sl_next){
1425 if(fputs(slp->sl_dat, fp) == EOF){
1426 rv = FAL0;
1427 break;
1428 }
1429
1430 if(!cnt_lines){
1431 jputnl:
1432 if(putc('\n', fp) == EOF){
1433 rv = FAL0;
1434 break;
1435 }
1436 ++lines;
1437 }else{
1438 char *cp;
1439 boole lastnl;
1440
1441 for(lastnl = FAL0, cp = slp->sl_dat; *cp != '\0'; ++cp)
1442 if((lastnl = (*cp == '\n')))
1443 ++lines;
1444 if(!lastnl)
1445 goto jputnl;
1446 }
1447 }
1448
1449 if(rv && lines == 0){
1450 if(fprintf(fp, _("# `%s': no data available\n"), cmdname) < 0)
1451 rv = FAL0;
1452 else
1453 lines = 1;
1454 }
1455
1456 if(fp != n_stdout){
1457 page_or_print(fp, lines);
1458
1459 mx_fs_close(fp);
1460 }else
1461 clearerr(fp);
1462
1463 NYD_OU;
1464 return rv;
1465 }
1466
1467 #include "su/code-ou.h"
1468 /* s-it-mode */
1469