Move argcount() to list.c
[s-mailx.git] / auxlily.c
blob1c0e8e2daddec675a2fe980957d6156639838c0c
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>.
6 */
7 /*
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
13 * are met:
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
33 * SUCH DAMAGE.
35 #undef n_FILE
36 #define n_FILE auxlily
38 #ifndef HAVE_AMALGAMATION
39 # include "nail.h"
40 #endif
42 #include <sys/utsname.h>
44 #include <ctype.h>
46 #ifdef HAVE_SOCKETS
47 # ifdef HAVE_GETADDRINFO
48 # include <sys/socket.h>
49 # endif
51 # include <netdb.h>
52 #endif
54 #ifndef HAVE_POSIX_RANDOM
55 union rand_state {
56 struct rand_arc4 {
57 ui8_t __pad[6];
58 ui8_t _i;
59 ui8_t _j;
60 ui8_t _dat[256];
61 } a;
62 ui8_t b8[sizeof(struct rand_arc4)];
63 ui32_t b32[sizeof(struct rand_arc4) / sizeof(ui32_t)];
65 #endif
67 #ifdef HAVE_ERRORS
68 struct a_aux_err_node{
69 struct a_aux_err_node *ae_next;
70 struct str ae_str;
72 #endif
74 #ifndef HAVE_POSIX_RANDOM
75 static union rand_state *_rand;
76 #endif
78 /* Error ring, for `errors' */
79 #ifdef HAVE_ERRORS
80 static struct a_aux_err_node *a_aux_err_head, *a_aux_err_tail;
81 static size_t a_aux_err_cnt, a_aux_err_cnt_noted;
82 #endif
83 static size_t a_aux_err_dirty;
85 /* Our ARC4 random generator with its completely unacademical pseudo
86 * initialization (shall /dev/urandom fail) */
87 #ifndef HAVE_POSIX_RANDOM
88 static void _rand_init(void);
89 static ui32_t _rand_weak(ui32_t seed);
90 SINLINE ui8_t _rand_get8(void);
91 #endif
93 #ifndef HAVE_POSIX_RANDOM
94 static void
95 _rand_init(void)
97 # ifdef HAVE_CLOCK_GETTIME
98 struct timespec ts;
99 # else
100 struct timeval ts;
101 # endif
102 union {int fd; size_t i;} u;
103 ui32_t seed, rnd;
104 NYD2_ENTER;
106 _rand = smalloc(sizeof *_rand);
108 if ((u.fd = open("/dev/urandom", O_RDONLY)) != -1) {
109 bool_t ok = (sizeof *_rand == (size_t)read(u.fd, _rand, sizeof *_rand));
111 close(u.fd);
112 if (ok)
113 goto jleave;
116 for (seed = (uintptr_t)_rand & UI32_MAX, rnd = 21; rnd != 0; --rnd) {
117 for (u.i = NELEM(_rand->b32); u.i-- != 0;) {
118 size_t t, k;
120 # ifdef HAVE_CLOCK_GETTIME
121 clock_gettime(CLOCK_REALTIME, &ts);
122 t = (ui32_t)ts.tv_nsec;
123 # else
124 gettimeofday(&ts, NULL);
125 t = (ui32_t)ts.tv_usec;
126 # endif
127 if (rnd & 1)
128 t = (t >> 16) | (t << 16);
129 _rand->b32[u.i] ^= _rand_weak(seed ^ t);
130 _rand->b32[t % NELEM(_rand->b32)] ^= seed;
131 if (rnd == 7 || rnd == 17)
132 _rand->b32[u.i] ^= _rand_weak(seed ^ (ui32_t)ts.tv_sec);
133 k = _rand->b32[u.i] % NELEM(_rand->b32);
134 _rand->b32[k] ^= _rand->b32[u.i];
135 seed ^= _rand_weak(_rand->b32[k]);
136 if ((rnd & 3) == 3)
137 seed ^= nextprime(seed);
141 for (u.i = 5 * sizeof(_rand->b8); u.i != 0; --u.i)
142 _rand_get8();
143 jleave:
144 NYD2_LEAVE;
147 static ui32_t
148 _rand_weak(ui32_t seed)
150 /* From "Random number generators: good ones are hard to find",
151 * Park and Miller, Communications of the ACM, vol. 31, no. 10,
152 * October 1988, p. 1195.
153 * (In fact: FreeBSD 4.7, /usr/src/lib/libc/stdlib/random.c.) */
154 ui32_t hi;
156 if (seed == 0)
157 seed = 123459876;
158 hi = seed / 127773;
159 seed %= 127773;
160 seed = (seed * 16807) - (hi * 2836);
161 if ((si32_t)seed < 0)
162 seed += SI32_MAX;
163 return seed;
166 SINLINE ui8_t
167 _rand_get8(void)
169 ui8_t si, sj;
171 si = _rand->a._dat[++_rand->a._i];
172 sj = _rand->a._dat[_rand->a._j += si];
173 _rand->a._dat[_rand->a._i] = sj;
174 _rand->a._dat[_rand->a._j] = si;
175 return _rand->a._dat[(ui8_t)(si + sj)];
177 #endif /* HAVE_POSIX_RANDOM */
179 FL int
180 screensize(void)
182 int s;
183 char *cp;
184 NYD_ENTER;
186 if ((cp = ok_vlook(screen)) == NULL || (s = atoi(cp)) <= 0)
187 s = scrnheight - 2; /* XXX no magics */
188 NYD_LEAVE;
189 return s;
192 FL char const *
193 n_pager_get(char const **env_addon)
195 char const *cp;
196 NYD_ENTER;
198 cp = ok_vlook(PAGER);
199 if (cp == NULL || *cp == '\0')
200 cp = XPAGER;
202 if (env_addon != NULL) {
203 *env_addon = NULL;
204 /* Update the manual upon any changes:
205 * *colour-pager*, $PAGER */
206 if(strstr(rv, "less") != NULL){
207 if(!env_blook("LESS", TRU1))
208 *env_addon =
209 #ifdef HAVE_TERMCAP
210 (pstate & PS_TERMCAP_CA_MODE) ? "LESS=Ri"
211 : !(pstate & PS_TERMCAP_DISABLE) ? "LESS=FRi" :
212 #endif
213 "LESS=FRXi";
214 }else if(strstr(rv, "lv") != NULL){
215 if(!env_blook("LV", TRU1))
216 *env_addon = "LV=-c";
219 NYD_LEAVE;
220 return cp;
223 FL void
224 page_or_print(FILE *fp, size_t lines)
226 int c;
227 char const *cp;
228 NYD_ENTER;
230 fflush_rewind(fp);
232 if ((options & OPT_INTERACTIVE) && (pstate & PS_STARTED) &&
233 (cp = ok_vlook(crt)) != NULL) {
234 char *eptr;
235 union {sl_i sli; size_t rows;} u;
237 u.sli = strtol(cp, &eptr, 0);
238 u.rows = (*cp != '\0' && *eptr == '\0')
239 ? (size_t)u.sli : (size_t)scrnheight;
241 if (u.rows > 0 && lines == 0) {
242 while ((c = getc(fp)) != EOF)
243 if (c == '\n' && ++lines >= u.rows)
244 break;
245 really_rewind(fp);
248 if (lines >= u.rows) {
249 run_command(n_pager_get(NULL), 0, fileno(fp), COMMAND_FD_PASS,
250 NULL, NULL, NULL, NULL);
251 goto jleave;
255 while ((c = getc(fp)) != EOF)
256 putchar(c);
257 jleave:
258 NYD_LEAVE;
261 FL enum protocol
262 which_protocol(char const *name) /* XXX (->URL (yet auxlily.c)) */
264 struct stat st;
265 char const *cp;
266 char *np;
267 size_t sz;
268 enum protocol rv = PROTO_UNKNOWN;
269 NYD_ENTER;
271 temporary_protocol_ext = NULL;
273 if (name[0] == '%' && name[1] == ':')
274 name += 2;
275 for (cp = name; *cp && *cp != ':'; cp++)
276 if (!alnumchar(*cp))
277 goto jfile;
279 if (cp[0] == ':' && cp[1] == '/' && cp[2] == '/') {
280 if (!strncmp(name, "pop3://", 7)) {
281 #ifdef HAVE_POP3
282 rv = PROTO_POP3;
283 #else
284 n_err(_("No POP3 support compiled in\n"));
285 #endif
286 } else if (!strncmp(name, "pop3s://", 8)) {
287 #if defined HAVE_POP3 && defined HAVE_SSL
288 rv = PROTO_POP3;
289 #else
290 # ifndef HAVE_POP3
291 n_err(_("No POP3 support compiled in\n"));
292 # endif
293 # ifndef HAVE_SSL
294 n_err(_("No SSL support compiled in\n"));
295 # endif
296 #endif
298 goto jleave;
301 /* TODO This is the de facto maildir code and thus belongs into there!
302 * TODO and: we should have maildir:// and mbox:// pseudo-protos, instead of
303 * TODO or (more likely) in addition to *newfolders*) */
304 jfile:
305 rv = PROTO_FILE;
306 np = ac_alloc((sz = strlen(name)) + 4 +1);
307 memcpy(np, name, sz + 1);
308 if (!stat(name, &st)) {
309 if (S_ISDIR(st.st_mode) &&
310 (memcpy(np+sz, "/tmp", 5), !stat(np, &st) && S_ISDIR(st.st_mode)) &&
311 (memcpy(np+sz, "/new", 5), !stat(np, &st) && S_ISDIR(st.st_mode)) &&
312 (memcpy(np+sz, "/cur", 5), !stat(np, &st) && S_ISDIR(st.st_mode)))
313 rv = PROTO_MAILDIR;
314 } else {
315 if ((memcpy(np+sz, cp=".gz", 4), !stat(np, &st) && S_ISREG(st.st_mode)) ||
316 (memcpy(np+sz, cp=".xz",4), !stat(np,&st) && S_ISREG(st.st_mode)) ||
317 (memcpy(np+sz, cp=".bz2",5), !stat(np, &st) && S_ISREG(st.st_mode)))
318 temporary_protocol_ext = cp;
319 else if ((cp = ok_vlook(newfolders)) != NULL && !strcmp(cp, "maildir"))
320 rv = PROTO_MAILDIR;
322 ac_free(np);
323 jleave:
324 NYD_LEAVE;
325 return rv;
328 FL ui32_t
329 torek_hash(char const *name)
331 /* Chris Torek's hash.
332 * NOTE: need to change *at least* mk-okey-map.pl when changing the
333 * algorithm!! */
334 ui32_t h = 0;
335 NYD_ENTER;
337 while (*name != '\0') {
338 h *= 33;
339 h += *name++;
341 NYD_LEAVE;
342 return h;
345 FL unsigned
346 pjw(char const *cp) /* TODO obsolete that -> torek_hash */
348 unsigned h = 0, g;
349 NYD_ENTER;
351 cp--;
352 while (*++cp) {
353 h = (h << 4 & 0xffffffff) + (*cp&0377);
354 if ((g = h & 0xf0000000) != 0) {
355 h = h ^ g >> 24;
356 h = h ^ g;
359 NYD_LEAVE;
360 return h;
363 FL ui32_t
364 nextprime(ui32_t n)
366 static ui32_t const primes[] = {
367 5, 11, 23, 47, 97, 157, 283,
368 509, 1021, 2039, 4093, 8191, 16381, 32749, 65521,
369 131071, 262139, 524287, 1048573, 2097143, 4194301,
370 8388593, 16777213, 33554393, 67108859, 134217689,
371 268435399, 536870909, 1073741789, 2147483647
374 ui32_t i, mprime;
375 NYD_ENTER;
377 i = (n < primes[NELEM(primes) / 4] ? 0
378 : (n < primes[NELEM(primes) / 2] ? NELEM(primes) / 4
379 : NELEM(primes) / 2));
381 if ((mprime = primes[i]) > n)
382 break;
383 while (++i < NELEM(primes));
384 if (i == NELEM(primes) && mprime < n)
385 mprime = n;
386 NYD_LEAVE;
387 return mprime;
390 FL char *
391 getprompt(void) /* TODO evaluate only as necessary (needs a bit) PART OF UI! */
392 { /* FIXME getprompt must mb->wc->mb+reset seq! */
393 static char buf[PROMPT_BUFFER_SIZE];
395 char *cp;
396 char const *ccp_base, *ccp;
397 size_t NATCH_CHAR( cclen_base COMMA cclen COMMA ) maxlen, dfmaxlen;
398 bool_t trigger; /* 1.: `errors' ring note? 2.: first loop tick done */
399 NYD_ENTER;
401 /* No other place to place this */
402 #ifdef HAVE_ERRORS
403 if (options & OPT_INTERACTIVE) {
404 if (!(pstate & PS_ERRORS_NOTED) && a_aux_err_head != NULL) {
405 pstate |= PS_ERRORS_NOTED;
406 fprintf(stderr, _("There are new messages in the error message ring "
407 "(denoted by \"#ERR#\")\n"
408 " The `errors' command manages this message ring\n"));
411 if ((trigger = (a_aux_err_cnt_noted != a_aux_err_cnt)))
412 a_aux_err_cnt_noted = a_aux_err_cnt;
413 } else
414 trigger = FAL0;
415 #endif
417 cp = buf;
418 if ((ccp_base = ok_vlook(prompt)) == NULL || *ccp_base == '\0') {
419 #ifdef HAVE_ERRORS
420 if (trigger)
421 ccp_base = "";
422 else
423 #endif
424 goto jleave;
426 #ifdef HAVE_ERRORS
427 if (trigger)
428 ccp_base = savecatsep(_("#ERR#"), '\0', ccp_base);
429 #endif
430 NATCH_CHAR( cclen_base = strlen(ccp_base); )
432 dfmaxlen = 0; /* keep CC happy */
433 trigger = FAL0;
434 jredo:
435 ccp = ccp_base;
436 NATCH_CHAR( cclen = cclen_base; )
437 maxlen = sizeof(buf) -1;
439 for (;;) {
440 size_t l;
441 int c;
443 if (maxlen == 0)
444 goto jleave;
445 #ifdef HAVE_NATCH_CHAR
446 c = mblen(ccp, cclen); /* TODO use mbrtowc() */
447 if (c <= 0) {
448 mblen(NULL, 0);
449 if (c < 0) {
450 *buf = '?';
451 cp = buf + 1;
452 goto jleave;
454 break;
455 } else if ((l = c) > 1) {
456 if (trigger) {
457 memcpy(cp, ccp, l);
458 cp += l;
460 ccp += l;
461 maxlen -= l;
462 continue;
463 } else
464 #endif
465 if ((c = n_shell_expand_escape(&ccp, TRU1)) > 0) {
466 if (trigger)
467 *cp++ = (char)c;
468 --maxlen;
469 continue;
471 if (c == 0 || c == PROMPT_STOP)
472 break;
474 if (trigger) {
475 char const *a = (c == PROMPT_DOLLAR) ? account_name : displayname;
476 if (a == NULL)
477 a = "";
478 if ((l = field_put_bidi_clip(cp, dfmaxlen, a, strlen(a))) > 0) {
479 cp += l;
480 maxlen -= l;
481 dfmaxlen -= l;
486 if (!trigger) {
487 trigger = TRU1;
488 dfmaxlen = maxlen;
489 goto jredo;
491 jleave:
492 *cp = '\0';
493 NYD_LEAVE;
494 return buf;
497 FL char *
498 nodename(int mayoverride)
500 static char *sys_hostname, *hostname; /* XXX free-at-exit */
502 struct utsname ut;
503 char *hn;
504 #ifdef HAVE_SOCKETS
505 # ifdef HAVE_GETADDRINFO
506 struct addrinfo hints, *res;
507 # else
508 struct hostent *hent;
509 # endif
510 #endif
511 NYD_ENTER;
513 if (mayoverride && (hn = ok_vlook(hostname)) != NULL && *hn != '\0') {
515 } else if ((hn = sys_hostname) == NULL) {
516 uname(&ut);
517 hn = ut.nodename;
518 #ifdef HAVE_SOCKETS
519 # ifdef HAVE_GETADDRINFO
520 memset(&hints, 0, sizeof hints);
521 hints.ai_family = AF_UNSPEC;
522 hints.ai_flags = AI_CANONNAME;
523 if (getaddrinfo(hn, NULL, &hints, &res) == 0) {
524 if (res->ai_canonname != NULL) {
525 size_t l = strlen(res->ai_canonname) +1;
527 hn = ac_alloc(l);
528 memcpy(hn, res->ai_canonname, l);
530 freeaddrinfo(res);
532 # else
533 hent = gethostbyname(hn);
534 if (hent != NULL)
535 hn = hent->h_name;
536 # endif
537 #endif
538 sys_hostname = sstrdup(hn);
539 #if defined HAVE_SOCKETS && defined HAVE_GETADDRINFO
540 if (hn != ut.nodename)
541 ac_free(hn);
542 #endif
543 hn = sys_hostname;
546 if (hostname != NULL && hostname != sys_hostname)
547 free(hostname);
548 hostname = sstrdup(hn);
549 NYD_LEAVE;
550 return hostname;
553 FL char *
554 getrandstring(size_t length)
556 struct str b64;
557 char *data;
558 size_t i;
559 NYD_ENTER;
561 #ifndef HAVE_POSIX_RANDOM
562 if (_rand == NULL)
563 _rand_init();
564 #endif
566 /* We use our base64 encoder with _NOPAD set, so ensure the encoded result
567 * with PAD stripped is still longer than what the user requests, easy way */
568 data = ac_alloc(i = length + 3);
570 #ifndef HAVE_POSIX_RANDOM
571 while (i-- > 0)
572 data[i] = (char)_rand_get8();
573 #else
574 { char *cp = data;
576 while (i > 0) {
577 union {ui32_t i4; char c[4];} r;
578 size_t j;
580 r.i4 = (ui32_t)arc4random();
581 switch ((j = i & 3)) {
582 case 0: cp[3] = r.c[3]; j = 4;
583 case 3: cp[2] = r.c[2];
584 case 2: cp[1] = r.c[1];
585 default: cp[0] = r.c[0]; break;
587 cp += j;
588 i -= j;
591 #endif
593 b64_encode_buf(&b64, data, length + 3,
594 B64_SALLOC | B64_RFC4648URL | B64_NOPAD);
595 ac_free(data);
597 assert(b64.l >= length);
598 b64.s[length] = '\0';
599 NYD_LEAVE;
600 return b64.s;
603 FL size_t
604 field_detect_width(char const *buf, size_t blen){
605 size_t rv;
606 NYD2_ENTER;
608 if(blen == UIZ_MAX)
609 blen = (buf == NULL) ? 0 : strlen(buf);
610 assert(blen == 0 || buf != NULL);
612 if((rv = blen) > 0){
613 #ifdef HAVE_C90AMEND1
614 mbstate_t mbs;
615 wchar_t wc;
617 memset(&mbs, 0, sizeof mbs);
619 for(rv = 0; blen > 0;){
620 size_t i = mbrtowc(&wc, buf, blen, &mbs);
622 switch(i){
623 case (size_t)-2:
624 case (size_t)-1:
625 rv = (size_t)-1;
626 /* FALLTHRU */
627 case 0:
628 blen = 0;
629 break;
630 default:
631 buf += i;
632 blen -= i;
633 # ifdef HAVE_WCWIDTH
634 /* C99 */{
635 int w = wcwidth(wc);
637 if(w > 0)
638 rv += w;
639 else if(wc == '\t')
640 ++rv;
642 # else
643 if(iswprint(wc))
644 rv += 1 + (wc >= 0x1100u); /* TODO use S-CText isfullwidth() */
645 else if(wc == '\t')
646 ++rv;
647 # endif
648 break;
651 #endif /* HAVE_C90AMEND1 */
653 NYD2_LEAVE;
654 return rv;
657 FL size_t
658 field_detect_clip(size_t maxlen, char const *buf, size_t blen)/*TODO mbrtowc()*/
660 size_t rv;
661 NYD_ENTER;
663 #ifdef HAVE_NATCH_CHAR
664 maxlen = MIN(maxlen, blen);
665 for (rv = 0; maxlen > 0;) {
666 int ml = mblen(buf, maxlen);
667 if (ml <= 0) {
668 mblen(NULL, 0);
669 break;
671 buf += ml;
672 rv += ml;
673 maxlen -= ml;
675 #else
676 rv = MIN(blen, maxlen);
677 #endif
678 NYD_LEAVE;
679 return rv;
682 FL size_t
683 field_put_bidi_clip(char *store, size_t maxlen, char const *buf, size_t blen)
685 NATCH_CHAR( struct bidi_info bi; )
686 size_t rv NATCH_CHAR( COMMA i );
687 NYD_ENTER;
689 rv = 0;
690 if (maxlen-- == 0)
691 goto j_leave;
693 #ifdef HAVE_NATCH_CHAR
694 bidi_info_create(&bi);
695 if (bi.bi_start.l == 0 || !bidi_info_needed(buf, blen)) {
696 bi.bi_end.l = 0;
697 goto jnobidi;
700 if (maxlen >= (i = bi.bi_pad + bi.bi_end.l + bi.bi_start.l))
701 maxlen -= i;
702 else
703 goto jleave;
705 if ((i = bi.bi_start.l) > 0) {
706 memcpy(store, bi.bi_start.s, i);
707 store += i;
708 rv += i;
711 jnobidi:
712 while (maxlen > 0) {
713 int ml = mblen(buf, blen);
714 if (ml <= 0) {
715 mblen(NULL, 0);
716 break;
718 if (UICMP(z, maxlen, <, ml))
719 break;
720 if (ml == 1)
721 *store = *buf;
722 else
723 memcpy(store, buf, ml);
724 store += ml;
725 buf += ml;
726 rv += ml;
727 maxlen -= ml;
730 if ((i = bi.bi_end.l) > 0) {
731 memcpy(store, bi.bi_end.s, i);
732 store += i;
733 rv += i;
735 jleave:
736 *store = '\0';
738 #else
739 rv = MIN(blen, maxlen);
740 memcpy(store, buf, rv);
741 store[rv] = '\0';
742 #endif
743 j_leave:
744 NYD_LEAVE;
745 return rv;
748 FL char *
749 colalign(char const *cp, int col, int fill, int *cols_decr_used_or_null)
751 NATCH_CHAR( struct bidi_info bi; )
752 int col_orig = col, n, sz;
753 bool_t isbidi, isuni, istab, isrepl;
754 char *nb, *np;
755 NYD_ENTER;
757 /* Bidi only on request and when there is 8-bit data */
758 isbidi = isuni = FAL0;
759 #ifdef HAVE_NATCH_CHAR
760 isuni = ((options & OPT_UNICODE) != 0);
761 bidi_info_create(&bi);
762 if (bi.bi_start.l == 0)
763 goto jnobidi;
764 if (!(isbidi = bidi_info_needed(cp, strlen(cp))))
765 goto jnobidi;
767 if ((size_t)col >= bi.bi_pad)
768 col -= bi.bi_pad;
769 else
770 col = 0;
771 jnobidi:
772 #endif
774 np = nb = salloc(mb_cur_max * strlen(cp) +
775 ((fill ? col : 0)
776 NATCH_CHAR( + (isbidi ? bi.bi_start.l + bi.bi_end.l : 0) )
777 +1));
779 #ifdef HAVE_NATCH_CHAR
780 if (isbidi) {
781 memcpy(np, bi.bi_start.s, bi.bi_start.l);
782 np += bi.bi_start.l;
784 #endif
786 while (*cp != '\0') {
787 istab = FAL0;
788 #ifdef HAVE_C90AMEND1
789 if (mb_cur_max > 1) {
790 wchar_t wc;
792 n = 1;
793 isrepl = TRU1;
794 if ((sz = mbtowc(&wc, cp, mb_cur_max)) == -1)
795 sz = 1;
796 else if (wc == L'\t') {
797 cp += sz - 1; /* Silly, no such charset known (.. until S-Ctext) */
798 isrepl = FAL0;
799 istab = TRU1;
800 } else if (iswprint(wc)) {
801 # ifndef HAVE_WCWIDTH
802 n = 1 + (wc >= 0x1100u); /* TODO use S-CText isfullwidth() */
803 # else
804 if ((n = wcwidth(wc)) == -1)
805 n = 1;
806 else
807 # endif
808 isrepl = FAL0;
810 } else
811 #endif
813 n = sz = 1;
814 istab = (*cp == '\t');
815 isrepl = !(istab || isprint((uc_i)*cp));
818 if (n > col)
819 break;
820 col -= n;
822 if (isrepl) {
823 if (isuni) {
824 np[0] = (char)0xEFu;
825 np[1] = (char)0xBFu;
826 np[2] = (char)0xBDu;
827 np += 3;
828 } else
829 *np++ = '?';
830 cp += sz;
831 } else if (istab || (sz == 1 && spacechar(*cp))) {
832 *np++ = ' ';
833 ++cp;
834 } else
835 while (sz--)
836 *np++ = *cp++;
839 if (fill && col != 0) {
840 if (fill > 0) {
841 memmove(nb + col, nb, PTR2SIZE(np - nb));
842 memset(nb, ' ', col);
843 } else
844 memset(np, ' ', col);
845 np += col;
846 col = 0;
849 #ifdef HAVE_NATCH_CHAR
850 if (isbidi) {
851 memcpy(np, bi.bi_end.s, bi.bi_end.l);
852 np += bi.bi_end.l;
854 #endif
856 *np = '\0';
857 if (cols_decr_used_or_null != NULL)
858 *cols_decr_used_or_null -= col_orig - col;
859 NYD_LEAVE;
860 return nb;
863 FL void
864 makeprint(struct str const *in, struct str *out)
866 char const *inp, *maxp;
867 char *outp;
868 DBG( size_t msz; )
869 NYD_ENTER;
871 out->s = outp = smalloc(DBG( msz = ) in->l*mb_cur_max + 2u*mb_cur_max +1);
872 inp = in->s;
873 maxp = inp + in->l;
875 #ifdef HAVE_NATCH_CHAR
876 if (mb_cur_max > 1) {
877 char mbb[MB_LEN_MAX + 1];
878 wchar_t wc;
879 int i, n;
880 bool_t isuni = ((options & OPT_UNICODE) != 0);
882 out->l = 0;
883 while (inp < maxp) {
884 if (*inp & 0200)
885 n = mbtowc(&wc, inp, PTR2SIZE(maxp - inp));
886 else {
887 wc = *inp;
888 n = 1;
890 if (n == -1) {
891 /* FIXME Why mbtowc() resetting here?
892 * FIXME what about ISO 2022-JP plus -- those
893 * FIXME will loose shifts, then!
894 * FIXME THUS - we'd need special "known points"
895 * FIXME to do so - say, after a newline!!
896 * FIXME WE NEED TO CHANGE ALL USES +MBLEN! */
897 mbtowc(&wc, NULL, mb_cur_max);
898 wc = isuni ? 0xFFFD : '?';
899 n = 1;
900 } else if (n == 0)
901 n = 1;
902 inp += n;
903 if (!iswprint(wc) && wc != '\n' && wc != '\r' && wc != '\b' &&
904 wc != '\t') {
905 if ((wc & ~(wchar_t)037) == 0)
906 wc = isuni ? 0x2400 | wc : '?';
907 else if (wc == 0177)
908 wc = isuni ? 0x2421 : '?';
909 else
910 wc = isuni ? 0x2426 : '?';
911 }else if(isuni){ /* TODO ctext */
912 /* We need to actively filter out L-TO-R and R-TO-R marks TODO ctext */
913 if(wc == 0x200E || wc == 0x200F || (wc >= 0x202A && wc <= 0x202E))
914 continue;
915 /* And some zero-width messes */
916 if(wc == 0x00AD || (wc >= 0x200B && wc <= 0x200D))
917 continue;
918 /* Oh about the ISO C wide character interfaces, baby! */
919 if(wc == 0xFEFF)
920 continue;
922 if ((n = wctomb(mbb, wc)) <= 0)
923 continue;
924 out->l += n;
925 assert(out->l < msz);
926 for (i = 0; i < n; ++i)
927 *outp++ = mbb[i];
929 } else
930 #endif /* NATCH_CHAR */
932 int c;
933 while (inp < maxp) {
934 c = *inp++ & 0377;
935 if (!isprint(c) && c != '\n' && c != '\r' && c != '\b' && c != '\t')
936 c = '?';
937 *outp++ = c;
939 out->l = in->l;
941 out->s[out->l] = '\0';
942 NYD_LEAVE;
945 FL size_t
946 delctrl(char *cp, size_t len)
948 size_t x, y;
949 NYD_ENTER;
951 for (x = y = 0; x < len; ++x)
952 if (!cntrlchar(cp[x]))
953 cp[y++] = cp[x];
954 cp[y] = '\0';
955 NYD_LEAVE;
956 return y;
959 FL char *
960 prstr(char const *s)
962 struct str in, out;
963 char *rp;
964 NYD_ENTER;
966 in.s = UNCONST(s);
967 in.l = strlen(s);
968 makeprint(&in, &out);
969 rp = savestrbuf(out.s, out.l);
970 free(out.s);
971 NYD_LEAVE;
972 return rp;
975 FL int
976 prout(char const *s, size_t sz, FILE *fp)
978 struct str in, out;
979 int n;
980 NYD_ENTER;
982 in.s = UNCONST(s);
983 in.l = sz;
984 makeprint(&in, &out);
985 n = fwrite(out.s, 1, out.l, fp);
986 free(out.s);
987 NYD_LEAVE;
988 return n;
991 FL size_t
992 putuc(int u, int c, FILE *fp)
994 size_t rv;
995 NYD_ENTER;
996 UNUSED(u);
998 #ifdef HAVE_NATCH_CHAR
999 if ((options & OPT_UNICODE) && (u & ~(wchar_t)0177)) {
1000 char mbb[MB_LEN_MAX];
1001 int i, n;
1003 if ((n = wctomb(mbb, u)) > 0) {
1004 rv = wcwidth(u);
1005 for (i = 0; i < n; ++i)
1006 if (putc(mbb[i] & 0377, fp) == EOF) {
1007 rv = 0;
1008 break;
1010 } else if (n == 0)
1011 rv = (putc('\0', fp) != EOF);
1012 else
1013 rv = 0;
1014 } else
1015 #endif
1016 rv = (putc(c, fp) != EOF);
1017 NYD_LEAVE;
1018 return rv;
1021 FL bool_t
1022 bidi_info_needed(char const *bdat, size_t blen)
1024 bool_t rv = FAL0;
1025 NYD_ENTER;
1027 #ifdef HAVE_NATCH_CHAR
1028 if (options & OPT_UNICODE)
1029 while (blen > 0) {
1030 /* TODO Checking for BIDI character: use S-CText fromutf8
1031 * TODO plus isrighttoleft (or whatever there will be)! */
1032 ui32_t c = n_utf8_to_utf32(&bdat, &blen);
1033 if (c == UI32_MAX)
1034 break;
1036 if (c <= 0x05BE)
1037 continue;
1039 /* (Very very fuzzy, awaiting S-CText for good) */
1040 if ((c >= 0x05BE && c <= 0x08E3) ||
1041 (c >= 0xFB1D && c <= 0xFE00) /* No: variation selectors */ ||
1042 (c >= 0xFE70 && c <= 0xFEFC) ||
1043 (c >= 0x10800 && c <= 0x10C48) ||
1044 (c >= 0x1EE00 && c <= 0x1EEF1)) {
1045 rv = TRU1;
1046 break;
1049 #endif /* HAVE_NATCH_CHAR */
1050 NYD_LEAVE;
1051 return rv;
1054 FL void
1055 bidi_info_create(struct bidi_info *bip)
1057 /* Unicode: how to isolate RIGHT-TO-LEFT scripts via *headline-bidi*
1058 * 1.1 (Jun 1993): U+200E (E2 80 8E) LEFT-TO-RIGHT MARK
1059 * 6.3 (Sep 2013): U+2068 (E2 81 A8) FIRST STRONG ISOLATE,
1060 * U+2069 (E2 81 A9) POP DIRECTIONAL ISOLATE
1061 * Worse results seen for: U+202D "\xE2\x80\xAD" U+202C "\xE2\x80\xAC" */
1062 NATCH_CHAR( char const *hb; )
1063 NYD_ENTER;
1065 memset(bip, 0, sizeof *bip);
1066 bip->bi_start.s = bip->bi_end.s = UNCONST("");
1068 #ifdef HAVE_NATCH_CHAR
1069 if ((options & OPT_UNICODE) && (hb = ok_vlook(headline_bidi)) != NULL) {
1070 switch (*hb) {
1071 case '3':
1072 bip->bi_pad = 2;
1073 /* FALLTHRU */
1074 case '2':
1075 bip->bi_start.s = bip->bi_end.s = UNCONST("\xE2\x80\x8E");
1076 break;
1077 case '1':
1078 bip->bi_pad = 2;
1079 /* FALLTHRU */
1080 default:
1081 bip->bi_start.s = UNCONST("\xE2\x81\xA8");
1082 bip->bi_end.s = UNCONST("\xE2\x81\xA9");
1083 break;
1085 bip->bi_start.l = bip->bi_end.l = 3;
1087 #endif
1088 NYD_LEAVE;
1091 FL si8_t
1092 boolify(char const *inbuf, uiz_t inlen, si8_t emptyrv)
1094 char *dat, *eptr;
1095 sl_i sli;
1096 si8_t rv;
1097 NYD_ENTER;
1099 assert(inlen == 0 || inbuf != NULL);
1101 if (inlen == UIZ_MAX)
1102 inlen = strlen(inbuf);
1104 if (inlen == 0)
1105 rv = (emptyrv >= 0) ? (emptyrv == 0 ? 0 : 1) : -1;
1106 else {
1107 if ((inlen == 1 && *inbuf == '1') ||
1108 !ascncasecmp(inbuf, "true", inlen) ||
1109 !ascncasecmp(inbuf, "yes", inlen) ||
1110 !ascncasecmp(inbuf, "on", inlen))
1111 rv = 1;
1112 else if ((inlen == 1 && *inbuf == '0') ||
1113 !ascncasecmp(inbuf, "false", inlen) ||
1114 !ascncasecmp(inbuf, "no", inlen) ||
1115 !ascncasecmp(inbuf, "off", inlen))
1116 rv = 0;
1117 else {
1118 dat = ac_alloc(inlen +1);
1119 memcpy(dat, inbuf, inlen);
1120 dat[inlen] = '\0';
1122 sli = strtol(dat, &eptr, 0);
1123 if (*dat != '\0' && *eptr == '\0')
1124 rv = (sli != 0);
1125 else
1126 rv = -1;
1128 ac_free(dat);
1131 NYD_LEAVE;
1132 return rv;
1135 FL si8_t
1136 quadify(char const *inbuf, uiz_t inlen, char const *prompt, si8_t emptyrv)
1138 si8_t rv;
1139 NYD_ENTER;
1141 assert(inlen == 0 || inbuf != NULL);
1143 if (inlen == UIZ_MAX)
1144 inlen = strlen(inbuf);
1146 if (inlen == 0)
1147 rv = (emptyrv >= 0) ? (emptyrv == 0 ? 0 : 1) : -1;
1148 else if ((rv = boolify(inbuf, inlen, -1)) < 0 &&
1149 !ascncasecmp(inbuf, "ask-", 4) &&
1150 (rv = boolify(inbuf + 4, inlen - 4, -1)) >= 0 &&
1151 (options & OPT_INTERACTIVE))
1152 rv = getapproval(prompt, rv);
1153 NYD_LEAVE;
1154 return rv;
1157 FL bool_t
1158 n_is_all_or_aster(char const *name){
1159 bool_t rv;
1160 NYD_ENTER;
1162 rv = ((name[0] == '*' && name[1] == '\0') || !asccasecmp(name, "all"));
1163 NYD_LEAVE;
1164 return rv;
1167 FL time_t
1168 n_time_epoch(void)
1170 #ifdef HAVE_CLOCK_GETTIME
1171 struct timespec ts;
1172 #elif defined HAVE_GETTIMEOFDAY
1173 struct timeval ts;
1174 #endif
1175 time_t rv;
1176 NYD2_ENTER;
1178 #ifdef HAVE_CLOCK_GETTIME
1179 clock_gettime(CLOCK_REALTIME, &ts);
1180 rv = (time_t)ts.tv_sec;
1181 #elif defined HAVE_GETTIMEOFDAY
1182 gettimeofday(&ts, NULL);
1183 rv = (time_t)ts.tv_sec;
1184 #else
1185 rv = time(NULL);
1186 #endif
1187 NYD2_LEAVE;
1188 return rv;
1191 FL void
1192 time_current_update(struct time_current *tc, bool_t full_update)
1194 NYD_ENTER;
1195 tc->tc_time = n_time_epoch();
1196 if (full_update) {
1197 memcpy(&tc->tc_gm, gmtime(&tc->tc_time), sizeof tc->tc_gm);
1198 memcpy(&tc->tc_local, localtime(&tc->tc_time), sizeof tc->tc_local);
1199 sstpcpy(tc->tc_ctime, ctime(&tc->tc_time));
1201 NYD_LEAVE;
1204 FL uiz_t
1205 n_msleep(uiz_t millis, bool_t ignint){
1206 uiz_t rv;
1207 NYD2_ENTER;
1209 #ifdef HAVE_NANOSLEEP
1210 /* C99 */{
1211 struct timespec ts, trem;
1212 int i;
1214 ts.tv_sec = millis / 1000;
1215 ts.tv_nsec = (millis %= 1000) * 1000 * 1000;
1217 while((i = nanosleep(&ts, &trem)) != 0 && ignint)
1218 ts = trem;
1219 rv = (i == 0) ? 0 : (trem.tv_sec * 1000) + (trem.tv_nsec / (1000 * 1000));
1222 #elif defined HAVE_SLEEP
1223 if((millis /= 1000) == 0)
1224 millis = 1;
1225 while((rv = sleep((unsigned int)millis)) != 0 && ignint)
1226 millis = rv;
1227 #else
1228 # error Configuration should have detected a function for sleeping.
1229 #endif
1231 NYD2_LEAVE;
1232 return rv;
1235 FL void
1236 n_err(char const *format, ...){
1237 va_list ap;
1238 NYD2_ENTER;
1240 va_start(ap, format);
1241 #ifdef HAVE_ERRORS
1242 if(options & OPT_INTERACTIVE)
1243 n_verr(format, ap);
1244 else
1245 #endif
1247 if(a_aux_err_dirty++ == 0)
1248 fputs(UAGENT ": ", stderr);
1249 vfprintf(stderr, format, ap);
1250 if(strchr(format, '\n') != NULL){ /* TODO */
1251 a_aux_err_dirty = 0;
1252 fflush(stderr);
1255 va_end(ap);
1256 NYD2_LEAVE;
1259 FL void
1260 n_verr(char const *format, va_list ap){
1261 /* Check use cases of PS_ERRORS_NOTED, too! */
1262 #ifdef HAVE_ERRORS
1263 char buf[LINESIZE], *xbuf;
1264 int lmax, l;
1265 struct a_aux_err_node *enp;
1267 LCTA(ERRORS_MAX > 3);
1268 #endif
1269 NYD2_ENTER;
1271 if(a_aux_err_dirty++ == 0)
1272 fputs(UAGENT ": ", stderr);
1274 #ifdef HAVE_ERRORS
1275 if(!(options & OPT_INTERACTIVE))
1276 #endif
1278 vfprintf(stderr, format, ap);
1279 goto jleave;
1282 #ifdef HAVE_ERRORS
1283 xbuf = buf;
1284 lmax = sizeof buf;
1285 jredo:
1286 l = vsnprintf(xbuf, lmax, format, ap);
1287 if (l <= 0)
1288 goto jleave;
1289 if (UICMP(z, l, >=, lmax)) {
1290 /* FIXME Cannot reuse va_list
1291 lmax = ++l;
1292 xbuf = srealloc((xbuf == buf ? NULL : xbuf), lmax);
1293 goto jredo;
1297 fwrite(xbuf, 1, l, stderr);
1299 /* Link it into the `errors' message ring */
1300 if((enp = a_aux_err_tail) == NULL){
1301 jcreat:
1302 enp = scalloc(1, sizeof *enp);
1303 if(a_aux_err_tail != NULL)
1304 a_aux_err_tail->ae_next = enp;
1305 else
1306 a_aux_err_head = enp;
1307 a_aux_err_tail = enp;
1308 ++a_aux_err_cnt;
1309 }else if(enp->ae_str.l > 0 && enp->ae_str.s[enp->ae_str.l - 1] == '\n'){
1310 if(a_aux_err_cnt < ERRORS_MAX)
1311 goto jcreat;
1313 a_aux_err_head = (enp = a_aux_err_head)->ae_next;
1314 a_aux_err_tail->ae_next = enp;
1315 a_aux_err_tail = enp;
1316 free(enp->ae_str.s);
1317 memset(enp, 0, sizeof *enp);
1320 n_str_add_buf(&enp->ae_str, xbuf, l);
1322 if(xbuf != buf)
1323 free(xbuf);
1324 #endif /* HAVE_ERRORS */
1326 jleave:
1327 /* If the format ends with newline, be clean again */
1328 /* C99 */{
1329 size_t i = strlen(format);
1331 if(i > 0 && format[i - 1] == '\n'){
1332 fflush(stderr);
1333 a_aux_err_dirty = 0;
1336 NYD2_LEAVE;
1339 FL void
1340 n_err_sighdl(char const *format, ...){ /* TODO sigsafe; obsolete! */
1341 va_list ap;
1342 NYD_X;
1344 va_start(ap, format);
1345 vfprintf(stderr, format, ap);
1346 va_end(ap);
1347 fflush(stderr);
1350 FL void
1351 n_perr(char const *msg, int errval){
1352 char const *fmt;
1353 NYD2_ENTER;
1355 if(msg == NULL){
1356 fmt = "%s%s\n";
1357 msg = "";
1358 }else
1359 fmt = "%s: %s\n";
1361 if(errval == 0)
1362 errval = errno;
1364 n_err(fmt, msg, strerror(errval));
1365 NYD2_LEAVE;
1368 FL void
1369 n_alert(char const *format, ...){
1370 va_list ap;
1371 NYD2_ENTER;
1373 n_err(a_aux_err_dirty > 0 ? _("\nAlert: ") : _("Alert: "));
1375 va_start(ap, format);
1376 n_verr(format, ap);
1377 va_end(ap);
1379 n_err("\n");
1380 NYD2_LEAVE;
1383 FL void
1384 n_panic(char const *format, ...){
1385 va_list ap;
1386 NYD2_ENTER;
1388 if(a_aux_err_dirty > 0){
1389 putc('\n', stderr);
1390 a_aux_err_dirty = 0;
1392 fprintf(stderr, UAGENT ": Panic: ");
1394 va_start(ap, format);
1395 vfprintf(stderr, format, ap);
1396 va_end(ap);
1398 putc('\n', stderr);
1399 fflush(stderr);
1400 NYD2_LEAVE;
1401 abort(); /* Was exit(EXIT_ERR); for a while, but no */
1404 #ifdef HAVE_ERRORS
1405 FL int
1406 c_errors(void *v){
1407 char **argv = v;
1408 struct a_aux_err_node *enp;
1409 NYD_ENTER;
1411 if(*argv == NULL)
1412 goto jlist;
1413 if(argv[1] != NULL)
1414 goto jerr;
1415 if(!asccasecmp(*argv, "show"))
1416 goto jlist;
1417 if(!asccasecmp(*argv, "clear"))
1418 goto jclear;
1419 jerr:
1420 fprintf(stderr, _("Synopsis: errors: (<show> or) <clear> the error ring\n"));
1421 v = NULL;
1422 jleave:
1423 NYD_LEAVE;
1424 return v == NULL ? !STOP : !OKAY; /* xxx 1:bad 0:good -- do some */
1426 jlist:{
1427 FILE *fp;
1428 size_t i;
1430 if(a_aux_err_head == NULL){
1431 fprintf(stderr, _("The error ring is empty\n"));
1432 goto jleave;
1435 if((fp = Ftmp(NULL, "errors", OF_RDWR | OF_UNLINK | OF_REGISTER)) ==
1436 NULL){
1437 fprintf(stderr, _("tmpfile"));
1438 v = NULL;
1439 goto jleave;
1442 for(i = 0, enp = a_aux_err_head; enp != NULL; enp = enp->ae_next)
1443 fprintf(fp, "- %4" PRIuZ ". %" PRIuZ " bytes: %s",
1444 ++i, enp->ae_str.l, enp->ae_str.s);
1445 /* We don't know wether last string ended with NL; be simple */
1446 putc('\n', fp);
1448 page_or_print(fp, 0);
1449 Fclose(fp);
1451 /* FALLTHRU */
1453 jclear:
1454 a_aux_err_tail = NULL;
1455 a_aux_err_cnt = a_aux_err_cnt_noted = 0;
1456 while((enp = a_aux_err_head) != NULL){
1457 a_aux_err_head = enp->ae_next;
1458 free(enp->ae_str.s);
1459 free(enp);
1461 goto jleave;
1463 #endif /* HAVE_ERRORS */
1465 /* s-it-mode */