1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Auxiliary functions that don't fit anywhere else.
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
9 * Copyright (c) 1980, 1993
10 * The Regents of the University of California. All rights reserved.
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions
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.
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
37 #define su_FILE auxlily
40 #ifndef mx_HAVE_AMALGAMATION
44 #include <sys/utsname.h>
47 # ifdef mx_HAVE_GETADDRINFO
48 # include <sys/socket.h>
55 # if mx_HAVE_IDNA == n_IDNA_IMPL_LIBIDN2
57 # elif mx_HAVE_IDNA == n_IDNA_IMPL_LIBIDN
59 # include <idn-free.h>
60 # elif mx_HAVE_IDNA == n_IDNA_IMPL_IDNKIT
66 #include <su/cs-dict.h>
67 #include <su/icodec.h>
72 #include "mx/cmd-filetype.h"
73 #include "mx/colour.h"
74 #include "mx/file-streams.h"
75 #include "mx/termios.h"
82 # include "mx/iconv.h"
86 #include "su/code-in.h"
90 CTAV(mx_ERRORS_MAX
> 1);
93 /* The difference in between mx_HAVE_ERRORS and not, is size of queue only */
94 struct a_aux_err_node
{
95 struct a_aux_err_node
*ae_next
;
100 struct n_string ae_str
;
103 /* Error ring, for `errors' */
104 static struct a_aux_err_node
*a_aux_err_head
;
105 static struct a_aux_err_node
*a_aux_err_tail
;
107 /* Get our $PAGER; if env_addon is not NULL it is checked whether we know about
108 * some environment variable that supports colour+ and set *env_addon to that,
109 * e.g., "LESS=FRSXi" */
110 static char const *a_aux_pager_get(char const **env_addon
);
113 a_aux_pager_get(char const **env_addon
){
117 rv
= ok_vlook(PAGER
);
119 if(env_addon
!= NIL
){
121 /* Update the manual upon any changes:
122 * *colour-pager*, $PAGER */
123 if(su_cs_find(rv
, "less") != NIL
){
124 if(getenv("LESS") == NIL
)
125 *env_addon
= "LESS=RXi";
126 }else if(su_cs_find(rv
, "lv") != NIL
){
127 if(getenv("LV") == NIL
)
128 *env_addon
= "LV=-c";
141 if((cp
= ok_vlook(screen
)) != NIL
){
142 su_idec_uz_cp(&rv
, cp
, 0, NIL
);
144 rv
= mx_termios_dimen
.tiosd_height
;
146 rv
= mx_termios_dimen
.tiosd_height
;
156 char const *env_add
[2], *pager
;
160 ASSERT(n_psonce
& n_PSO_INTERACTIVE
);
162 pager
= a_aux_pager_get(env_add
+ 0);
165 if((rv
= mx_fs_pipe_open(pager
, "w", NIL
, env_add
, mx_CHILD_FD_PASS
)
173 mx_pager_close(FILE *fp
){
177 rv
= mx_fs_pipe_close(fp
, TRU1
);
183 page_or_print(FILE *fp
, uz lines
)
191 if (n_go_may_yield_control() && (cp
= ok_vlook(crt
)) != NULL
) {
195 rows
= mx_termios_dimen
.tiosd_height
;
197 su_idec_uz_cp(&rows
, cp
, 0, NULL
);
198 /* Avoid overflow later on */
202 if (rows
> 0 && lines
== 0) {
203 while ((c
= getc(fp
)) != EOF
)
204 if (c
== '\n' && ++lines
>= rows
)
209 /* Take account for the follow-up prompt */
210 if(lines
+ 1 >= rows
){
211 struct mx_child_ctx cc
;
212 char const *env_addon
[2];
214 mx_child_ctx_setup(&cc
);
215 cc
.cc_flags
= mx_CHILD_RUN_WAIT_LIFE
;
216 cc
.cc_fds
[mx_CHILD_FD_IN
] = fileno(fp
);
217 cc
.cc_cmd
= a_aux_pager_get(&env_addon
[0]);
219 cc
.cc_env_addon
= env_addon
;
225 while ((c
= getc(fp
)) != EOF
)
232 which_protocol(char const *name
, boole check_stat
, boole try_hooks
,
233 char const **adjusted_or_nil
)
235 /* TODO This which_protocol() sickness should be URL::new()->protocol() */
236 char const *cp
, *orig_name
;
237 enum protocol rv
, fixrv
;
240 rv
= fixrv
= PROTO_UNKNOWN
;
242 if(name
[0] == '%' && name
[1] == ':')
246 for(cp
= name
; *cp
&& *cp
!= ':'; cp
++)
247 if(!su_cs_is_alnum(*cp
))
250 if(cp
[0] == ':' && cp
[1] == '/' && cp
[2] == '/'){ /* TODO lookup table */
255 if(!su_cs_cmp_case_n(name
, "file", sizeof("file") -1) ||
256 !su_cs_cmp_case_n(name
, "mbox", sizeof("mbox") -1))
257 yeshooks
= TRU1
, rv
= PROTO_FILE
;
258 else if(!su_cs_cmp_case_n(name
, "eml", sizeof("eml") -1))
259 yeshooks
= TRU1
, rv
= n_PROTO_EML
;
260 else if(!su_cs_cmp_case_n(name
, "maildir", sizeof("maildir") -1)){
261 #ifdef mx_HAVE_MAILDIR
264 n_err(_("No Maildir directory support compiled in\n"));
266 }else if(!su_cs_cmp_case_n(name
, "pop3", sizeof("pop3") -1)){
270 n_err(_("No POP3 support compiled in\n"));
272 }else if(!su_cs_cmp_case_n(name
, "pop3s", sizeof("pop3s") -1)){
273 #if defined mx_HAVE_POP3 && defined mx_HAVE_TLS
276 n_err(_("No POP3S support compiled in\n"));
278 }else if(!su_cs_cmp_case_n(name
, "imap", sizeof("imap") -1)){
282 n_err(_("No IMAP support compiled in\n"));
284 }else if(!su_cs_cmp_case_n(name
, "imaps", sizeof("imaps") -1)){
285 #if defined mx_HAVE_IMAP && defined mx_HAVE_TLS
288 n_err(_("No IMAPS support compiled in\n"));
292 orig_name
= name
= &cp
[3];
302 if(check_stat
|| try_hooks
){
303 struct mx_filetype ft
;
308 np
= n_lofi_alloc((i
= su_cs_len(name
)) + 4 +1);
309 su_mem_copy(np
, name
, i
+1);
311 if(!stat(name
, &stb
)){
312 if(S_ISDIR(stb
.st_mode
)
313 #ifdef mx_HAVE_MAILDIR
314 && (su_mem_copy(&np
[i
], "/tmp", 5),
315 !stat(np
, &stb
) && S_ISDIR(stb
.st_mode
)) &&
316 (su_mem_copy(&np
[i
], "/new", 5),
317 !stat(np
, &stb
) && S_ISDIR(stb
.st_mode
)) &&
318 (su_mem_copy(&np
[i
], "/cur", 5),
319 !stat(np
, &stb
) && S_ISDIR(stb
.st_mode
))
323 #ifdef mx_HAVE_MAILDIR
330 }else if(try_hooks
&& mx_filetype_trial(&ft
, name
)){
331 orig_name
= savecatsep(name
, '.', ft
.ft_ext_dat
);
332 if(fixrv
!= PROTO_UNKNOWN
)
334 }else if(fixrv
== PROTO_UNKNOWN
&&
335 (cp
= ok_vlook(newfolders
)) != NIL
&&
336 !su_cs_cmp_case(cp
, "maildir")){
338 #ifdef mx_HAVE_MAILDIR
344 #ifndef mx_HAVE_MAILDIR
345 n_err(_("*newfolders*: no Maildir support compiled in\n"));
351 if(fixrv
!= PROTO_UNKNOWN
&& fixrv
!= rv
)
356 if(adjusted_or_nil
!= NIL
)
357 *adjusted_or_nil
= orig_name
;
364 n_c_to_hex_base16(char store
[3], char c
){
365 static char const itoa16
[] = "0123456789ABCDEF";
369 store
[1] = itoa16
[(u8
)c
& 0x0F];
370 c
= ((u8
)c
>> 4) & 0x0F;
371 store
[0] = itoa16
[(u8
)c
];
377 n_c_from_hex_base16(char const hex
[2]){
378 static u8
const atoi16
[] = {
379 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, /* 0x30-0x37 */
380 0x08, 0x09, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /* 0x38-0x3F */
381 0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, /* 0x40-0x47 */
382 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /* 0x48-0x4f */
383 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /* 0x50-0x57 */
384 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /* 0x58-0x5f */
385 0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF /* 0x60-0x67 */
391 if ((i1
= (u8
)hex
[0] - '0') >= NELEM(atoi16
) ||
392 (i2
= (u8
)hex
[1] - '0') >= NELEM(atoi16
))
396 if ((i1
| i2
) & 0xF0u
)
410 n_getdeadletter(void){
417 cp
= fexpand(ok_vlook(DEAD
), FEXP_NOPROTO
| FEXP_LOCAL_FILE
| FEXP_NSHELL
);
418 if(cp
== NULL
|| su_cs_len(cp
) >= PATH_MAX
){
420 n_err(_("Failed to expand *DEAD*, setting default (%s): %s\n"),
421 VAL_DEAD
, n_shexp_quote_cp((cp
== NULL
? n_empty
: cp
), FAL0
));
426 cp
= savecatsep(ok_vlook(TMPDIR
), '/', VAL_DEAD_BASENAME
);
427 n_err(_("Cannot expand *DEAD*, using: %s\n"), cp
);
435 n_nodename(boole mayoverride
){
436 static char *sys_hostname
, *hostname
; /* XXX free-at-exit */
441 # ifdef mx_HAVE_GETADDRINFO
442 struct addrinfo hints
, *res
;
444 struct hostent
*hent
;
449 if(mayoverride
&& (hn
= ok_vlook(hostname
)) != NULL
&& *hn
!= '\0'){
451 }else if(su_state_has(su_STATE_REPRODUCIBLE
)){
452 hn
= n_UNCONST(su_reproducible_build
);
453 }else if((hn
= sys_hostname
) == NULL
){
461 # ifdef mx_HAVE_GETADDRINFO
462 su_mem_set(&hints
, 0, sizeof hints
);
463 hints
.ai_family
= AF_UNSPEC
;
464 hints
.ai_flags
= AI_CANONNAME
;
465 if(getaddrinfo(hn
, NULL
, &hints
, &res
) == 0){
466 if(res
->ai_canonname
!= NULL
){
469 l
= su_cs_len(res
->ai_canonname
) +1;
470 hn
= n_lofi_alloc(l
);
472 su_mem_copy(hn
, res
->ai_canonname
, l
);
477 hent
= gethostbyname(hn
);
481 #endif /* mx_HAVE_NET */
483 /* Ensure it is non-empty! */
485 hn
= n_UNCONST(n_LOCALHOST_DEFAULT_NAME
);
491 n_string_creat(&cnv
);
492 if(!n_idna_to_ascii(&cnv
, hn
, UZ_MAX
))
493 n_panic(_("The system hostname is invalid, "
494 "IDNA conversion failed: %s\n"),
495 n_shexp_quote_cp(hn
, FAL0
));
496 sys_hostname
= n_string_cp(&cnv
);
497 n_string_drop_ownership(&cnv
);
498 /*n_string_gut(&cnv);*/
501 sys_hostname
= su_cs_dup(hn
, 0);
509 if(hostname
!= NULL
&& hostname
!= sys_hostname
)
511 hostname
= su_cs_dup(hn
, 0);
518 n_idna_to_ascii(struct n_string
*out
, char const *ibuf
, uz ilen
){
524 ilen
= su_cs_len(ibuf
);
528 if((rv
= (ilen
== 0)))
530 if(ibuf
[ilen
] != '\0'){
532 idna_utf8
= n_lofi_alloc(ilen
+1);
533 su_mem_copy(idna_utf8
, ibuf
, ilen
);
534 idna_utf8
[ilen
] = '\0';
539 # ifndef mx_HAVE_ALWAYS_UNICODE_LOCALE
540 if(n_psonce
& n_PSO_UNICODE
)
542 idna_utf8
= n_UNCONST(ibuf
);
543 # ifndef mx_HAVE_ALWAYS_UNICODE_LOCALE
544 else if((idna_utf8
= n_iconv_onetime_cp(n_ICONV_NONE
, "utf-8",
545 ok_vlook(ttycharset
), ibuf
)) == NULL
)
549 # if mx_HAVE_IDNA == n_IDNA_IMPL_LIBIDN2
554 f
= IDN2_NONTRANSITIONAL
;
556 if((rc
= idn2_to_ascii_8z(idna_utf8
, &idna_ascii
, f
)) == IDN2_OK
){
557 out
= n_string_assign_cp(out
, idna_ascii
);
558 idn2_free(idna_ascii
);
561 }else if(rc
== IDN2_DISALLOWED
&& f
!= IDN2_TRANSITIONAL
){
562 f
= IDN2_TRANSITIONAL
;
567 # elif mx_HAVE_IDNA == n_IDNA_IMPL_LIBIDN
571 if(idna_to_ascii_8z(idna_utf8
, &idna_ascii
, 0) == IDNA_SUCCESS
){
572 out
= n_string_assign_cp(out
, idna_ascii
);
573 idn_free(idna_ascii
);
579 # elif mx_HAVE_IDNA == n_IDNA_IMPL_IDNKIT
580 ilen
= su_cs_len(idna_utf8
);
582 switch(idn_encodename(
583 /* LOCALCONV changed meaning in v2 and is no longer available for
584 * encoding. This makes sense, bu */
586 # ifdef IDN_UNICODECONV /* v2 */
587 IDN_ENCODE_APP
& ~IDN_UNICODECONV
589 IDN_DELIMMAP
| IDN_LOCALMAP
| IDN_NAMEPREP
| IDN_IDNCONV
|
590 IDN_LENCHECK
| IDN_ASCCHECK
593 n_string_resize(n_string_trunc(out
, 0), ilen
)->s_dat
, ilen
)){
594 case idn_buffer_overflow
:
595 ilen
+= HOST_NAME_MAX
+1;
599 ilen
= su_cs_len(out
->s_dat
);
607 # error Unknown mx_HAVE_IDNA
611 n_lofi_free(n_UNCONST(ibuf
));
612 out
= n_string_trunc(out
, ilen
);
616 #endif /* mx_HAVE_IDNA */
619 n_boolify(char const *inbuf
, uz inlen
, boole emptyrv
){
622 ASSERT(inlen
== 0 || inbuf
!= NULL
);
625 inlen
= su_cs_len(inbuf
);
628 rv
= (emptyrv
>= FAL0
) ? (emptyrv
== FAL0
? FAL0
: TRU1
) : TRU2
;
630 if((inlen
== 1 && (*inbuf
== '1' || *inbuf
== 'y' || *inbuf
== 'Y')) ||
631 !su_cs_cmp_case_n(inbuf
, "true", inlen
) ||
632 !su_cs_cmp_case_n(inbuf
, "yes", inlen
) ||
633 !su_cs_cmp_case_n(inbuf
, "on", inlen
))
635 else if((inlen
== 1 &&
636 (*inbuf
== '0' || *inbuf
== 'n' || *inbuf
== 'N')) ||
637 !su_cs_cmp_case_n(inbuf
, "false", inlen
) ||
638 !su_cs_cmp_case_n(inbuf
, "no", inlen
) ||
639 !su_cs_cmp_case_n(inbuf
, "off", inlen
))
644 if((su_idec(&ib
, inbuf
, inlen
, 0, 0, NULL
) & (su_IDEC_STATE_EMASK
|
645 su_IDEC_STATE_CONSUMED
)) != su_IDEC_STATE_CONSUMED
)
656 n_quadify(char const *inbuf
, uz inlen
, char const *prompt
, boole emptyrv
){
659 ASSERT(inlen
== 0 || inbuf
!= NULL
);
662 inlen
= su_cs_len(inbuf
);
665 rv
= (emptyrv
>= FAL0
) ? (emptyrv
== FAL0
? FAL0
: TRU1
) : TRU2
;
666 else if((rv
= n_boolify(inbuf
, inlen
, emptyrv
)) < FAL0
&&
667 !su_cs_cmp_case_n(inbuf
, "ask-", 4) &&
668 (rv
= n_boolify(&inbuf
[4], inlen
- 4, emptyrv
)) >= FAL0
&&
669 (n_psonce
& n_PSO_INTERACTIVE
) && !(n_pstate
& n_PS_ROBOT
))
670 rv
= mx_tty_yesorno(prompt
, rv
);
676 n_is_all_or_aster(char const *name
){
680 rv
= ((name
[0] == '*' && name
[1] == '\0') || !su_cs_cmp_case(name
, "all"));
685 FL
struct n_timespec
const *
686 n_time_now(boole force_update
){ /* TODO event loop update IF cmd requests! */
687 static struct n_timespec ts_now
;
690 if(UNLIKELY(su_state_has(su_STATE_REPRODUCIBLE
))){
691 /* Guaranteed 32-bit posnum TODO SOURCE_DATE_EPOCH should be 64-bit! */
692 (void)su_idec_s64_cp(&ts_now
.ts_sec
, ok_vlook(SOURCE_DATE_EPOCH
),
695 }else if(force_update
|| ts_now
.ts_sec
== 0){
696 #ifdef mx_HAVE_CLOCK_GETTIME
699 clock_gettime(CLOCK_REALTIME
, &ts
);
700 ts_now
.ts_sec
= (s64
)ts
.tv_sec
;
701 ts_now
.ts_nsec
= (sz
)ts
.tv_nsec
;
702 #elif defined mx_HAVE_GETTIMEOFDAY
705 gettimeofday(&tv
, NULL
);
706 ts_now
.ts_sec
= (s64
)tv
.tv_sec
;
707 ts_now
.ts_nsec
= (sz
)tv
.tv_usec
* 1000;
709 ts_now
.ts_sec
= (s64
)time(NULL
);
715 if(UNLIKELY(ts_now
.ts_sec
< 0))
722 time_current_update(struct time_current
*tc
, boole full_update
){
724 tc
->tc_time
= (time_t)n_time_now(TRU1
)->ts_sec
;
733 if((tmp
= gmtime(&t
)) == NULL
){
737 su_mem_copy(&tc
->tc_gm
, tmp
, sizeof tc
->tc_gm
);
738 if((tmp
= localtime(&t
)) == NULL
){
742 su_mem_copy(&tc
->tc_local
, tmp
, sizeof tc
->tc_local
);
743 cp
= su_cs_pcopy(tc
->tc_ctime
, n_time_ctime((s64
)tc
->tc_time
, tmp
));
746 ASSERT(P2UZ(++cp
- tc
->tc_ctime
) < sizeof(tc
->tc_ctime
));
752 n_time_ctime(s64 secsepoch
, struct tm
const *localtime_or_nil
){/* TODO err*/
753 /* Problem is that secsepoch may be invalid for representation of ctime(3),
754 * which indeed is asctime(localtime(t)); musl libc says for asctime(3):
755 * ISO C requires us to use the above format string,
756 * even if it will not fit in the buffer. Thus asctime_r
757 * is _supposed_ to crash if the fields in tm are too large.
758 * We follow this behavior and crash "gracefully" to warn
759 * application developers that they may not be so lucky
760 * on other implementations (e.g. stack smashing..).
761 * So we need to do it on our own or the libc may kill us */
762 static char buf
[32]; /* TODO static buffer (-> datetime_to_format()) */
764 s32 y
, md
, th
, tm
, ts
;
765 char const *wdn
, *mn
;
766 struct tm
const *tmp
;
769 if((tmp
= localtime_or_nil
) == NULL
){
772 t
= (time_t)secsepoch
;
774 if((tmp
= localtime(&t
)) == NULL
){
781 if(UNLIKELY((y
= tmp
->tm_year
) < 0 || y
>= 9999/*S32_MAX*/ - 1900)){
783 wdn
= n_weekday_names
[4];
784 mn
= n_month_names
[0];
789 wdn
= (tmp
->tm_wday
>= 0 && tmp
->tm_wday
<= 6)
790 ? n_weekday_names
[tmp
->tm_wday
] : n_qm
;
791 mn
= (tmp
->tm_mon
>= 0 && tmp
->tm_mon
<= 11)
792 ? n_month_names
[tmp
->tm_mon
] : n_qm
;
794 if((md
= tmp
->tm_mday
) < 1 || md
> 31)
797 if((th
= tmp
->tm_hour
) < 0 || th
> 23)
799 if((tm
= tmp
->tm_min
) < 0 || tm
> 59)
801 if((ts
= tmp
->tm_sec
) < 0 || ts
> 60)
805 (void)snprintf(buf
, sizeof buf
, "%3s %3s%3d %.2d:%.2d:%.2d %d",
806 wdn
, mn
, md
, th
, tm
, ts
, y
);
812 n_msleep(uz millis
, boole ignint
){
816 #ifdef mx_HAVE_NANOSLEEP
818 struct timespec ts
, trem
;
821 ts
.tv_sec
= millis
/ 1000;
822 ts
.tv_nsec
= (millis
%= 1000) * 1000 * 1000;
824 while((i
= nanosleep(&ts
, &trem
)) != 0 && ignint
)
827 : (trem
.tv_sec
* 1000) + (trem
.tv_nsec
/ (1000 * 1000));
830 #elif defined mx_HAVE_SLEEP
831 if((millis
/= 1000) == 0)
833 while((rv
= sleep((unsigned int)millis
)) != 0 && ignint
)
836 # error Configuration should have detected a function for sleeping.
844 n_err(char const *format
, ...){
848 va_start(ap
, format
);
849 n_verrx(FAL0
, format
, ap
);
855 n_errx(boole allow_multiple
, char const *format
, ...){
859 va_start(ap
, format
);
860 n_verrx(allow_multiple
, format
, ap
);
866 n_verr(char const *format
, va_list ap
){
868 n_verrx(FAL0
, format
, ap
);
873 n_verrx(boole allow_multiple
, char const *format
, va_list ap
){/*XXX sigcondom*/
874 mx_COLOUR( static uz c5recur
; ) /* *termcap* recursion */
876 struct a_aux_err_node
*lenp
, *enp
;
878 char const *lpref
, *c5pref
, *c5suff
;
881 mx_COLOUR( ++c5recur
; )
883 c5pref
= c5suff
= su_empty
;
885 /* Fully expand the buffer (TODO use fmtenc) */
887 #ifdef mx_HAVE_N_VA_COPY
890 # define a_X MIN(LINESIZE, 1024)
892 mx_fs_linepool_aquire(&s_b
.s
, &s_b
.l
);
898 for(;; s_b
.l
= ++i
/* xxx could wrap, maybe */){
899 #ifdef mx_HAVE_N_VA_COPY
908 s_b
.s
= su_MEM_REALLOC(s_b
.s
, s_b
.l
);
910 i
= vsnprintf(s_b
.s
, s_b
.l
, format
, vac
);
912 #ifdef mx_HAVE_N_VA_COPY
920 if(UCMP(z
, i
, >=, s_b
.l
)){
921 #ifdef mx_HAVE_N_VA_COPY
924 i
= S(int,su_cs_len(s_b
.s
));
932 /* Remove control characters but \n as we do not makeprint() XXX config */
934 char *ins
, *curr
, *max
, c
;
936 for(ins
= curr
= s
.s
, max
= &ins
[s
.l
]; curr
< max
; ++curr
)
937 if(!su_cs_is_cntrl(c
= *curr
) || c
== '\n')
940 s
.l
= P2UZ(ins
- s
.s
);
943 /* We have the prepared error message, take it over line-by-line, possibly
944 * completing partly prepared one first */
945 n_pstate
|= n_PS_ERRORS_PROMPT
;
946 if(n_pstate
& n_PS_ERRORS_NEED_PRINT_ONCE
){
947 n_pstate
^= n_PS_ERRORS_NEED_PRINT_ONCE
;
948 allow_multiple
= TRU1
;
954 poption_save
= n_poption
; /* XXX sigh */
955 n_poption
&= ~n_PO_D_V
;
957 lpref
= ok_vlook(log_prefix
);
959 #ifdef mx_HAVE_COLOUR
960 if(c5recur
== 1 && (n_psonce
& n_PSO_TTYANY
)){
961 struct str
const *pref
, *suff
;
962 struct mx_colour_pen
*cp
;
964 if((cp
= mx_colour_get_pen(mx_COLOUR_GET_FORCED
,
965 mx_COLOUR_CTX_MLE
, mx_COLOUR_ID_MLE_ERROR
, NIL
)
966 ) != NIL
&& (pref
= mx_colour_pen_get_cseq(cp
)) != NIL
&&
967 (suff
= mx_colour_get_reset_cseq(mx_COLOUR_GET_FORCED
)
975 n_poption
= poption_save
;
978 for(i
= 0; UCMP(z
, i
, <, s
.l
);){
982 lenp
= enp
= a_aux_err_tail
;
983 if(enp
== NIL
|| enp
->ae_done
){
984 enp
= su_TCALLOC(struct a_aux_err_node
, 1);
986 n_string_creat(&enp
->ae_str
);
988 if(a_aux_err_tail
!= NIL
)
989 a_aux_err_tail
->ae_next
= enp
;
991 a_aux_err_head
= enp
;
992 a_aux_err_tail
= enp
;
995 /* xxx if(!n_string_book(&enp->ae_str, s.l - i))
998 /* We have completed a line? */
1004 k
= enp
->ae_str
.s_len
;
1005 cp
= S(char*,su_mem_find(&s
.s
[oi
], '\n', j
));
1008 n_string_push_buf(&enp
->ae_str
, &s
.s
[oi
], j
);
1011 j
= P2UZ(cp
- &s
.s
[oi
]);
1013 n_string_push_buf(&enp
->ae_str
, &s
.s
[oi
], j
);
1016 /* We need to write it out regardless of whether it is a complete line
1017 * or not, say (for at least `echoerrn') TODO IO errors not handled */
1018 if(cp
== NIL
|| allow_multiple
|| !(n_psonce
& n_PSO_INTERACTIVE
)){
1019 fprintf(n_stderr
, "%s%s%s%s%s",
1020 c5pref
, (enp
->ae_dumped_till
== 0 ? lpref
: su_empty
),
1021 &n_string_cp(&enp
->ae_str
)[k
], c5suff
,
1022 (cp
!= NIL
? "\n" : su_empty
));
1024 enp
->ae_dumped_till
= enp
->ae_str
.s_len
;
1030 enp
->ae_done
= TRU1
;
1032 /* Check whether it is identical to the last one dumped, in which case
1033 * we throw it away and only increment the counter, as syslog would.
1034 * If not, dump it out, if not already */
1038 lenp
->ae_str
.s_len
== enp
->ae_str
.s_len
&&
1039 !su_mem_cmp(lenp
->ae_str
.s_dat
, enp
->ae_str
.s_dat
,
1040 enp
->ae_str
.s_len
)){
1044 /* Otherwise, if the last error has a count, say so, unless it would
1045 * soil and intermix display */
1046 else if(lenp
->ae_cnt
> 1 && !allow_multiple
&&
1047 (n_psonce
& n_PSO_INTERACTIVE
)){
1049 _("%s%s-- Last message repeated %u times --%s\n"),
1050 c5pref
, lpref
, lenp
->ae_cnt
, c5suff
);
1055 /* When we come here we need to write at least the/a \n! */
1056 if(!isdup
&& !allow_multiple
&& (n_psonce
& n_PSO_INTERACTIVE
)){
1057 fprintf(n_stderr
, "%s%s%s%s\n",
1058 c5pref
, (enp
->ae_dumped_till
== 0 ? lpref
: su_empty
),
1059 &n_string_cp(&enp
->ae_str
)[enp
->ae_dumped_till
], c5suff
);
1064 lenp
->ae_next
= NIL
;
1065 a_aux_err_tail
= lenp
;
1066 n_string_gut(&enp
->ae_str
);
1071 #ifdef mx_HAVE_ERRORS
1072 if(n_pstate_err_cnt
< mx_ERRORS_MAX
){
1076 a_aux_err_head
= (lenp
= a_aux_err_head
)->ae_next
;
1078 a_aux_err_head
= a_aux_err_tail
= enp
;
1081 n_string_gut(&lenp
->ae_str
);
1087 mx_fs_linepool_release(s_b
.s
, s_b
.l
);
1088 mx_COLOUR( --c5recur
; )
1093 n_err_sighdl(char const *format
, ...){ /* TODO sigsafe; obsolete! */
1097 va_start(ap
, format
);
1098 vfprintf(n_stderr
, format
, ap
);
1104 n_perr(char const *msg
, int errval
){
1115 e
= (errval
== 0) ? su_err_no() : errval
;
1116 n_errx(FAL0
, fmt
, msg
, su_err_doc(e
));
1123 n_alert(char const *format
, ...){
1128 n_err((a_aux_err_tail
!= NIL
&& !a_aux_err_tail
->ae_done
)
1129 ? _("\nAlert: ") : _("Alert: "));
1131 va_start(ap
, format
);
1132 n_verrx(TRU1
, format
, ap
);
1140 n_panic(char const *format
, ...){
1144 if(a_aux_err_tail
!= NIL
&& !a_aux_err_tail
->ae_done
){
1145 a_aux_err_tail
->ae_done
= TRU1
;
1146 putc('\n', n_stderr
);
1148 fprintf(n_stderr
, "%sPanic: ", ok_vlook(log_prefix
));
1150 va_start(ap
, format
);
1151 vfprintf(n_stderr
, format
, ap
);
1154 putc('\n', n_stderr
);
1157 abort(); /* Was exit(n_EXIT_ERR); for a while, but no */
1160 #ifdef mx_HAVE_ERRORS
1164 struct a_aux_err_node
*enp
;
1171 if(!su_cs_cmp_case(*argv
, "show"))
1173 if(!su_cs_cmp_case(*argv
, "clear"))
1176 mx_cmd_print_synopsis(mx_cmd_firstfit("errors"), NIL
);
1180 return (v
== NULL
) ? !STOP
: !OKAY
; /* xxx 1:bad 0:good -- do some */
1186 if(a_aux_err_head
== NIL
){
1187 fprintf(n_stderr
, _("The error ring is empty\n"));
1191 if((fp
= mx_fs_tmp_open("errors", (mx_FS_O_RDWR
| mx_FS_O_UNLINK
|
1192 mx_FS_O_REGISTER
), NIL
)) == NIL
)
1195 for(i
= 0, enp
= a_aux_err_head
; enp
!= NIL
; enp
= enp
->ae_next
)
1196 fprintf(fp
, "%4" PRIuZ
"/%-3u %s\n",
1197 ++i
, enp
->ae_cnt
, n_string_cp(&enp
->ae_str
));
1200 page_or_print(fp
, 0);
1209 a_aux_err_tail
= NIL
;
1210 n_pstate_err_cnt
= 0;
1211 while((enp
= a_aux_err_head
) != NIL
){
1212 a_aux_err_head
= enp
->ae_next
;
1213 n_string_gut(&enp
->ae_str
);
1218 #endif /* mx_HAVE_ERRORS */
1220 #ifdef mx_HAVE_REGEX
1222 n_regex_err_to_doc(const regex_t
*rep
, int e
){
1227 i
= regerror(e
, rep
, NULL
, 0) +1;
1228 cp
= n_autorec_alloc(i
);
1229 regerror(e
, rep
, cp
, i
);
1236 mx_unxy_dict(char const *cmdname
, struct su_cs_dict
*dp
, void *vp
){
1237 char const **argv
, *key
;
1242 key
= (argv
= vp
)[0];
1245 if(key
[1] == '\0' && key
[0] == '*'){
1247 su_cs_dict_clear(dp
);
1248 }else if(dp
== NIL
|| !su_cs_dict_remove(dp
, key
)){
1249 n_err(_("No such `%s': %s\n"), cmdname
, n_shexp_quote_cp(key
, FAL0
));
1252 }while((key
= *++argv
) != NIL
);
1259 mx_xy_dump_dict(char const *cmdname
, struct su_cs_dict
*dp
,
1260 struct n_strlist
**result
, struct n_strlist
**tailpp_or_nil
,
1261 struct n_strlist
*(*ptf
)(char const *cmdname
, char const *key
,
1263 struct su_cs_dict_view dv
;
1264 char const **cpp
, **xcpp
;
1266 struct n_strlist
*resp
, *tailp
;
1273 if(tailpp_or_nil
!= NIL
)
1274 tailp
= *tailpp_or_nil
;
1275 else if((tailp
= resp
) != NIL
)
1276 for(;; tailp
= tailp
->sl_next
)
1277 if(tailp
->sl_next
== NIL
)
1280 if(dp
== NIL
|| (cnt
= su_cs_dict_count(dp
)) == 0)
1283 if(n_poption
& n_PO_D_V
)
1284 su_cs_dict_statistics(dp
);
1286 /* TODO we need LOFI/AUTOREC TALLOC version which check overflow!!
1287 * TODO these then could _really_ return NIL... */
1288 if(U32_MAX
/ sizeof(*cpp
) <= cnt
||
1289 (cpp
= S(char const**,n_autorec_alloc(sizeof(*cpp
) * cnt
))) == NIL
)
1293 su_CS_DICT_FOREACH(dp
, &dv
)
1294 *xcpp
++ = su_cs_dict_view_key(&dv
);
1296 /* This works even for case-insensitive keys because cs_dict will store
1297 * keys in lowercase-normalized versions, then */
1298 su_sort_shell_vpp(su_S(void const**,cpp
), cnt
, su_cs_toolbox
.tb_compare
);
1300 for(xcpp
= cpp
; cnt
> 0; ++xcpp
, --cnt
){
1301 struct n_strlist
*slp
;
1303 if((slp
= (*ptf
)(cmdname
, *xcpp
, su_cs_dict_lookup(dp
, *xcpp
))) == NIL
)
1308 tailp
->sl_next
= slp
;
1314 if(tailpp_or_nil
!= NIL
)
1315 *tailpp_or_nil
= tailp
;
1321 FL
struct n_strlist
*
1322 mx_xy_dump_dict_gen_ptf(char const *cmdname
, char const *key
, void const *dat
){
1323 /* XXX real strlist + str_to_fmt() */
1325 struct n_strlist
*slp
;
1327 char const *kp
, *dp
;
1330 kp
= n_shexp_quote_cp(key
, TRU1
);
1331 dp
= n_shexp_quote_cp(su_S(char const*,dat
), TRU1
);
1334 cl
= su_cs_len(cmdname
);
1336 slp
= n_STRLIST_AUTO_ALLOC(cl
+ 1 + kl
+ 1 + dl
+1);
1339 su_mem_copy(cp
, cmdname
, cl
);
1342 su_mem_copy(cp
, kp
, kl
);
1345 su_mem_copy(cp
, dp
, dl
);
1348 slp
->sl_len
= P2UZ(cp
- slp
->sl_dat
);
1355 mx_page_or_print_strlist(char const *cmdname
, struct n_strlist
*slp
,
1364 if((fp
= mx_fs_tmp_open(cmdname
, (mx_FS_O_RDWR
| mx_FS_O_UNLINK
|
1365 mx_FS_O_REGISTER
), NIL
)) == NIL
)
1368 /* Create visual result */
1369 for(lines
= 0; slp
!= NIL
; slp
= slp
->sl_next
){
1370 if(fputs(slp
->sl_dat
, fp
) == EOF
){
1377 if(putc('\n', fp
) == EOF
){
1386 for(lastnl
= FAL0
, cp
= slp
->sl_dat
; *cp
!= '\0'; ++cp
)
1387 if((lastnl
= (*cp
== '\n')))
1394 if(rv
&& lines
== 0){
1395 if(fprintf(fp
, _("# `%s': no data available\n"), cmdname
) < 0)
1402 page_or_print(fp
, lines
);
1412 #include "su/code-ou.h"