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