1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Auxiliary functions.
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 is_dir(char const *name
)
314 if (!stat(name
, &sbuf
)) {
315 rv
= (S_ISDIR(sbuf
.st_mode
) != 0);
317 } else if (errno
!= EINTR
)
324 argcount(char **argv
)
329 for (ap
= argv
; *ap
++ != NULL
;)
332 return (int)PTR2SIZE(ap
- argv
- 1);
342 if ((cp
= ok_vlook(screen
)) == NULL
|| (s
= atoi(cp
)) <= 0)
343 s
= scrnheight
- 2; /* XXX no magics */
349 n_pager_get(char const **env_addon
)
354 cp
= ok_vlook(PAGER
);
355 if (cp
== NULL
|| *cp
== '\0')
358 if (env_addon
!= NULL
) {
360 /* Update the manual upon any changes:
361 * *colour-pager*, $PAGER */
362 if(strstr(rv
, "less") != NULL
){
363 if(!env_blook("LESS", TRU1
))
366 (pstate
& PS_TERMCAP_CA_MODE
) ? "LESS=Ri"
367 : !(pstate
& PS_TERMCAP_DISABLE
) ? "LESS=FRi" :
370 }else if(strstr(rv
, "lv") != NULL
){
371 if(!env_blook("LV", TRU1
))
372 *env_addon
= "LV=-c";
380 page_or_print(FILE *fp
, size_t lines
)
388 if ((options
& OPT_INTERACTIVE
) && (pstate
& PS_STARTED
) &&
389 (cp
= ok_vlook(crt
)) != NULL
) {
391 union {sl_i sli
; size_t rows
;} u
;
393 u
.sli
= strtol(cp
, &eptr
, 0);
394 u
.rows
= (*cp
!= '\0' && *eptr
== '\0')
395 ? (size_t)u
.sli
: (size_t)scrnheight
;
397 if (u
.rows
> 0 && lines
== 0) {
398 while ((c
= getc(fp
)) != EOF
)
399 if (c
== '\n' && ++lines
>= u
.rows
)
404 if (lines
>= u
.rows
) {
405 run_command(n_pager_get(NULL
), 0, fileno(fp
), COMMAND_FD_PASS
,
406 NULL
, NULL
, NULL
, NULL
);
411 while ((c
= getc(fp
)) != EOF
)
418 which_protocol(char const *name
) /* XXX (->URL (yet auxlily.c)) */
424 enum protocol rv
= PROTO_UNKNOWN
;
427 temporary_protocol_ext
= NULL
;
429 if (name
[0] == '%' && name
[1] == ':')
431 for (cp
= name
; *cp
&& *cp
!= ':'; cp
++)
435 if (cp
[0] == ':' && cp
[1] == '/' && cp
[2] == '/') {
436 if (!strncmp(name
, "pop3://", 7)) {
440 n_err(_("No POP3 support compiled in\n"));
442 } else if (!strncmp(name
, "pop3s://", 8)) {
443 #if defined HAVE_POP3 && defined HAVE_SSL
447 n_err(_("No POP3 support compiled in\n"));
450 n_err(_("No SSL support compiled in\n"));
457 /* TODO This is the de facto maildir code and thus belongs into there!
458 * TODO and: we should have maildir:// and mbox:// pseudo-protos, instead of
459 * TODO or (more likely) in addition to *newfolders*) */
462 np
= ac_alloc((sz
= strlen(name
)) + 4 +1);
463 memcpy(np
, name
, sz
+ 1);
464 if (!stat(name
, &st
)) {
465 if (S_ISDIR(st
.st_mode
) &&
466 (memcpy(np
+sz
, "/tmp", 5), !stat(np
, &st
) && S_ISDIR(st
.st_mode
)) &&
467 (memcpy(np
+sz
, "/new", 5), !stat(np
, &st
) && S_ISDIR(st
.st_mode
)) &&
468 (memcpy(np
+sz
, "/cur", 5), !stat(np
, &st
) && S_ISDIR(st
.st_mode
)))
471 if ((memcpy(np
+sz
, cp
=".gz", 4), !stat(np
, &st
) && S_ISREG(st
.st_mode
)) ||
472 (memcpy(np
+sz
, cp
=".xz",4), !stat(np
,&st
) && S_ISREG(st
.st_mode
)) ||
473 (memcpy(np
+sz
, cp
=".bz2",5), !stat(np
, &st
) && S_ISREG(st
.st_mode
)))
474 temporary_protocol_ext
= cp
;
475 else if ((cp
= ok_vlook(newfolders
)) != NULL
&& !strcmp(cp
, "maildir"))
485 torek_hash(char const *name
)
487 /* Chris Torek's hash.
488 * NOTE: need to change *at least* mk-okey-map.pl when changing the
493 while (*name
!= '\0') {
502 pjw(char const *cp
) /* TODO obsolete that -> torek_hash */
509 h
= (h
<< 4 & 0xffffffff) + (*cp
&0377);
510 if ((g
= h
& 0xf0000000) != 0) {
522 static ui32_t
const primes
[] = {
523 5, 11, 23, 47, 97, 157, 283,
524 509, 1021, 2039, 4093, 8191, 16381, 32749, 65521,
525 131071, 262139, 524287, 1048573, 2097143, 4194301,
526 8388593, 16777213, 33554393, 67108859, 134217689,
527 268435399, 536870909, 1073741789, 2147483647
533 i
= (n
< primes
[NELEM(primes
) / 4] ? 0
534 : (n
< primes
[NELEM(primes
) / 2] ? NELEM(primes
) / 4
535 : NELEM(primes
) / 2));
537 if ((mprime
= primes
[i
]) > n
)
539 while (++i
< NELEM(primes
));
540 if (i
== NELEM(primes
) && mprime
< n
)
547 n_shell_expand_tilde(char const *s
, bool_t
*err_or_null
)
561 if (*(rp
= s
+ 1) == '/' || *rp
== '\0')
564 if ((rp
= strchr(s
+ 1, '/')) == NULL
)
565 rp
= (np
= UNCONST(s
)) + 1;
567 nl
= PTR2SIZE(rp
- s
);
568 np
= savestrbuf(s
, nl
);
571 if ((pwp
= getpwnam(np
)) == NULL
) {
580 rv
= salloc(nl
+ 1 + rl
+1);
583 memcpy(rv
+ nl
, rp
, rl
);
592 if (err_or_null
!= NULL
)
599 n_shell_expand_var(char const *s
, bool_t bsescape
, bool_t
*err_or_null
)
601 struct shvar_stack top
;
605 memset(&top
, 0, sizeof top
);
608 if ((top
.shs_err
= err_or_null
) != NULL
)
610 top
.shs_bsesc
= bsescape
;
611 rv
= _sh_exp_var(&top
);
617 n_shell_expand_escape(char const **s
, bool_t use_nail_extensions
)
625 if ((c
= *xs
& 0xFF) == '\0')
631 switch ((c
= *xs
& 0xFF)) {
632 case 'a': c
= '\a'; break;
633 case 'b': c
= '\b'; break;
634 case 'c': c
= PROMPT_STOP
; break;
635 case 'f': c
= '\f'; break;
636 case 'n': c
= '\n'; break;
637 case 'r': c
= '\r'; break;
638 case 't': c
= '\t'; break;
639 case 'v': c
= '\v'; break;
641 /* Hexadecimal TODO uses ASCII */
644 static ui8_t
const hexatoi
[] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
647 hexatoi[(ui8_t)((n) - ((n) <= '9' ? 48 : ((n) <= 'F' ? 55 : 87)))]
655 if(options
& OPT_D_V
)
656 n_err(_("Invalid \"\\xNUMBER\" notation in \"%s\"\n"), xs
- 1);
670 /* octal, with optional 0 prefix */
680 for (c
= 0, n
= 3; n
-- > 0 && octalchar(*xs
); ++xs
) {
686 /* S-nail extension for nice (get)prompt(()) support */
691 if (use_nail_extensions
) {
693 case '&': c
= ok_blook(bsdcompat
) ? '&' : '?'; break;
694 case '?': c
= (pstate
& PS_EVAL_ERROR
) ? '1' : '0'; break;
695 case '$': c
= PROMPT_DOLLAR
; break;
696 case '@': c
= PROMPT_AT
; break;
703 /* A sole <backslash> at EOS is treated as-is! */
718 getprompt(void) /* TODO evaluate only as necessary (needs a bit) PART OF UI! */
719 { /* FIXME getprompt must mb->wc->mb+reset seq! */
720 static char buf
[PROMPT_BUFFER_SIZE
];
723 char const *ccp_base
, *ccp
;
724 size_t NATCH_CHAR( cclen_base COMMA cclen COMMA
) maxlen
, dfmaxlen
;
725 bool_t trigger
; /* 1.: `errors' ring note? 2.: first loop tick done */
728 /* No other place to place this */
730 if (options
& OPT_INTERACTIVE
) {
731 if (!(pstate
& PS_ERRORS_NOTED
) && a_aux_err_head
!= NULL
) {
732 pstate
|= PS_ERRORS_NOTED
;
733 fprintf(stderr
, _("There are new messages in the error message ring "
734 "(denoted by \"#ERR#\")\n"
735 " The `errors' command manages this message ring\n"));
738 if ((trigger
= (a_aux_err_cnt_noted
!= a_aux_err_cnt
)))
739 a_aux_err_cnt_noted
= a_aux_err_cnt
;
745 if ((ccp_base
= ok_vlook(prompt
)) == NULL
|| *ccp_base
== '\0') {
755 ccp_base
= savecatsep(_("#ERR#"), '\0', ccp_base
);
757 NATCH_CHAR( cclen_base
= strlen(ccp_base
); )
759 dfmaxlen
= 0; /* keep CC happy */
763 NATCH_CHAR( cclen
= cclen_base
; )
764 maxlen
= sizeof(buf
) -1;
772 #ifdef HAVE_NATCH_CHAR
773 c
= mblen(ccp
, cclen
); /* TODO use mbrtowc() */
782 } else if ((l
= c
) > 1) {
792 if ((c
= n_shell_expand_escape(&ccp
, TRU1
)) > 0) {
798 if (c
== 0 || c
== PROMPT_STOP
)
802 char const *a
= (c
== PROMPT_DOLLAR
) ? account_name
: displayname
;
805 if ((l
= field_put_bidi_clip(cp
, dfmaxlen
, a
, strlen(a
))) > 0) {
825 nodename(int mayoverride
)
827 static char *sys_hostname
, *hostname
; /* XXX free-at-exit */
832 # ifdef HAVE_GETADDRINFO
833 struct addrinfo hints
, *res
;
835 struct hostent
*hent
;
840 if (mayoverride
&& (hn
= ok_vlook(hostname
)) != NULL
&& *hn
!= '\0') {
842 } else if ((hn
= sys_hostname
) == NULL
) {
846 # ifdef HAVE_GETADDRINFO
847 memset(&hints
, 0, sizeof hints
);
848 hints
.ai_family
= AF_UNSPEC
;
849 hints
.ai_flags
= AI_CANONNAME
;
850 if (getaddrinfo(hn
, NULL
, &hints
, &res
) == 0) {
851 if (res
->ai_canonname
!= NULL
) {
852 size_t l
= strlen(res
->ai_canonname
) +1;
855 memcpy(hn
, res
->ai_canonname
, l
);
860 hent
= gethostbyname(hn
);
865 sys_hostname
= sstrdup(hn
);
866 #if defined HAVE_SOCKETS && defined HAVE_GETADDRINFO
867 if (hn
!= ut
.nodename
)
873 if (hostname
!= NULL
&& hostname
!= sys_hostname
)
875 hostname
= sstrdup(hn
);
881 getrandstring(size_t length
)
888 #ifndef HAVE_POSIX_RANDOM
893 /* We use our base64 encoder with _NOPAD set, so ensure the encoded result
894 * with PAD stripped is still longer than what the user requests, easy way */
895 data
= ac_alloc(i
= length
+ 3);
897 #ifndef HAVE_POSIX_RANDOM
899 data
[i
] = (char)_rand_get8();
904 union {ui32_t i4
; char c
[4];} r
;
907 r
.i4
= (ui32_t
)arc4random();
908 switch ((j
= i
& 3)) {
909 case 0: cp
[3] = r
.c
[3]; j
= 4;
910 case 3: cp
[2] = r
.c
[2];
911 case 2: cp
[1] = r
.c
[1];
912 default: cp
[0] = r
.c
[0]; break;
920 b64_encode_buf(&b64
, data
, length
+ 3,
921 B64_SALLOC
| B64_RFC4648URL
| B64_NOPAD
);
924 assert(b64
.l
>= length
);
925 b64
.s
[length
] = '\0';
931 makedir(char const *name
)
937 if (!mkdir(name
, 0700))
941 if ((e
== EEXIST
|| e
== ENOSYS
) && !stat(name
, &st
) &&
956 if ((cw
->cw_fd
= open(".", O_RDONLY
)) == -1)
958 if (fchdir(cw
->cw_fd
) == -1) {
974 if (!fchdir(cw
->cw_fd
))
981 cwrelse(struct cw
*cw
)
988 #else /* !HAVE_FCHDIR */
995 if (getcwd(cw
->cw_wd
, sizeof cw
->cw_wd
) != NULL
&& !chdir(cw
->cw_wd
))
1002 cwret(struct cw
*cw
)
1004 enum okay rv
= STOP
;
1007 if (!chdir(cw
->cw_wd
))
1014 cwrelse(struct cw
*cw
)
1020 #endif /* !HAVE_FCHDIR */
1023 field_detect_width(char const *buf
, size_t blen
){
1028 blen
= (buf
== NULL
) ? 0 : strlen(buf
);
1029 assert(blen
== 0 || buf
!= NULL
);
1031 if((rv
= blen
) > 0){
1032 #ifdef HAVE_C90AMEND1
1036 memset(&mbs
, 0, sizeof mbs
);
1038 for(rv
= 0; blen
> 0;){
1039 size_t i
= mbrtowc(&wc
, buf
, blen
, &mbs
);
1052 # ifdef HAVE_WCWIDTH
1054 int w
= wcwidth(wc
);
1063 rv
+= 1 + (wc
>= 0x1100u
); /* TODO use S-CText isfullwidth() */
1070 #endif /* HAVE_C90AMEND1 */
1077 field_detect_clip(size_t maxlen
, char const *buf
, size_t blen
)/*TODO mbrtowc()*/
1082 #ifdef HAVE_NATCH_CHAR
1083 maxlen
= MIN(maxlen
, blen
);
1084 for (rv
= 0; maxlen
> 0;) {
1085 int ml
= mblen(buf
, maxlen
);
1095 rv
= MIN(blen
, maxlen
);
1102 field_put_bidi_clip(char *store
, size_t maxlen
, char const *buf
, size_t blen
)
1104 NATCH_CHAR( struct bidi_info bi
; )
1105 size_t rv
NATCH_CHAR( COMMA i
);
1112 #ifdef HAVE_NATCH_CHAR
1113 bidi_info_create(&bi
);
1114 if (bi
.bi_start
.l
== 0 || !bidi_info_needed(buf
, blen
)) {
1119 if (maxlen
>= (i
= bi
.bi_pad
+ bi
.bi_end
.l
+ bi
.bi_start
.l
))
1124 if ((i
= bi
.bi_start
.l
) > 0) {
1125 memcpy(store
, bi
.bi_start
.s
, i
);
1131 while (maxlen
> 0) {
1132 int ml
= mblen(buf
, blen
);
1137 if (UICMP(z
, maxlen
, <, ml
))
1142 memcpy(store
, buf
, ml
);
1149 if ((i
= bi
.bi_end
.l
) > 0) {
1150 memcpy(store
, bi
.bi_end
.s
, i
);
1158 rv
= MIN(blen
, maxlen
);
1159 memcpy(store
, buf
, rv
);
1168 colalign(char const *cp
, int col
, int fill
, int *cols_decr_used_or_null
)
1170 NATCH_CHAR( struct bidi_info bi
; )
1171 int col_orig
= col
, n
, sz
;
1172 bool_t isbidi
, isuni
, istab
, isrepl
;
1176 /* Bidi only on request and when there is 8-bit data */
1177 isbidi
= isuni
= FAL0
;
1178 #ifdef HAVE_NATCH_CHAR
1179 isuni
= ((options
& OPT_UNICODE
) != 0);
1180 bidi_info_create(&bi
);
1181 if (bi
.bi_start
.l
== 0)
1183 if (!(isbidi
= bidi_info_needed(cp
, strlen(cp
))))
1186 if ((size_t)col
>= bi
.bi_pad
)
1193 np
= nb
= salloc(mb_cur_max
* strlen(cp
) +
1195 NATCH_CHAR( + (isbidi
? bi
.bi_start
.l
+ bi
.bi_end
.l
: 0) )
1198 #ifdef HAVE_NATCH_CHAR
1200 memcpy(np
, bi
.bi_start
.s
, bi
.bi_start
.l
);
1201 np
+= bi
.bi_start
.l
;
1205 while (*cp
!= '\0') {
1207 #ifdef HAVE_C90AMEND1
1208 if (mb_cur_max
> 1) {
1213 if ((sz
= mbtowc(&wc
, cp
, mb_cur_max
)) == -1)
1215 else if (wc
== L
'\t') {
1216 cp
+= sz
- 1; /* Silly, no such charset known (.. until S-Ctext) */
1219 } else if (iswprint(wc
)) {
1220 # ifndef HAVE_WCWIDTH
1221 n
= 1 + (wc
>= 0x1100u
); /* TODO use S-CText isfullwidth() */
1223 if ((n
= wcwidth(wc
)) == -1)
1233 istab
= (*cp
== '\t');
1234 isrepl
= !(istab
|| isprint((uc_i
)*cp
));
1243 np
[0] = (char)0xEFu
;
1244 np
[1] = (char)0xBFu
;
1245 np
[2] = (char)0xBDu
;
1250 } else if (istab
|| (sz
== 1 && spacechar(*cp
))) {
1258 if (fill
&& col
!= 0) {
1260 memmove(nb
+ col
, nb
, PTR2SIZE(np
- nb
));
1261 memset(nb
, ' ', col
);
1263 memset(np
, ' ', col
);
1268 #ifdef HAVE_NATCH_CHAR
1270 memcpy(np
, bi
.bi_end
.s
, bi
.bi_end
.l
);
1276 if (cols_decr_used_or_null
!= NULL
)
1277 *cols_decr_used_or_null
-= col_orig
- col
;
1283 makeprint(struct str
const *in
, struct str
*out
)
1285 char const *inp
, *maxp
;
1290 out
->s
= outp
= smalloc(DBG( msz
= ) in
->l
*mb_cur_max
+ 2u*mb_cur_max
+1);
1294 #ifdef HAVE_NATCH_CHAR
1295 if (mb_cur_max
> 1) {
1296 char mbb
[MB_LEN_MAX
+ 1];
1299 bool_t isuni
= ((options
& OPT_UNICODE
) != 0);
1302 while (inp
< maxp
) {
1304 n
= mbtowc(&wc
, inp
, PTR2SIZE(maxp
- inp
));
1310 /* FIXME Why mbtowc() resetting here?
1311 * FIXME what about ISO 2022-JP plus -- those
1312 * FIXME will loose shifts, then!
1313 * FIXME THUS - we'd need special "known points"
1314 * FIXME to do so - say, after a newline!!
1315 * FIXME WE NEED TO CHANGE ALL USES +MBLEN! */
1316 mbtowc(&wc
, NULL
, mb_cur_max
);
1317 wc
= isuni
? 0xFFFD : '?';
1322 if (!iswprint(wc
) && wc
!= '\n' && wc
!= '\r' && wc
!= '\b' &&
1324 if ((wc
& ~(wchar_t)037) == 0)
1325 wc
= isuni
? 0x2400 | wc
: '?';
1326 else if (wc
== 0177)
1327 wc
= isuni
? 0x2421 : '?';
1329 wc
= isuni
? 0x2426 : '?';
1330 }else if(isuni
){ /* TODO ctext */
1331 /* We need to actively filter out L-TO-R and R-TO-R marks TODO ctext */
1332 if(wc
== 0x200E || wc
== 0x200F || (wc
>= 0x202A && wc
<= 0x202E))
1334 /* And some zero-width messes */
1335 if(wc
== 0x00AD || (wc
>= 0x200B && wc
<= 0x200D))
1337 /* Oh about the ISO C wide character interfaces, baby! */
1341 if ((n
= wctomb(mbb
, wc
)) <= 0)
1344 assert(out
->l
< msz
);
1345 for (i
= 0; i
< n
; ++i
)
1349 #endif /* NATCH_CHAR */
1352 while (inp
< maxp
) {
1354 if (!isprint(c
) && c
!= '\n' && c
!= '\r' && c
!= '\b' && c
!= '\t')
1360 out
->s
[out
->l
] = '\0';
1365 delctrl(char *cp
, size_t len
)
1370 for (x
= y
= 0; x
< len
; ++x
)
1371 if (!cntrlchar(cp
[x
]))
1379 prstr(char const *s
)
1387 makeprint(&in
, &out
);
1388 rp
= savestrbuf(out
.s
, out
.l
);
1395 prout(char const *s
, size_t sz
, FILE *fp
)
1403 makeprint(&in
, &out
);
1404 n
= fwrite(out
.s
, 1, out
.l
, fp
);
1411 putuc(int u
, int c
, FILE *fp
)
1417 #ifdef HAVE_NATCH_CHAR
1418 if ((options
& OPT_UNICODE
) && (u
& ~(wchar_t)0177)) {
1419 char mbb
[MB_LEN_MAX
];
1422 if ((n
= wctomb(mbb
, u
)) > 0) {
1424 for (i
= 0; i
< n
; ++i
)
1425 if (putc(mbb
[i
] & 0377, fp
) == EOF
) {
1430 rv
= (putc('\0', fp
) != EOF
);
1435 rv
= (putc(c
, fp
) != EOF
);
1441 bidi_info_needed(char const *bdat
, size_t blen
)
1446 #ifdef HAVE_NATCH_CHAR
1447 if (options
& OPT_UNICODE
)
1449 /* TODO Checking for BIDI character: use S-CText fromutf8
1450 * TODO plus isrighttoleft (or whatever there will be)! */
1451 ui32_t c
= n_utf8_to_utf32(&bdat
, &blen
);
1458 /* (Very very fuzzy, awaiting S-CText for good) */
1459 if ((c
>= 0x05BE && c
<= 0x08E3) ||
1460 (c
>= 0xFB1D && c
<= 0xFE00) /* No: variation selectors */ ||
1461 (c
>= 0xFE70 && c
<= 0xFEFC) ||
1462 (c
>= 0x10800 && c
<= 0x10C48) ||
1463 (c
>= 0x1EE00 && c
<= 0x1EEF1)) {
1468 #endif /* HAVE_NATCH_CHAR */
1474 bidi_info_create(struct bidi_info
*bip
)
1476 /* Unicode: how to isolate RIGHT-TO-LEFT scripts via *headline-bidi*
1477 * 1.1 (Jun 1993): U+200E (E2 80 8E) LEFT-TO-RIGHT MARK
1478 * 6.3 (Sep 2013): U+2068 (E2 81 A8) FIRST STRONG ISOLATE,
1479 * U+2069 (E2 81 A9) POP DIRECTIONAL ISOLATE
1480 * Worse results seen for: U+202D "\xE2\x80\xAD" U+202C "\xE2\x80\xAC" */
1481 NATCH_CHAR( char const *hb
; )
1484 memset(bip
, 0, sizeof *bip
);
1485 bip
->bi_start
.s
= bip
->bi_end
.s
= UNCONST("");
1487 #ifdef HAVE_NATCH_CHAR
1488 if ((options
& OPT_UNICODE
) && (hb
= ok_vlook(headline_bidi
)) != NULL
) {
1494 bip
->bi_start
.s
= bip
->bi_end
.s
= UNCONST("\xE2\x80\x8E");
1500 bip
->bi_start
.s
= UNCONST("\xE2\x81\xA8");
1501 bip
->bi_end
.s
= UNCONST("\xE2\x81\xA9");
1504 bip
->bi_start
.l
= bip
->bi_end
.l
= 3;
1511 boolify(char const *inbuf
, uiz_t inlen
, si8_t emptyrv
)
1518 assert(inlen
== 0 || inbuf
!= NULL
);
1520 if (inlen
== UIZ_MAX
)
1521 inlen
= strlen(inbuf
);
1524 rv
= (emptyrv
>= 0) ? (emptyrv
== 0 ? 0 : 1) : -1;
1526 if ((inlen
== 1 && *inbuf
== '1') ||
1527 !ascncasecmp(inbuf
, "true", inlen
) ||
1528 !ascncasecmp(inbuf
, "yes", inlen
) ||
1529 !ascncasecmp(inbuf
, "on", inlen
))
1531 else if ((inlen
== 1 && *inbuf
== '0') ||
1532 !ascncasecmp(inbuf
, "false", inlen
) ||
1533 !ascncasecmp(inbuf
, "no", inlen
) ||
1534 !ascncasecmp(inbuf
, "off", inlen
))
1537 dat
= ac_alloc(inlen
+1);
1538 memcpy(dat
, inbuf
, inlen
);
1541 sli
= strtol(dat
, &eptr
, 0);
1542 if (*dat
!= '\0' && *eptr
== '\0')
1555 quadify(char const *inbuf
, uiz_t inlen
, char const *prompt
, si8_t emptyrv
)
1560 assert(inlen
== 0 || inbuf
!= NULL
);
1562 if (inlen
== UIZ_MAX
)
1563 inlen
= strlen(inbuf
);
1566 rv
= (emptyrv
>= 0) ? (emptyrv
== 0 ? 0 : 1) : -1;
1567 else if ((rv
= boolify(inbuf
, inlen
, -1)) < 0 &&
1568 !ascncasecmp(inbuf
, "ask-", 4) &&
1569 (rv
= boolify(inbuf
+ 4, inlen
- 4, -1)) >= 0 &&
1570 (options
& OPT_INTERACTIVE
))
1571 rv
= getapproval(prompt
, rv
);
1577 n_is_all_or_aster(char const *name
){
1581 rv
= ((name
[0] == '*' && name
[1] == '\0') || !asccasecmp(name
, "all"));
1589 #ifdef HAVE_CLOCK_GETTIME
1591 #elif defined HAVE_GETTIMEOFDAY
1597 #ifdef HAVE_CLOCK_GETTIME
1598 clock_gettime(CLOCK_REALTIME
, &ts
);
1599 rv
= (time_t)ts
.tv_sec
;
1600 #elif defined HAVE_GETTIMEOFDAY
1601 gettimeofday(&ts
, NULL
);
1602 rv
= (time_t)ts
.tv_sec
;
1611 time_current_update(struct time_current
*tc
, bool_t full_update
)
1614 tc
->tc_time
= n_time_epoch();
1616 memcpy(&tc
->tc_gm
, gmtime(&tc
->tc_time
), sizeof tc
->tc_gm
);
1617 memcpy(&tc
->tc_local
, localtime(&tc
->tc_time
), sizeof tc
->tc_local
);
1618 sstpcpy(tc
->tc_ctime
, ctime(&tc
->tc_time
));
1624 n_msleep(uiz_t millis
, bool_t ignint
){
1628 #ifdef HAVE_NANOSLEEP
1630 struct timespec ts
, trem
;
1633 ts
.tv_sec
= millis
/ 1000;
1634 ts
.tv_nsec
= (millis
%= 1000) * 1000 * 1000;
1636 while((i
= nanosleep(&ts
, &trem
)) != 0 && ignint
)
1638 rv
= (i
== 0) ? 0 : (trem
.tv_sec
* 1000) + (trem
.tv_nsec
/ (1000 * 1000));
1641 #elif defined HAVE_SLEEP
1642 if((millis
/= 1000) == 0)
1644 while((rv
= sleep((unsigned int)millis
)) != 0 && ignint
)
1647 # error Configuration should have detected a function for sleeping.
1655 n_err(char const *format
, ...){
1659 va_start(ap
, format
);
1661 if(options
& OPT_INTERACTIVE
)
1666 if(a_aux_err_dirty
++ == 0)
1667 fputs(UAGENT
": ", stderr
);
1668 vfprintf(stderr
, format
, ap
);
1669 if(strchr(format
, '\n') != NULL
){ /* TODO */
1670 a_aux_err_dirty
= 0;
1679 n_verr(char const *format
, va_list ap
){
1680 /* Check use cases of PS_ERRORS_NOTED, too! */
1682 char buf
[LINESIZE
], *xbuf
;
1684 struct a_aux_err_node
*enp
;
1686 LCTA(ERRORS_MAX
> 3);
1690 if(a_aux_err_dirty
++ == 0)
1691 fputs(UAGENT
": ", stderr
);
1694 if(!(options
& OPT_INTERACTIVE
))
1697 vfprintf(stderr
, format
, ap
);
1705 l
= vsnprintf(xbuf
, lmax
, format
, ap
);
1708 if (UICMP(z
, l
, >=, lmax
)) {
1709 /* FIXME Cannot reuse va_list
1711 xbuf = srealloc((xbuf == buf ? NULL : xbuf), lmax);
1716 fwrite(xbuf
, 1, l
, stderr
);
1718 /* Link it into the `errors' message ring */
1719 if((enp
= a_aux_err_tail
) == NULL
){
1721 enp
= scalloc(1, sizeof *enp
);
1722 if(a_aux_err_tail
!= NULL
)
1723 a_aux_err_tail
->ae_next
= enp
;
1725 a_aux_err_head
= enp
;
1726 a_aux_err_tail
= enp
;
1728 }else if(enp
->ae_str
.l
> 0 && enp
->ae_str
.s
[enp
->ae_str
.l
- 1] == '\n'){
1729 if(a_aux_err_cnt
< ERRORS_MAX
)
1732 a_aux_err_head
= (enp
= a_aux_err_head
)->ae_next
;
1733 a_aux_err_tail
->ae_next
= enp
;
1734 a_aux_err_tail
= enp
;
1735 free(enp
->ae_str
.s
);
1736 memset(enp
, 0, sizeof *enp
);
1739 n_str_add_buf(&enp
->ae_str
, xbuf
, l
);
1743 #endif /* HAVE_ERRORS */
1746 /* If the format ends with newline, be clean again */
1748 size_t i
= strlen(format
);
1750 if(i
> 0 && format
[i
- 1] == '\n'){
1752 a_aux_err_dirty
= 0;
1759 n_err_sighdl(char const *format
, ...){ /* TODO sigsafe; obsolete! */
1763 va_start(ap
, format
);
1764 vfprintf(stderr
, format
, ap
);
1770 n_perr(char const *msg
, int errval
){
1783 n_err(fmt
, msg
, strerror(errval
));
1788 n_alert(char const *format
, ...){
1792 n_err(a_aux_err_dirty
> 0 ? _("\nAlert: ") : _("Alert: "));
1794 va_start(ap
, format
);
1803 n_panic(char const *format
, ...){
1807 if(a_aux_err_dirty
> 0){
1809 a_aux_err_dirty
= 0;
1811 fprintf(stderr
, UAGENT
": Panic: ");
1813 va_start(ap
, format
);
1814 vfprintf(stderr
, format
, ap
);
1820 abort(); /* Was exit(EXIT_ERR); for a while, but no */
1827 struct a_aux_err_node
*enp
;
1834 if(!asccasecmp(*argv
, "show"))
1836 if(!asccasecmp(*argv
, "clear"))
1839 fprintf(stderr
, _("Synopsis: errors: (<show> or) <clear> the error ring\n"));
1843 return v
== NULL
? !STOP
: !OKAY
; /* xxx 1:bad 0:good -- do some */
1849 if(a_aux_err_head
== NULL
){
1850 fprintf(stderr
, _("The error ring is empty\n"));
1854 if((fp
= Ftmp(NULL
, "errors", OF_RDWR
| OF_UNLINK
| OF_REGISTER
)) ==
1856 fprintf(stderr
, _("tmpfile"));
1861 for(i
= 0, enp
= a_aux_err_head
; enp
!= NULL
; enp
= enp
->ae_next
)
1862 fprintf(fp
, "- %4" PRIuZ
". %" PRIuZ
" bytes: %s",
1863 ++i
, enp
->ae_str
.l
, enp
->ae_str
.s
);
1864 /* We don't know wether last string ended with NL; be simple */
1867 page_or_print(fp
, 0);
1873 a_aux_err_tail
= NULL
;
1874 a_aux_err_cnt
= a_aux_err_cnt_noted
= 0;
1875 while((enp
= a_aux_err_head
) != NULL
){
1876 a_aux_err_head
= enp
->ae_next
;
1877 free(enp
->ae_str
.s
);
1882 #endif /* HAVE_ERRORS */