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 - 2015 Steffen (Daode) Nurpmeso <sdaoden@users.sf.net>.
8 * Copyright (c) 1980, 1993
9 * The Regents of the University of California. All rights reserved.
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. Neither the name of the University nor the names of its contributors
20 * may be used to endorse or promote products derived from this software
21 * without specific prior written permission.
23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
36 #define n_FILE auxlily
38 #ifndef HAVE_AMALGAMATION
42 #include <sys/utsname.h>
48 # ifdef HAVE_GETADDRINFO
49 # include <sys/socket.h>
55 #ifndef HAVE_POSIX_RANDOM
63 ui8_t b8
[sizeof(struct rand_arc4
)];
64 ui32_t b32
[sizeof(struct rand_arc4
) / sizeof(ui32_t
)];
69 struct shvar_stack
*shs_next
; /* Outer stack frame */
70 char const *shs_value
; /* Remaining value to expand */
71 size_t shs_len
; /* gth of .shs_dat this level */
72 char const *shs_dat
; /* Result data of this level */
73 bool_t
*shs_err
; /* Or NULL */
74 bool_t shs_bsesc
; /* Shall backslash escaping be performed */
78 struct a_aux_err_node
{
79 struct a_aux_err_node
*ae_next
;
84 #ifndef HAVE_POSIX_RANDOM
85 static union rand_state
*_rand
;
88 /* Error ring, for `errors' */
90 static struct a_aux_err_node
*a_aux_err_head
, *a_aux_err_tail
;
91 static size_t a_aux_err_cnt
, a_aux_err_cnt_noted
;
93 static size_t a_aux_err_dirty
;
95 /* Our ARC4 random generator with its completely unacademical pseudo
96 * initialization (shall /dev/urandom fail) */
97 #ifndef HAVE_POSIX_RANDOM
98 static void _rand_init(void);
99 static ui32_t
_rand_weak(ui32_t seed
);
100 SINLINE ui8_t
_rand_get8(void);
103 /* Perform shell variable expansion */
104 static char * _sh_exp_var(struct shvar_stack
*shsp
);
106 #ifndef HAVE_POSIX_RANDOM
110 # ifdef HAVE_CLOCK_GETTIME
115 union {int fd
; size_t i
;} u
;
119 _rand
= smalloc(sizeof *_rand
);
121 if ((u
.fd
= open("/dev/urandom", O_RDONLY
)) != -1) {
122 bool_t ok
= (sizeof *_rand
== (size_t)read(u
.fd
, _rand
, sizeof *_rand
));
129 for (seed
= (uintptr_t)_rand
& UI32_MAX
, rnd
= 21; rnd
!= 0; --rnd
) {
130 for (u
.i
= NELEM(_rand
->b32
); u
.i
-- != 0;) {
133 # ifdef HAVE_CLOCK_GETTIME
134 clock_gettime(CLOCK_REALTIME
, &ts
);
135 t
= (ui32_t
)ts
.tv_nsec
;
137 gettimeofday(&ts
, NULL
);
138 t
= (ui32_t
)ts
.tv_usec
;
141 t
= (t
>> 16) | (t
<< 16);
142 _rand
->b32
[u
.i
] ^= _rand_weak(seed
^ t
);
143 _rand
->b32
[t
% NELEM(_rand
->b32
)] ^= seed
;
144 if (rnd
== 7 || rnd
== 17)
145 _rand
->b32
[u
.i
] ^= _rand_weak(seed
^ (ui32_t
)ts
.tv_sec
);
146 k
= _rand
->b32
[u
.i
] % NELEM(_rand
->b32
);
147 _rand
->b32
[k
] ^= _rand
->b32
[u
.i
];
148 seed
^= _rand_weak(_rand
->b32
[k
]);
150 seed
^= nextprime(seed
);
154 for (u
.i
= 5 * sizeof(_rand
->b8
); u
.i
!= 0; --u
.i
)
161 _rand_weak(ui32_t seed
)
163 /* From "Random number generators: good ones are hard to find",
164 * Park and Miller, Communications of the ACM, vol. 31, no. 10,
165 * October 1988, p. 1195.
166 * (In fact: FreeBSD 4.7, /usr/src/lib/libc/stdlib/random.c.) */
173 seed
= (seed
* 16807) - (hi
* 2836);
174 if ((si32_t
)seed
< 0)
184 si
= _rand
->a
._dat
[++_rand
->a
._i
];
185 sj
= _rand
->a
._dat
[_rand
->a
._j
+= si
];
186 _rand
->a
._dat
[_rand
->a
._i
] = sj
;
187 _rand
->a
._dat
[_rand
->a
._j
] = si
;
188 return _rand
->a
._dat
[(ui8_t
)(si
+ sj
)];
190 #endif /* HAVE_POSIX_RANDOM */
193 _sh_exp_var(struct shvar_stack
*shsp
)
195 struct shvar_stack next
, *np
, *tmp
;
197 char lc
, c
, *cp
, *rv
;
201 if (*(vp
= shsp
->shs_value
) != '$') {
202 bool_t bsesc
= shsp
->shs_bsesc
;
203 union {bool_t hadbs
; char c
;} u
= {FAL0
};
206 for (lc
= '\0', i
= 0; ((c
= *vp
) != '\0'); ++i
, ++vp
) {
207 if (c
== '$' && lc
!= '\\')
211 lc
= (lc
== '\\') ? (u
.hadbs
= TRU1
, '\0') : c
;
216 shsp
->shs_dat
= cp
= savestrbuf(shsp
->shs_dat
, i
);
218 for (lc
= '\0', rv
= cp
; (u
.c
= *cp
++) != '\0';) {
219 if (u
.c
!= '\\' || lc
== '\\')
221 lc
= (lc
== '\\') ? '\0' : u
.c
;
225 shsp
->shs_len
= PTR2SIZE(rv
- shsp
->shs_dat
);
228 if ((lc
= (*++vp
== '{')))
232 * Environment variable names used by the utilities in the Shell and
233 * Utilities volume of POSIX.1-2008 consist solely of uppercase
234 * letters, digits, and the <underscore> ('_') from the characters
235 * defined in Portable Character Set and do not begin with a digit.
236 * Other characters may be permitted by an implementation;
237 * applications shall tolerate the presence of such names.
238 * We do support the hyphen "-" because it is common for mailx. */
240 for (i
= 0; (c
= *vp
) != '\0'; ++i
, ++vp
)
241 if (!alnumchar(c
) && c
!= '_' && c
!= '-')
246 n_err(_("Variable name misses closing \"}\": \"%s\"\n"),
248 shsp
->shs_len
= strlen(shsp
->shs_value
);
249 shsp
->shs_dat
= shsp
->shs_value
;
250 if (shsp
->shs_err
!= NULL
)
251 *shsp
->shs_err
= TRU1
;
258 if ((cp
= vok_vlook(savestrbuf(shsp
->shs_dat
, i
))) != NULL
)
259 shsp
->shs_len
= strlen(shsp
->shs_dat
= cp
);
264 /* That level made the great and completed encoding. Build result */
266 for (i
= 0, np
= shsp
, shsp
= NULL
; np
!= NULL
;) {
274 cp
= rv
= salloc(i
+1);
275 while (shsp
!= NULL
) {
277 shsp
= shsp
->shs_next
;
278 memcpy(cp
, np
->shs_dat
, np
->shs_len
);
287 memset(&next
, 0, sizeof next
);
288 next
.shs_next
= shsp
;
290 next
.shs_err
= shsp
->shs_err
;
291 next
.shs_bsesc
= shsp
->shs_bsesc
;
292 rv
= _sh_exp_var(&next
);
297 touch(struct message
*mp
)
300 mp
->m_flag
|= MTOUCH
;
301 if (!(mp
->m_flag
& MREAD
))
302 mp
->m_flag
|= MREAD
| MSTATUS
;
307 argcount(char **argv
)
312 for (ap
= argv
; *ap
++ != NULL
;)
315 return (int)PTR2SIZE(ap
- argv
- 1);
325 if ((cp
= ok_vlook(screen
)) == NULL
|| (s
= atoi(cp
)) <= 0)
326 s
= scrnheight
- 2; /* XXX no magics */
332 n_pager_get(char const **env_addon
)
337 cp
= ok_vlook(PAGER
);
338 if (cp
== NULL
|| *cp
== '\0')
341 if (env_addon
!= NULL
) {
343 /* Update the manual upon any changes:
344 * *colour-pager*, $PAGER */
345 if(strstr(rv
, "less") != NULL
){
346 if(!env_blook("LESS", TRU1
))
349 (pstate
& PS_TERMCAP_CA_MODE
) ? "LESS=Ri"
350 : !(pstate
& PS_TERMCAP_DISABLE
) ? "LESS=FRi" :
353 }else if(strstr(rv
, "lv") != NULL
){
354 if(!env_blook("LV", TRU1
))
355 *env_addon
= "LV=-c";
363 page_or_print(FILE *fp
, size_t lines
)
371 if ((options
& OPT_INTERACTIVE
) && (pstate
& PS_STARTED
) &&
372 (cp
= ok_vlook(crt
)) != NULL
) {
374 union {sl_i sli
; size_t rows
;} u
;
376 u
.sli
= strtol(cp
, &eptr
, 0);
377 u
.rows
= (*cp
!= '\0' && *eptr
== '\0')
378 ? (size_t)u
.sli
: (size_t)scrnheight
;
380 if (u
.rows
> 0 && lines
== 0) {
381 while ((c
= getc(fp
)) != EOF
)
382 if (c
== '\n' && ++lines
>= u
.rows
)
387 if (lines
>= u
.rows
) {
388 run_command(n_pager_get(NULL
), 0, fileno(fp
), COMMAND_FD_PASS
,
389 NULL
, NULL
, NULL
, NULL
);
394 while ((c
= getc(fp
)) != EOF
)
401 which_protocol(char const *name
) /* XXX (->URL (yet auxlily.c)) */
407 enum protocol rv
= PROTO_UNKNOWN
;
410 temporary_protocol_ext
= NULL
;
412 if (name
[0] == '%' && name
[1] == ':')
414 for (cp
= name
; *cp
&& *cp
!= ':'; cp
++)
418 if (cp
[0] == ':' && cp
[1] == '/' && cp
[2] == '/') {
419 if (!strncmp(name
, "pop3://", 7)) {
423 n_err(_("No POP3 support compiled in\n"));
425 } else if (!strncmp(name
, "pop3s://", 8)) {
426 #if defined HAVE_POP3 && defined HAVE_SSL
430 n_err(_("No POP3 support compiled in\n"));
433 n_err(_("No SSL support compiled in\n"));
440 /* TODO This is the de facto maildir code and thus belongs into there!
441 * TODO and: we should have maildir:// and mbox:// pseudo-protos, instead of
442 * TODO or (more likely) in addition to *newfolders*) */
445 np
= ac_alloc((sz
= strlen(name
)) + 4 +1);
446 memcpy(np
, name
, sz
+ 1);
447 if (!stat(name
, &st
)) {
448 if (S_ISDIR(st
.st_mode
) &&
449 (memcpy(np
+sz
, "/tmp", 5), !stat(np
, &st
) && S_ISDIR(st
.st_mode
)) &&
450 (memcpy(np
+sz
, "/new", 5), !stat(np
, &st
) && S_ISDIR(st
.st_mode
)) &&
451 (memcpy(np
+sz
, "/cur", 5), !stat(np
, &st
) && S_ISDIR(st
.st_mode
)))
454 if ((memcpy(np
+sz
, cp
=".gz", 4), !stat(np
, &st
) && S_ISREG(st
.st_mode
)) ||
455 (memcpy(np
+sz
, cp
=".xz",4), !stat(np
,&st
) && S_ISREG(st
.st_mode
)) ||
456 (memcpy(np
+sz
, cp
=".bz2",5), !stat(np
, &st
) && S_ISREG(st
.st_mode
)))
457 temporary_protocol_ext
= cp
;
458 else if ((cp
= ok_vlook(newfolders
)) != NULL
&& !strcmp(cp
, "maildir"))
468 torek_hash(char const *name
)
470 /* Chris Torek's hash.
471 * NOTE: need to change *at least* mk-okey-map.pl when changing the
476 while (*name
!= '\0') {
485 pjw(char const *cp
) /* TODO obsolete that -> torek_hash */
492 h
= (h
<< 4 & 0xffffffff) + (*cp
&0377);
493 if ((g
= h
& 0xf0000000) != 0) {
505 static ui32_t
const primes
[] = {
506 5, 11, 23, 47, 97, 157, 283,
507 509, 1021, 2039, 4093, 8191, 16381, 32749, 65521,
508 131071, 262139, 524287, 1048573, 2097143, 4194301,
509 8388593, 16777213, 33554393, 67108859, 134217689,
510 268435399, 536870909, 1073741789, 2147483647
516 i
= (n
< primes
[NELEM(primes
) / 4] ? 0
517 : (n
< primes
[NELEM(primes
) / 2] ? NELEM(primes
) / 4
518 : NELEM(primes
) / 2));
520 if ((mprime
= primes
[i
]) > n
)
522 while (++i
< NELEM(primes
));
523 if (i
== NELEM(primes
) && mprime
< n
)
530 n_shell_expand_tilde(char const *s
, bool_t
*err_or_null
)
544 if (*(rp
= s
+ 1) == '/' || *rp
== '\0')
547 if ((rp
= strchr(s
+ 1, '/')) == NULL
)
548 rp
= (np
= UNCONST(s
)) + 1;
550 nl
= PTR2SIZE(rp
- s
);
551 np
= savestrbuf(s
, nl
);
554 if ((pwp
= getpwnam(np
)) == NULL
) {
563 rv
= salloc(nl
+ 1 + rl
+1);
566 memcpy(rv
+ nl
, rp
, rl
);
575 if (err_or_null
!= NULL
)
582 n_shell_expand_var(char const *s
, bool_t bsescape
, bool_t
*err_or_null
)
584 struct shvar_stack top
;
588 memset(&top
, 0, sizeof top
);
591 if ((top
.shs_err
= err_or_null
) != NULL
)
593 top
.shs_bsesc
= bsescape
;
594 rv
= _sh_exp_var(&top
);
600 n_shell_expand_escape(char const **s
, bool_t use_nail_extensions
)
608 if ((c
= *xs
& 0xFF) == '\0')
614 switch ((c
= *xs
& 0xFF)) {
615 case 'a': c
= '\a'; break;
616 case 'b': c
= '\b'; break;
617 case 'c': c
= PROMPT_STOP
; break;
618 case 'f': c
= '\f'; break;
619 case 'n': c
= '\n'; break;
620 case 'r': c
= '\r'; break;
621 case 't': c
= '\t'; break;
622 case 'v': c
= '\v'; break;
624 /* Hexadecimal TODO uses ASCII */
627 static ui8_t
const hexatoi
[] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
630 hexatoi[(ui8_t)((n) - ((n) <= '9' ? 48 : ((n) <= 'F' ? 55 : 87)))]
638 if(options
& OPT_D_V
)
639 n_err(_("Invalid \"\\xNUMBER\" notation in \"%s\"\n"), xs
- 1);
653 /* octal, with optional 0 prefix */
663 for (c
= 0, n
= 3; n
-- > 0 && octalchar(*xs
); ++xs
) {
669 /* S-nail extension for nice (get)prompt(()) support */
674 if (use_nail_extensions
) {
676 case '&': c
= ok_blook(bsdcompat
) ? '&' : '?'; break;
677 case '?': c
= (pstate
& PS_EVAL_ERROR
) ? '1' : '0'; break;
678 case '$': c
= PROMPT_DOLLAR
; break;
679 case '@': c
= PROMPT_AT
; break;
686 /* A sole <backslash> at EOS is treated as-is! */
701 getprompt(void) /* TODO evaluate only as necessary (needs a bit) PART OF UI! */
702 { /* FIXME getprompt must mb->wc->mb+reset seq! */
703 static char buf
[PROMPT_BUFFER_SIZE
];
706 char const *ccp_base
, *ccp
;
707 size_t NATCH_CHAR( cclen_base COMMA cclen COMMA
) maxlen
, dfmaxlen
;
708 bool_t trigger
; /* 1.: `errors' ring note? 2.: first loop tick done */
711 /* No other place to place this */
713 if (options
& OPT_INTERACTIVE
) {
714 if (!(pstate
& PS_ERRORS_NOTED
) && a_aux_err_head
!= NULL
) {
715 pstate
|= PS_ERRORS_NOTED
;
716 fprintf(stderr
, _("There are new messages in the error message ring "
717 "(denoted by \"#ERR#\")\n"
718 " The `errors' command manages this message ring\n"));
721 if ((trigger
= (a_aux_err_cnt_noted
!= a_aux_err_cnt
)))
722 a_aux_err_cnt_noted
= a_aux_err_cnt
;
728 if ((ccp_base
= ok_vlook(prompt
)) == NULL
|| *ccp_base
== '\0') {
738 ccp_base
= savecatsep(_("#ERR#"), '\0', ccp_base
);
740 NATCH_CHAR( cclen_base
= strlen(ccp_base
); )
742 dfmaxlen
= 0; /* keep CC happy */
746 NATCH_CHAR( cclen
= cclen_base
; )
747 maxlen
= sizeof(buf
) -1;
755 #ifdef HAVE_NATCH_CHAR
756 c
= mblen(ccp
, cclen
); /* TODO use mbrtowc() */
765 } else if ((l
= c
) > 1) {
775 if ((c
= n_shell_expand_escape(&ccp
, TRU1
)) > 0) {
781 if (c
== 0 || c
== PROMPT_STOP
)
785 char const *a
= (c
== PROMPT_DOLLAR
) ? account_name
: displayname
;
788 if ((l
= field_put_bidi_clip(cp
, dfmaxlen
, a
, strlen(a
))) > 0) {
808 nodename(int mayoverride
)
810 static char *sys_hostname
, *hostname
; /* XXX free-at-exit */
815 # ifdef HAVE_GETADDRINFO
816 struct addrinfo hints
, *res
;
818 struct hostent
*hent
;
823 if (mayoverride
&& (hn
= ok_vlook(hostname
)) != NULL
&& *hn
!= '\0') {
825 } else if ((hn
= sys_hostname
) == NULL
) {
829 # ifdef HAVE_GETADDRINFO
830 memset(&hints
, 0, sizeof hints
);
831 hints
.ai_family
= AF_UNSPEC
;
832 hints
.ai_flags
= AI_CANONNAME
;
833 if (getaddrinfo(hn
, NULL
, &hints
, &res
) == 0) {
834 if (res
->ai_canonname
!= NULL
) {
835 size_t l
= strlen(res
->ai_canonname
) +1;
838 memcpy(hn
, res
->ai_canonname
, l
);
843 hent
= gethostbyname(hn
);
848 sys_hostname
= sstrdup(hn
);
849 #if defined HAVE_SOCKETS && defined HAVE_GETADDRINFO
850 if (hn
!= ut
.nodename
)
856 if (hostname
!= NULL
&& hostname
!= sys_hostname
)
858 hostname
= sstrdup(hn
);
864 getrandstring(size_t length
)
871 #ifndef HAVE_POSIX_RANDOM
876 /* We use our base64 encoder with _NOPAD set, so ensure the encoded result
877 * with PAD stripped is still longer than what the user requests, easy way */
878 data
= ac_alloc(i
= length
+ 3);
880 #ifndef HAVE_POSIX_RANDOM
882 data
[i
] = (char)_rand_get8();
887 union {ui32_t i4
; char c
[4];} r
;
890 r
.i4
= (ui32_t
)arc4random();
891 switch ((j
= i
& 3)) {
892 case 0: cp
[3] = r
.c
[3]; j
= 4;
893 case 3: cp
[2] = r
.c
[2];
894 case 2: cp
[1] = r
.c
[1];
895 default: cp
[0] = r
.c
[0]; break;
903 b64_encode_buf(&b64
, data
, length
+ 3,
904 B64_SALLOC
| B64_RFC4648URL
| B64_NOPAD
);
907 assert(b64
.l
>= length
);
908 b64
.s
[length
] = '\0';
914 field_detect_width(char const *buf
, size_t blen
){
919 blen
= (buf
== NULL
) ? 0 : strlen(buf
);
920 assert(blen
== 0 || buf
!= NULL
);
923 #ifdef HAVE_C90AMEND1
927 memset(&mbs
, 0, sizeof mbs
);
929 for(rv
= 0; blen
> 0;){
930 size_t i
= mbrtowc(&wc
, buf
, blen
, &mbs
);
954 rv
+= 1 + (wc
>= 0x1100u
); /* TODO use S-CText isfullwidth() */
961 #endif /* HAVE_C90AMEND1 */
968 field_detect_clip(size_t maxlen
, char const *buf
, size_t blen
)/*TODO mbrtowc()*/
973 #ifdef HAVE_NATCH_CHAR
974 maxlen
= MIN(maxlen
, blen
);
975 for (rv
= 0; maxlen
> 0;) {
976 int ml
= mblen(buf
, maxlen
);
986 rv
= MIN(blen
, maxlen
);
993 field_put_bidi_clip(char *store
, size_t maxlen
, char const *buf
, size_t blen
)
995 NATCH_CHAR( struct bidi_info bi
; )
996 size_t rv
NATCH_CHAR( COMMA i
);
1003 #ifdef HAVE_NATCH_CHAR
1004 bidi_info_create(&bi
);
1005 if (bi
.bi_start
.l
== 0 || !bidi_info_needed(buf
, blen
)) {
1010 if (maxlen
>= (i
= bi
.bi_pad
+ bi
.bi_end
.l
+ bi
.bi_start
.l
))
1015 if ((i
= bi
.bi_start
.l
) > 0) {
1016 memcpy(store
, bi
.bi_start
.s
, i
);
1022 while (maxlen
> 0) {
1023 int ml
= mblen(buf
, blen
);
1028 if (UICMP(z
, maxlen
, <, ml
))
1033 memcpy(store
, buf
, ml
);
1040 if ((i
= bi
.bi_end
.l
) > 0) {
1041 memcpy(store
, bi
.bi_end
.s
, i
);
1049 rv
= MIN(blen
, maxlen
);
1050 memcpy(store
, buf
, rv
);
1059 colalign(char const *cp
, int col
, int fill
, int *cols_decr_used_or_null
)
1061 NATCH_CHAR( struct bidi_info bi
; )
1062 int col_orig
= col
, n
, sz
;
1063 bool_t isbidi
, isuni
, istab
, isrepl
;
1067 /* Bidi only on request and when there is 8-bit data */
1068 isbidi
= isuni
= FAL0
;
1069 #ifdef HAVE_NATCH_CHAR
1070 isuni
= ((options
& OPT_UNICODE
) != 0);
1071 bidi_info_create(&bi
);
1072 if (bi
.bi_start
.l
== 0)
1074 if (!(isbidi
= bidi_info_needed(cp
, strlen(cp
))))
1077 if ((size_t)col
>= bi
.bi_pad
)
1084 np
= nb
= salloc(mb_cur_max
* strlen(cp
) +
1086 NATCH_CHAR( + (isbidi
? bi
.bi_start
.l
+ bi
.bi_end
.l
: 0) )
1089 #ifdef HAVE_NATCH_CHAR
1091 memcpy(np
, bi
.bi_start
.s
, bi
.bi_start
.l
);
1092 np
+= bi
.bi_start
.l
;
1096 while (*cp
!= '\0') {
1098 #ifdef HAVE_C90AMEND1
1099 if (mb_cur_max
> 1) {
1104 if ((sz
= mbtowc(&wc
, cp
, mb_cur_max
)) == -1)
1106 else if (wc
== L
'\t') {
1107 cp
+= sz
- 1; /* Silly, no such charset known (.. until S-Ctext) */
1110 } else if (iswprint(wc
)) {
1111 # ifndef HAVE_WCWIDTH
1112 n
= 1 + (wc
>= 0x1100u
); /* TODO use S-CText isfullwidth() */
1114 if ((n
= wcwidth(wc
)) == -1)
1124 istab
= (*cp
== '\t');
1125 isrepl
= !(istab
|| isprint((uc_i
)*cp
));
1134 np
[0] = (char)0xEFu
;
1135 np
[1] = (char)0xBFu
;
1136 np
[2] = (char)0xBDu
;
1141 } else if (istab
|| (sz
== 1 && spacechar(*cp
))) {
1149 if (fill
&& col
!= 0) {
1151 memmove(nb
+ col
, nb
, PTR2SIZE(np
- nb
));
1152 memset(nb
, ' ', col
);
1154 memset(np
, ' ', col
);
1159 #ifdef HAVE_NATCH_CHAR
1161 memcpy(np
, bi
.bi_end
.s
, bi
.bi_end
.l
);
1167 if (cols_decr_used_or_null
!= NULL
)
1168 *cols_decr_used_or_null
-= col_orig
- col
;
1174 makeprint(struct str
const *in
, struct str
*out
)
1176 char const *inp
, *maxp
;
1181 out
->s
= outp
= smalloc(DBG( msz
= ) in
->l
*mb_cur_max
+ 2u*mb_cur_max
+1);
1185 #ifdef HAVE_NATCH_CHAR
1186 if (mb_cur_max
> 1) {
1187 char mbb
[MB_LEN_MAX
+ 1];
1190 bool_t isuni
= ((options
& OPT_UNICODE
) != 0);
1193 while (inp
< maxp
) {
1195 n
= mbtowc(&wc
, inp
, PTR2SIZE(maxp
- inp
));
1201 /* FIXME Why mbtowc() resetting here?
1202 * FIXME what about ISO 2022-JP plus -- those
1203 * FIXME will loose shifts, then!
1204 * FIXME THUS - we'd need special "known points"
1205 * FIXME to do so - say, after a newline!!
1206 * FIXME WE NEED TO CHANGE ALL USES +MBLEN! */
1207 mbtowc(&wc
, NULL
, mb_cur_max
);
1208 wc
= isuni
? 0xFFFD : '?';
1213 if (!iswprint(wc
) && wc
!= '\n' && wc
!= '\r' && wc
!= '\b' &&
1215 if ((wc
& ~(wchar_t)037) == 0)
1216 wc
= isuni
? 0x2400 | wc
: '?';
1217 else if (wc
== 0177)
1218 wc
= isuni
? 0x2421 : '?';
1220 wc
= isuni
? 0x2426 : '?';
1221 }else if(isuni
){ /* TODO ctext */
1222 /* We need to actively filter out L-TO-R and R-TO-R marks TODO ctext */
1223 if(wc
== 0x200E || wc
== 0x200F || (wc
>= 0x202A && wc
<= 0x202E))
1225 /* And some zero-width messes */
1226 if(wc
== 0x00AD || (wc
>= 0x200B && wc
<= 0x200D))
1228 /* Oh about the ISO C wide character interfaces, baby! */
1232 if ((n
= wctomb(mbb
, wc
)) <= 0)
1235 assert(out
->l
< msz
);
1236 for (i
= 0; i
< n
; ++i
)
1240 #endif /* NATCH_CHAR */
1243 while (inp
< maxp
) {
1245 if (!isprint(c
) && c
!= '\n' && c
!= '\r' && c
!= '\b' && c
!= '\t')
1251 out
->s
[out
->l
] = '\0';
1256 delctrl(char *cp
, size_t len
)
1261 for (x
= y
= 0; x
< len
; ++x
)
1262 if (!cntrlchar(cp
[x
]))
1270 prstr(char const *s
)
1278 makeprint(&in
, &out
);
1279 rp
= savestrbuf(out
.s
, out
.l
);
1286 prout(char const *s
, size_t sz
, FILE *fp
)
1294 makeprint(&in
, &out
);
1295 n
= fwrite(out
.s
, 1, out
.l
, fp
);
1302 putuc(int u
, int c
, FILE *fp
)
1308 #ifdef HAVE_NATCH_CHAR
1309 if ((options
& OPT_UNICODE
) && (u
& ~(wchar_t)0177)) {
1310 char mbb
[MB_LEN_MAX
];
1313 if ((n
= wctomb(mbb
, u
)) > 0) {
1315 for (i
= 0; i
< n
; ++i
)
1316 if (putc(mbb
[i
] & 0377, fp
) == EOF
) {
1321 rv
= (putc('\0', fp
) != EOF
);
1326 rv
= (putc(c
, fp
) != EOF
);
1332 bidi_info_needed(char const *bdat
, size_t blen
)
1337 #ifdef HAVE_NATCH_CHAR
1338 if (options
& OPT_UNICODE
)
1340 /* TODO Checking for BIDI character: use S-CText fromutf8
1341 * TODO plus isrighttoleft (or whatever there will be)! */
1342 ui32_t c
= n_utf8_to_utf32(&bdat
, &blen
);
1349 /* (Very very fuzzy, awaiting S-CText for good) */
1350 if ((c
>= 0x05BE && c
<= 0x08E3) ||
1351 (c
>= 0xFB1D && c
<= 0xFE00) /* No: variation selectors */ ||
1352 (c
>= 0xFE70 && c
<= 0xFEFC) ||
1353 (c
>= 0x10800 && c
<= 0x10C48) ||
1354 (c
>= 0x1EE00 && c
<= 0x1EEF1)) {
1359 #endif /* HAVE_NATCH_CHAR */
1365 bidi_info_create(struct bidi_info
*bip
)
1367 /* Unicode: how to isolate RIGHT-TO-LEFT scripts via *headline-bidi*
1368 * 1.1 (Jun 1993): U+200E (E2 80 8E) LEFT-TO-RIGHT MARK
1369 * 6.3 (Sep 2013): U+2068 (E2 81 A8) FIRST STRONG ISOLATE,
1370 * U+2069 (E2 81 A9) POP DIRECTIONAL ISOLATE
1371 * Worse results seen for: U+202D "\xE2\x80\xAD" U+202C "\xE2\x80\xAC" */
1372 NATCH_CHAR( char const *hb
; )
1375 memset(bip
, 0, sizeof *bip
);
1376 bip
->bi_start
.s
= bip
->bi_end
.s
= UNCONST("");
1378 #ifdef HAVE_NATCH_CHAR
1379 if ((options
& OPT_UNICODE
) && (hb
= ok_vlook(headline_bidi
)) != NULL
) {
1385 bip
->bi_start
.s
= bip
->bi_end
.s
= UNCONST("\xE2\x80\x8E");
1391 bip
->bi_start
.s
= UNCONST("\xE2\x81\xA8");
1392 bip
->bi_end
.s
= UNCONST("\xE2\x81\xA9");
1395 bip
->bi_start
.l
= bip
->bi_end
.l
= 3;
1402 boolify(char const *inbuf
, uiz_t inlen
, si8_t emptyrv
)
1409 assert(inlen
== 0 || inbuf
!= NULL
);
1411 if (inlen
== UIZ_MAX
)
1412 inlen
= strlen(inbuf
);
1415 rv
= (emptyrv
>= 0) ? (emptyrv
== 0 ? 0 : 1) : -1;
1417 if ((inlen
== 1 && *inbuf
== '1') ||
1418 !ascncasecmp(inbuf
, "true", inlen
) ||
1419 !ascncasecmp(inbuf
, "yes", inlen
) ||
1420 !ascncasecmp(inbuf
, "on", inlen
))
1422 else if ((inlen
== 1 && *inbuf
== '0') ||
1423 !ascncasecmp(inbuf
, "false", inlen
) ||
1424 !ascncasecmp(inbuf
, "no", inlen
) ||
1425 !ascncasecmp(inbuf
, "off", inlen
))
1428 dat
= ac_alloc(inlen
+1);
1429 memcpy(dat
, inbuf
, inlen
);
1432 sli
= strtol(dat
, &eptr
, 0);
1433 if (*dat
!= '\0' && *eptr
== '\0')
1446 quadify(char const *inbuf
, uiz_t inlen
, char const *prompt
, si8_t emptyrv
)
1451 assert(inlen
== 0 || inbuf
!= NULL
);
1453 if (inlen
== UIZ_MAX
)
1454 inlen
= strlen(inbuf
);
1457 rv
= (emptyrv
>= 0) ? (emptyrv
== 0 ? 0 : 1) : -1;
1458 else if ((rv
= boolify(inbuf
, inlen
, -1)) < 0 &&
1459 !ascncasecmp(inbuf
, "ask-", 4) &&
1460 (rv
= boolify(inbuf
+ 4, inlen
- 4, -1)) >= 0 &&
1461 (options
& OPT_INTERACTIVE
))
1462 rv
= getapproval(prompt
, rv
);
1468 n_is_all_or_aster(char const *name
){
1472 rv
= ((name
[0] == '*' && name
[1] == '\0') || !asccasecmp(name
, "all"));
1480 #ifdef HAVE_CLOCK_GETTIME
1482 #elif defined HAVE_GETTIMEOFDAY
1488 #ifdef HAVE_CLOCK_GETTIME
1489 clock_gettime(CLOCK_REALTIME
, &ts
);
1490 rv
= (time_t)ts
.tv_sec
;
1491 #elif defined HAVE_GETTIMEOFDAY
1492 gettimeofday(&ts
, NULL
);
1493 rv
= (time_t)ts
.tv_sec
;
1502 time_current_update(struct time_current
*tc
, bool_t full_update
)
1505 tc
->tc_time
= n_time_epoch();
1507 memcpy(&tc
->tc_gm
, gmtime(&tc
->tc_time
), sizeof tc
->tc_gm
);
1508 memcpy(&tc
->tc_local
, localtime(&tc
->tc_time
), sizeof tc
->tc_local
);
1509 sstpcpy(tc
->tc_ctime
, ctime(&tc
->tc_time
));
1515 n_msleep(uiz_t millis
, bool_t ignint
){
1519 #ifdef HAVE_NANOSLEEP
1521 struct timespec ts
, trem
;
1524 ts
.tv_sec
= millis
/ 1000;
1525 ts
.tv_nsec
= (millis
%= 1000) * 1000 * 1000;
1527 while((i
= nanosleep(&ts
, &trem
)) != 0 && ignint
)
1529 rv
= (i
== 0) ? 0 : (trem
.tv_sec
* 1000) + (trem
.tv_nsec
/ (1000 * 1000));
1532 #elif defined HAVE_SLEEP
1533 if((millis
/= 1000) == 0)
1535 while((rv
= sleep((unsigned int)millis
)) != 0 && ignint
)
1538 # error Configuration should have detected a function for sleeping.
1546 n_err(char const *format
, ...){
1550 va_start(ap
, format
);
1552 if(options
& OPT_INTERACTIVE
)
1557 if(a_aux_err_dirty
++ == 0)
1558 fputs(UAGENT
": ", stderr
);
1559 vfprintf(stderr
, format
, ap
);
1560 if(strchr(format
, '\n') != NULL
){ /* TODO */
1561 a_aux_err_dirty
= 0;
1570 n_verr(char const *format
, va_list ap
){
1571 /* Check use cases of PS_ERRORS_NOTED, too! */
1573 char buf
[LINESIZE
], *xbuf
;
1575 struct a_aux_err_node
*enp
;
1577 LCTA(ERRORS_MAX
> 3);
1581 if(a_aux_err_dirty
++ == 0)
1582 fputs(UAGENT
": ", stderr
);
1585 if(!(options
& OPT_INTERACTIVE
))
1588 vfprintf(stderr
, format
, ap
);
1596 l
= vsnprintf(xbuf
, lmax
, format
, ap
);
1599 if (UICMP(z
, l
, >=, lmax
)) {
1600 /* FIXME Cannot reuse va_list
1602 xbuf = srealloc((xbuf == buf ? NULL : xbuf), lmax);
1607 fwrite(xbuf
, 1, l
, stderr
);
1609 /* Link it into the `errors' message ring */
1610 if((enp
= a_aux_err_tail
) == NULL
){
1612 enp
= scalloc(1, sizeof *enp
);
1613 if(a_aux_err_tail
!= NULL
)
1614 a_aux_err_tail
->ae_next
= enp
;
1616 a_aux_err_head
= enp
;
1617 a_aux_err_tail
= enp
;
1619 }else if(enp
->ae_str
.l
> 0 && enp
->ae_str
.s
[enp
->ae_str
.l
- 1] == '\n'){
1620 if(a_aux_err_cnt
< ERRORS_MAX
)
1623 a_aux_err_head
= (enp
= a_aux_err_head
)->ae_next
;
1624 a_aux_err_tail
->ae_next
= enp
;
1625 a_aux_err_tail
= enp
;
1626 free(enp
->ae_str
.s
);
1627 memset(enp
, 0, sizeof *enp
);
1630 n_str_add_buf(&enp
->ae_str
, xbuf
, l
);
1634 #endif /* HAVE_ERRORS */
1637 /* If the format ends with newline, be clean again */
1639 size_t i
= strlen(format
);
1641 if(i
> 0 && format
[i
- 1] == '\n'){
1643 a_aux_err_dirty
= 0;
1650 n_err_sighdl(char const *format
, ...){ /* TODO sigsafe; obsolete! */
1654 va_start(ap
, format
);
1655 vfprintf(stderr
, format
, ap
);
1661 n_perr(char const *msg
, int errval
){
1674 n_err(fmt
, msg
, strerror(errval
));
1679 n_alert(char const *format
, ...){
1683 n_err(a_aux_err_dirty
> 0 ? _("\nAlert: ") : _("Alert: "));
1685 va_start(ap
, format
);
1694 n_panic(char const *format
, ...){
1698 if(a_aux_err_dirty
> 0){
1700 a_aux_err_dirty
= 0;
1702 fprintf(stderr
, UAGENT
": Panic: ");
1704 va_start(ap
, format
);
1705 vfprintf(stderr
, format
, ap
);
1711 abort(); /* Was exit(EXIT_ERR); for a while, but no */
1718 struct a_aux_err_node
*enp
;
1725 if(!asccasecmp(*argv
, "show"))
1727 if(!asccasecmp(*argv
, "clear"))
1730 fprintf(stderr
, _("Synopsis: errors: (<show> or) <clear> the error ring\n"));
1734 return v
== NULL
? !STOP
: !OKAY
; /* xxx 1:bad 0:good -- do some */
1740 if(a_aux_err_head
== NULL
){
1741 fprintf(stderr
, _("The error ring is empty\n"));
1745 if((fp
= Ftmp(NULL
, "errors", OF_RDWR
| OF_UNLINK
| OF_REGISTER
)) ==
1747 fprintf(stderr
, _("tmpfile"));
1752 for(i
= 0, enp
= a_aux_err_head
; enp
!= NULL
; enp
= enp
->ae_next
)
1753 fprintf(fp
, "- %4" PRIuZ
". %" PRIuZ
" bytes: %s",
1754 ++i
, enp
->ae_str
.l
, enp
->ae_str
.s
);
1755 /* We don't know wether last string ended with NL; be simple */
1758 page_or_print(fp
, 0);
1764 a_aux_err_tail
= NULL
;
1765 a_aux_err_cnt
= a_aux_err_cnt_noted
= 0;
1766 while((enp
= a_aux_err_head
) != NULL
){
1767 a_aux_err_head
= enp
->ae_next
;
1768 free(enp
->ae_str
.s
);
1773 #endif /* HAVE_ERRORS */