Herrje, fix a lot of ^.{80,} column hits (misses)
[s-mailx.git] / src / mx / auxlily.c
blobabfb16aeb1e7cc5e4f0d9988d6eb35bf3fe337c4
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
7 */
8 /*
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
14 * are met:
15 * 1. Redistributions of source code must retain the above copyright
16 * notice, this list of conditions and the following disclaimer.
17 * 2. Redistributions in binary form must reproduce the above copyright
18 * notice, this list of conditions and the following disclaimer in the
19 * documentation and/or other materials provided with the distribution.
20 * 3. Neither the name of the University nor the names of its contributors
21 * may be used to endorse or promote products derived from this software
22 * without specific prior written permission.
24 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34 * SUCH DAMAGE.
36 #undef su_FILE
37 #define su_FILE auxlily
38 #define mx_SOURCE
40 #ifndef mx_HAVE_AMALGAMATION
41 # include "mx/nail.h"
42 #endif
44 #include <sys/utsname.h>
46 #ifdef mx_HAVE_NET
47 # ifdef mx_HAVE_GETADDRINFO
48 # include <sys/socket.h>
49 # endif
51 # include <netdb.h>
52 #endif
54 #ifdef mx_HAVE_IDNA
55 # if mx_HAVE_IDNA == n_IDNA_IMPL_LIBIDN2
56 # include <idn2.h>
57 # elif mx_HAVE_IDNA == n_IDNA_IMPL_LIBIDN
58 # include <idna.h>
59 # include <idn-free.h>
60 # elif mx_HAVE_IDNA == n_IDNA_IMPL_IDNKIT
61 # include <idn/api.h>
62 # endif
63 #endif
65 #include <su/cs.h>
66 #include <su/cs-dict.h>
67 #include <su/icodec.h>
68 #include <su/mem.h>
69 #include <su/sort.h>
71 #include "mx/child.h"
72 #include "mx/cmd-filetype.h"
73 #include "mx/colour.h"
74 #include "mx/file-streams.h"
75 #include "mx/termios.h"
76 #include "mx/tty.h"
78 #ifdef mx_HAVE_ERRORS
79 # include "mx/cmd.h"
80 #endif
81 #ifdef mx_HAVE_IDNA
82 # include "mx/iconv.h"
83 #endif
85 /* TODO fake */
86 #include "su/code-in.h"
88 /* */
89 #ifdef mx_HAVE_ERRORS
90 CTAV(mx_ERRORS_MAX > 1);
91 #endif
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;
96 u32 ae_cnt;
97 boole ae_done;
98 u8 ae_pad[3];
99 uz ae_dumped_till;
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);
112 static char const *
113 a_aux_pager_get(char const **env_addon){
114 char const *rv;
115 NYD_IN;
117 rv = ok_vlook(PAGER);
119 if(env_addon != NIL){
120 *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";
131 NYD_OU;
132 return rv;
135 FL uz
136 n_screensize(void){
137 char const *cp;
138 uz rv;
139 NYD2_IN;
141 if((cp = ok_vlook(screen)) != NIL){
142 su_idec_uz_cp(&rv, cp, 0, NIL);
143 if(rv == 0)
144 rv = mx_termios_dimen.tiosd_height;
145 }else
146 rv = mx_termios_dimen.tiosd_height;
148 if(rv > 2)
149 rv -= 2;
150 NYD2_OU;
151 return rv;
154 FL FILE *
155 mx_pager_open(void){
156 char const *env_add[2], *pager;
157 FILE *rv;
158 NYD_IN;
160 ASSERT(n_psonce & n_PSO_INTERACTIVE);
162 pager = a_aux_pager_get(env_add + 0);
163 env_add[1] = NIL;
165 if((rv = mx_fs_pipe_open(pager, "w", NIL, env_add, mx_CHILD_FD_PASS)
166 ) == NIL)
167 n_perr(pager, 0);
168 NYD_OU;
169 return rv;
172 FL boole
173 mx_pager_close(FILE *fp){
174 boole rv;
175 NYD_IN;
177 rv = mx_fs_pipe_close(fp, TRU1);
178 NYD_OU;
179 return rv;
182 FL void
183 page_or_print(FILE *fp, uz lines)
185 int c;
186 char const *cp;
187 NYD_IN;
189 fflush_rewind(fp);
191 if (n_go_may_yield_control() && (cp = ok_vlook(crt)) != NULL) {
192 uz rows;
194 if(*cp == '\0')
195 rows = mx_termios_dimen.tiosd_height;
196 else
197 su_idec_uz_cp(&rows, cp, 0, NULL);
198 /* Avoid overflow later on */
199 if(rows == UZ_MAX)
200 --rows;
202 if (rows > 0 && lines == 0) {
203 while ((c = getc(fp)) != EOF)
204 if (c == '\n' && ++lines >= rows)
205 break;
206 really_rewind(fp);
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]);
218 env_addon[1] = NIL;
219 cc.cc_env_addon = env_addon;
220 mx_child_run(&cc);
221 goto jleave;
225 while ((c = getc(fp)) != EOF)
226 putc(c, n_stdout);
227 jleave:
228 NYD_OU;
231 FL enum protocol
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;
238 NYD2_IN;
240 rv = fixrv = PROTO_UNKNOWN;
242 if(name[0] == '%' && name[1] == ':')
243 name += 2;
244 orig_name = name;
246 for(cp = name; *cp && *cp != ':'; cp++)
247 if(!su_cs_is_alnum(*cp))
248 goto jfile;
250 if(cp[0] == ':' && cp[1] == '/' && cp[2] == '/'){ /* TODO lookup table */
251 boole yeshooks;
253 yeshooks = FAL0;
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
262 rv = PROTO_MAILDIR;
263 #else
264 n_err(_("No Maildir directory support compiled in\n"));
265 #endif
266 }else if(!su_cs_cmp_case_n(name, "pop3", sizeof("pop3") -1)){
267 #ifdef mx_HAVE_POP3
268 rv = PROTO_POP3;
269 #else
270 n_err(_("No POP3 support compiled in\n"));
271 #endif
272 }else if(!su_cs_cmp_case_n(name, "pop3s", sizeof("pop3s") -1)){
273 #if defined mx_HAVE_POP3 && defined mx_HAVE_TLS
274 rv = PROTO_POP3;
275 #else
276 n_err(_("No POP3S support compiled in\n"));
277 #endif
278 }else if(!su_cs_cmp_case_n(name, "imap", sizeof("imap") -1)){
279 #ifdef mx_HAVE_IMAP
280 rv = PROTO_IMAP;
281 #else
282 n_err(_("No IMAP support compiled in\n"));
283 #endif
284 }else if(!su_cs_cmp_case_n(name, "imaps", sizeof("imaps") -1)){
285 #if defined mx_HAVE_IMAP && defined mx_HAVE_TLS
286 rv = PROTO_IMAP;
287 #else
288 n_err(_("No IMAPS support compiled in\n"));
289 #endif
292 orig_name = name = &cp[3];
294 if(yeshooks){
295 fixrv = rv;
296 goto jcheck;
298 }else{
299 jfile:
300 rv = PROTO_FILE;
301 jcheck:
302 if(check_stat || try_hooks){
303 struct mx_filetype ft;
304 struct stat stb;
305 char *np;
306 uz i;
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))
320 #endif
322 rv =
323 #ifdef mx_HAVE_MAILDIR
324 PROTO_MAILDIR
325 #else
326 PROTO_UNKNOWN
327 #endif
330 }else if(try_hooks && mx_filetype_trial(&ft, name)){
331 orig_name = savecatsep(name, '.', ft.ft_ext_dat);
332 if(fixrv != PROTO_UNKNOWN)
333 rv = fixrv;
334 }else if(fixrv == PROTO_UNKNOWN &&
335 (cp = ok_vlook(newfolders)) != NIL &&
336 !su_cs_cmp_case(cp, "maildir")){
337 rv =
338 #ifdef mx_HAVE_MAILDIR
339 PROTO_MAILDIR
340 #else
341 PROTO_UNKNOWN
342 #endif
344 #ifndef mx_HAVE_MAILDIR
345 n_err(_("*newfolders*: no Maildir support compiled in\n"));
346 #endif
349 n_lofi_free(np);
351 if(fixrv != PROTO_UNKNOWN && fixrv != rv)
352 rv = PROTO_UNKNOWN;
356 if(adjusted_or_nil != NIL)
357 *adjusted_or_nil = orig_name;
359 NYD2_OU;
360 return rv;
363 FL char *
364 n_c_to_hex_base16(char store[3], char c){
365 static char const itoa16[] = "0123456789ABCDEF";
366 NYD2_IN;
368 store[2] = '\0';
369 store[1] = itoa16[(u8)c & 0x0F];
370 c = ((u8)c >> 4) & 0x0F;
371 store[0] = itoa16[(u8)c];
372 NYD2_OU;
373 return store;
376 FL s32
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 */
387 u8 i1, i2;
388 s32 rv;
389 NYD2_IN;
391 if ((i1 = (u8)hex[0] - '0') >= NELEM(atoi16) ||
392 (i2 = (u8)hex[1] - '0') >= NELEM(atoi16))
393 goto jerr;
394 i1 = atoi16[i1];
395 i2 = atoi16[i2];
396 if ((i1 | i2) & 0xF0u)
397 goto jerr;
398 rv = i1;
399 rv <<= 4;
400 rv += i2;
401 jleave:
402 NYD2_OU;
403 return rv;
404 jerr:
405 rv = -1;
406 goto jleave;
409 FL char const *
410 n_getdeadletter(void){
411 char const *cp;
412 boole bla;
413 NYD_IN;
415 bla = FAL0;
416 jredo:
417 cp = fexpand(ok_vlook(DEAD), FEXP_NOPROTO | FEXP_LOCAL_FILE | FEXP_NSHELL);
418 if(cp == NULL || su_cs_len(cp) >= PATH_MAX){
419 if(!bla){
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));
422 ok_vclear(DEAD);
423 bla = TRU1;
424 goto jredo;
425 }else{
426 cp = savecatsep(ok_vlook(TMPDIR), '/', VAL_DEAD_BASENAME);
427 n_err(_("Cannot expand *DEAD*, using: %s\n"), cp);
430 NYD_OU;
431 return cp;
434 FL char *
435 n_nodename(boole mayoverride){
436 static char *sys_hostname, *hostname; /* XXX free-at-exit */
438 struct utsname ut;
439 char *hn;
440 #ifdef mx_HAVE_NET
441 # ifdef mx_HAVE_GETADDRINFO
442 struct addrinfo hints, *res;
443 # else
444 struct hostent *hent;
445 # endif
446 #endif
447 NYD2_IN;
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){
454 boole lofi;
456 lofi = FAL0;
457 uname(&ut);
458 hn = ut.nodename;
460 #ifdef mx_HAVE_NET
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){
467 uz l;
469 l = su_cs_len(res->ai_canonname) +1;
470 hn = n_lofi_alloc(l);
471 lofi = TRU1;
472 su_mem_copy(hn, res->ai_canonname, l);
474 freeaddrinfo(res);
476 # else
477 hent = gethostbyname(hn);
478 if(hent != NULL)
479 hn = hent->h_name;
480 # endif
481 #endif /* mx_HAVE_NET */
483 /* Ensure it is non-empty! */
484 if(hn[0] == '\0')
485 hn = n_UNCONST(n_LOCALHOST_DEFAULT_NAME);
487 #ifdef mx_HAVE_IDNA
488 /* C99 */{
489 struct n_string cnv;
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);*/
500 #else
501 sys_hostname = su_cs_dup(hn, 0);
502 #endif
504 if(lofi)
505 n_lofi_free(hn);
506 hn = sys_hostname;
509 if(hostname != NULL && hostname != sys_hostname)
510 n_free(hostname);
511 hostname = su_cs_dup(hn, 0);
512 NYD2_OU;
513 return hostname;
516 #ifdef mx_HAVE_IDNA
517 FL boole
518 n_idna_to_ascii(struct n_string *out, char const *ibuf, uz ilen){
519 char *idna_utf8;
520 boole lofi, rv;
521 NYD_IN;
523 if(ilen == UZ_MAX)
524 ilen = su_cs_len(ibuf);
526 lofi = FAL0;
528 if((rv = (ilen == 0)))
529 goto jleave;
530 if(ibuf[ilen] != '\0'){
531 lofi = TRU1;
532 idna_utf8 = n_lofi_alloc(ilen +1);
533 su_mem_copy(idna_utf8, ibuf, ilen);
534 idna_utf8[ilen] = '\0';
535 ibuf = idna_utf8;
537 ilen = 0;
539 # ifndef mx_HAVE_ALWAYS_UNICODE_LOCALE
540 if(n_psonce & n_PSO_UNICODE)
541 # endif
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)
546 goto jleave;
547 # endif
549 # if mx_HAVE_IDNA == n_IDNA_IMPL_LIBIDN2
550 /* C99 */{
551 char *idna_ascii;
552 int f, rc;
554 f = IDN2_NONTRANSITIONAL;
555 jidn2_redo:
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);
559 rv = TRU1;
560 ilen = out->s_len;
561 }else if(rc == IDN2_DISALLOWED && f != IDN2_TRANSITIONAL){
562 f = IDN2_TRANSITIONAL;
563 goto jidn2_redo;
567 # elif mx_HAVE_IDNA == n_IDNA_IMPL_LIBIDN
568 /* C99 */{
569 char *idna_ascii;
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);
574 rv = TRU1;
575 ilen = out->s_len;
579 # elif mx_HAVE_IDNA == n_IDNA_IMPL_IDNKIT
580 ilen = su_cs_len(idna_utf8);
581 jredo:
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
588 # else
589 IDN_DELIMMAP | IDN_LOCALMAP | IDN_NAMEPREP | IDN_IDNCONV |
590 IDN_LENCHECK | IDN_ASCCHECK
591 # endif
592 ), idna_utf8,
593 n_string_resize(n_string_trunc(out, 0), ilen)->s_dat, ilen)){
594 case idn_buffer_overflow:
595 ilen += HOST_NAME_MAX +1;
596 goto jredo;
597 case idn_success:
598 rv = TRU1;
599 ilen = su_cs_len(out->s_dat);
600 break;
601 default:
602 ilen = 0;
603 break;
606 # else
607 # error Unknown mx_HAVE_IDNA
608 # endif
609 jleave:
610 if(lofi)
611 n_lofi_free(n_UNCONST(ibuf));
612 out = n_string_trunc(out, ilen);
613 NYD_OU;
614 return rv;
616 #endif /* mx_HAVE_IDNA */
618 FL boole
619 n_boolify(char const *inbuf, uz inlen, boole emptyrv){
620 boole rv;
621 NYD2_IN;
622 ASSERT(inlen == 0 || inbuf != NULL);
624 if(inlen == UZ_MAX)
625 inlen = su_cs_len(inbuf);
627 if(inlen == 0)
628 rv = (emptyrv >= FAL0) ? (emptyrv == FAL0 ? FAL0 : TRU1) : TRU2;
629 else{
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))
634 rv = TRU1;
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))
640 rv = FAL0;
641 else{
642 u64 ib;
644 if((su_idec(&ib, inbuf, inlen, 0, 0, NULL) & (su_IDEC_STATE_EMASK |
645 su_IDEC_STATE_CONSUMED)) != su_IDEC_STATE_CONSUMED)
646 rv = TRUM1;
647 else
648 rv = (ib != 0);
651 NYD2_OU;
652 return rv;
655 FL boole
656 n_quadify(char const *inbuf, uz inlen, char const *prompt, boole emptyrv){
657 boole rv;
658 NYD2_IN;
659 ASSERT(inlen == 0 || inbuf != NULL);
661 if(inlen == UZ_MAX)
662 inlen = su_cs_len(inbuf);
664 if(inlen == 0)
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);
671 NYD2_OU;
672 return rv;
675 FL boole
676 n_is_all_or_aster(char const *name){
677 boole rv;
678 NYD2_IN;
680 rv = ((name[0] == '*' && name[1] == '\0') || !su_cs_cmp_case(name, "all"));
681 NYD2_OU;
682 return rv;
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;
688 NYD2_IN;
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),
693 0,NULL);
694 ts_now.ts_nsec = 0;
695 }else if(force_update || ts_now.ts_sec == 0){
696 #ifdef mx_HAVE_CLOCK_GETTIME
697 struct timespec ts;
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
703 struct timeval tv;
705 gettimeofday(&tv, NULL);
706 ts_now.ts_sec = (s64)tv.tv_sec;
707 ts_now.ts_nsec = (sz)tv.tv_usec * 1000;
708 #else
709 ts_now.ts_sec = (s64)time(NULL);
710 ts_now.ts_nsec = 0;
711 #endif
714 /* Just in case.. */
715 if(UNLIKELY(ts_now.ts_sec < 0))
716 ts_now.ts_sec = 0;
717 NYD2_OU;
718 return &ts_now;
721 FL void
722 time_current_update(struct time_current *tc, boole full_update){
723 NYD_IN;
724 tc->tc_time = (time_t)n_time_now(TRU1)->ts_sec;
726 if(full_update){
727 char *cp;
728 struct tm *tmp;
729 time_t t;
731 t = tc->tc_time;
732 jredo:
733 if((tmp = gmtime(&t)) == NULL){
734 t = 0;
735 goto jredo;
737 su_mem_copy(&tc->tc_gm, tmp, sizeof tc->tc_gm);
738 if((tmp = localtime(&t)) == NULL){
739 t = 0;
740 goto jredo;
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));
744 *cp++ = '\n';
745 *cp = '\0';
746 ASSERT(P2UZ(++cp - tc->tc_ctime) < sizeof(tc->tc_ctime));
748 NYD_OU;
751 FL char *
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;
767 NYD_IN;
769 if((tmp = localtime_or_nil) == NULL){
770 time_t t;
772 t = (time_t)secsepoch;
773 jredo:
774 if((tmp = localtime(&t)) == NULL){
775 /* TODO error log */
776 t = 0;
777 goto jredo;
781 if(UNLIKELY((y = tmp->tm_year) < 0 || y >= 9999/*S32_MAX*/ - 1900)){
782 y = 1970;
783 wdn = n_weekday_names[4];
784 mn = n_month_names[0];
785 md = 1;
786 th = tm = ts = 0;
787 }else{
788 y += 1900;
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)
795 md = 1;
797 if((th = tmp->tm_hour) < 0 || th > 23)
798 th = 0;
799 if((tm = tmp->tm_min) < 0 || tm > 59)
800 tm = 0;
801 if((ts = tmp->tm_sec) < 0 || ts > 60)
802 ts = 0;
805 (void)snprintf(buf, sizeof buf, "%3s %3s%3d %.2d:%.2d:%.2d %d",
806 wdn, mn, md, th, tm, ts, y);
807 NYD_OU;
808 return buf;
811 FL uz
812 n_msleep(uz millis, boole ignint){
813 uz rv;
814 NYD2_IN;
816 #ifdef mx_HAVE_NANOSLEEP
817 /* C99 */{
818 struct timespec ts, trem;
819 int i;
821 ts.tv_sec = millis / 1000;
822 ts.tv_nsec = (millis %= 1000) * 1000 * 1000;
824 while((i = nanosleep(&ts, &trem)) != 0 && ignint)
825 ts = trem;
826 rv = (i == 0) ? 0
827 : (trem.tv_sec * 1000) + (trem.tv_nsec / (1000 * 1000));
830 #elif defined mx_HAVE_SLEEP
831 if((millis /= 1000) == 0)
832 millis = 1;
833 while((rv = sleep((unsigned int)millis)) != 0 && ignint)
834 millis = rv;
835 #else
836 # error Configuration should have detected a function for sleeping.
837 #endif
839 NYD2_OU;
840 return rv;
843 FL void
844 n_err(char const *format, ...){
845 va_list ap;
846 NYD2_IN;
848 va_start(ap, format);
849 n_verrx(FAL0, format, ap);
850 va_end(ap);
851 NYD2_OU;
854 FL void
855 n_errx(boole allow_multiple, char const *format, ...){
856 va_list ap;
857 NYD2_IN;
859 va_start(ap, format);
860 n_verrx(allow_multiple, format, ap);
861 va_end(ap);
862 NYD2_OU;
865 FL void
866 n_verr(char const *format, va_list ap){
867 NYD2_IN;
868 n_verrx(FAL0, format, ap);
869 NYD2_OU;
872 FL void
873 n_verrx(boole allow_multiple, char const *format, va_list ap){/*XXX sigcondom*/
874 mx_COLOUR( static uz c5recur; ) /* *termcap* recursion */
875 struct str s_b, s;
876 struct a_aux_err_node *lenp, *enp;
877 sz i;
878 char const *lpref, *c5pref, *c5suff;
879 NYD2_IN;
881 mx_COLOUR( ++c5recur; )
882 lpref = NIL;
883 c5pref = c5suff = su_empty;
885 /* Fully expand the buffer (TODO use fmtenc) */
886 #undef a_X
887 #ifdef mx_HAVE_N_VA_COPY
888 # define a_X 128
889 #else
890 # define a_X MIN(LINESIZE, 1024)
891 #endif
892 mx_fs_linepool_aquire(&s_b.s, &s_b.l);
893 i = 0;
894 if(s_b.l < a_X)
895 i = s_b.l = a_X;
896 #undef a_X
898 for(;; s_b.l = ++i /* xxx could wrap, maybe */){
899 #ifdef mx_HAVE_N_VA_COPY
900 va_list vac;
902 n_va_copy(vac, ap);
903 #else
904 # define vac ap
905 #endif
907 if(i != 0)
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
913 va_end(vac);
914 #else
915 # undef vac
916 #endif
918 if(i <= 0)
919 goto jleave;
920 if(UCMP(z, i, >=, s_b.l)){
921 #ifdef mx_HAVE_N_VA_COPY
922 continue;
923 #else
924 i = S(int,su_cs_len(s_b.s));
925 #endif
927 break;
929 s = s_b;
930 s.l = S(uz,i);
932 /* Remove control characters but \n as we do not makeprint() XXX config */
933 /* C99 */{
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')
938 *ins++ = c;
939 *ins = '\0';
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;
951 /* C99 */{
952 u32 poption_save;
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)
968 ) != NIL){
969 c5pref = pref->s;
970 c5suff = suff->s;
973 #endif
975 n_poption = poption_save;
978 for(i = 0; UCMP(z, i, <, s.l);){
979 char *cp;
980 boole isdup;
982 lenp = enp = a_aux_err_tail;
983 if(enp == NIL || enp->ae_done){
984 enp = su_TCALLOC(struct a_aux_err_node, 1);
985 enp->ae_cnt = 1;
986 n_string_creat(&enp->ae_str);
988 if(a_aux_err_tail != NIL)
989 a_aux_err_tail->ae_next = enp;
990 else
991 a_aux_err_head = enp;
992 a_aux_err_tail = enp;
995 /* xxx if(!n_string_book(&enp->ae_str, s.l - i))
996 * xxx goto jleave;*/
998 /* We have completed a line? */
999 /* C99 */{
1000 uz oi, j, k;
1002 oi = S(uz,i);
1003 j = s.l - oi;
1004 k = enp->ae_str.s_len;
1005 cp = S(char*,su_mem_find(&s.s[oi], '\n', j));
1007 if(cp == NIL){
1008 n_string_push_buf(&enp->ae_str, &s.s[oi], j);
1009 i = s.l;
1010 }else{
1011 j = P2UZ(cp - &s.s[oi]);
1012 i += j + 1;
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));
1023 fflush(n_stderr);
1024 enp->ae_dumped_till = enp->ae_str.s_len;
1028 if(cp == NIL)
1029 continue;
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 */
1035 isdup = FAL0;
1036 if(lenp != NIL){
1037 if(lenp != enp &&
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)){
1041 ++lenp->ae_cnt;
1042 isdup = TRU1;
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)){
1048 fprintf(n_stderr,
1049 _("%s%s-- Last message repeated %u times --%s\n"),
1050 c5pref, lpref, lenp->ae_cnt, c5suff);
1051 fflush(n_stderr);
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);
1060 fflush(n_stderr);
1063 if(isdup){
1064 lenp->ae_next = NIL;
1065 a_aux_err_tail = lenp;
1066 n_string_gut(&enp->ae_str);
1067 su_FREE(enp);
1068 continue;
1071 #ifdef mx_HAVE_ERRORS
1072 if(n_pstate_err_cnt < mx_ERRORS_MAX){
1073 ++n_pstate_err_cnt;
1074 continue;
1076 a_aux_err_head = (lenp = a_aux_err_head)->ae_next;
1077 #else
1078 a_aux_err_head = a_aux_err_tail = enp;
1079 #endif
1080 if(lenp != NIL){
1081 n_string_gut(&lenp->ae_str);
1082 su_FREE(lenp);
1086 jleave:
1087 mx_fs_linepool_release(s_b.s, s_b.l);
1088 mx_COLOUR( --c5recur; )
1089 NYD2_OU;
1092 FL void
1093 n_err_sighdl(char const *format, ...){ /* TODO sigsafe; obsolete! */
1094 va_list ap;
1095 NYD;
1097 va_start(ap, format);
1098 vfprintf(n_stderr, format, ap);
1099 va_end(ap);
1100 fflush(n_stderr);
1103 FL void
1104 n_perr(char const *msg, int errval){
1105 int e;
1106 char const *fmt;
1107 NYD2_IN;
1109 if(msg == NULL){
1110 fmt = "%s%s\n";
1111 msg = n_empty;
1112 }else
1113 fmt = "%s: %s\n";
1115 e = (errval == 0) ? su_err_no() : errval;
1116 n_errx(FAL0, fmt, msg, su_err_doc(e));
1117 if(errval == 0)
1118 su_err_set_no(e);
1119 NYD2_OU;
1122 FL void
1123 n_alert(char const *format, ...){
1124 va_list ap;
1125 NYD2_IN;
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);
1133 va_end(ap);
1135 n_errx(TRU1, "\n");
1136 NYD2_OU;
1139 FL void
1140 n_panic(char const *format, ...){
1141 va_list ap;
1142 NYD2_IN;
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);
1152 va_end(ap);
1154 putc('\n', n_stderr);
1155 fflush(n_stderr);
1156 NYD2_OU;
1157 abort(); /* Was exit(n_EXIT_ERR); for a while, but no */
1160 #ifdef mx_HAVE_ERRORS
1161 FL int
1162 c_errors(void *v){
1163 char **argv = v;
1164 struct a_aux_err_node *enp;
1165 NYD_IN;
1167 if(*argv == NULL)
1168 goto jlist;
1169 if(argv[1] != NULL)
1170 goto jerr;
1171 if(!su_cs_cmp_case(*argv, "show"))
1172 goto jlist;
1173 if(!su_cs_cmp_case(*argv, "clear"))
1174 goto jclear;
1175 jerr:
1176 mx_cmd_print_synopsis(mx_cmd_firstfit("errors"), NIL);
1177 v = NIL;
1178 jleave:
1179 NYD_OU;
1180 return (v == NULL) ? !STOP : !OKAY; /* xxx 1:bad 0:good -- do some */
1182 jlist:{
1183 FILE *fp;
1184 uz i;
1186 if(a_aux_err_head == NIL){
1187 fprintf(n_stderr, _("The error ring is empty\n"));
1188 goto jleave;
1191 if((fp = mx_fs_tmp_open("errors", (mx_FS_O_RDWR | mx_FS_O_UNLINK |
1192 mx_FS_O_REGISTER), NIL)) == NIL)
1193 fp = n_stdout;
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));
1199 if(fp != n_stdout){
1200 page_or_print(fp, 0);
1202 mx_fs_close(fp);
1203 }else
1204 clearerr(fp);
1206 /* FALLTHRU */
1208 jclear:
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);
1214 su_FREE(enp);
1216 goto jleave;
1218 #endif /* mx_HAVE_ERRORS */
1220 #ifdef mx_HAVE_REGEX
1221 FL char const *
1222 n_regex_err_to_doc(const regex_t *rep, int e){
1223 char *cp;
1224 uz i;
1225 NYD2_IN;
1227 i = regerror(e, rep, NULL, 0) +1;
1228 cp = n_autorec_alloc(i);
1229 regerror(e, rep, cp, i);
1230 NYD2_OU;
1231 return cp;
1233 #endif
1235 FL boole
1236 mx_unxy_dict(char const *cmdname, struct su_cs_dict *dp, void *vp){
1237 char const **argv, *key;
1238 boole rv;
1239 NYD_IN;
1241 rv = TRU1;
1242 key = (argv = vp)[0];
1245 if(key[1] == '\0' && key[0] == '*'){
1246 if(dp != NIL)
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));
1250 rv = FAL0;
1252 }while((key = *++argv) != NIL);
1254 NYD_OU;
1255 return rv;
1258 FL boole
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,
1262 void const *dat)){
1263 struct su_cs_dict_view dv;
1264 char const **cpp, **xcpp;
1265 u32 cnt;
1266 struct n_strlist *resp, *tailp;
1267 boole rv;
1268 NYD_IN;
1270 rv = TRU1;
1272 resp = *result;
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)
1278 break;
1280 if(dp == NIL || (cnt = su_cs_dict_count(dp)) == 0)
1281 goto jleave;
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)
1290 goto jleave;
1292 xcpp = cpp;
1293 su_CS_DICT_FOREACH(dp, &dv)
1294 *xcpp++ = su_cs_dict_view_key(&dv);
1295 if(cnt > 1)
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)
1304 continue;
1305 if(resp == NIL)
1306 resp = slp;
1307 else
1308 tailp->sl_next = slp;
1309 tailp = slp;
1312 jleave:
1313 *result = resp;
1314 if(tailpp_or_nil != NIL)
1315 *tailpp_or_nil = tailp;
1317 NYD_OU;
1318 return rv;
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() */
1324 char *cp;
1325 struct n_strlist *slp;
1326 uz kl, dl, cl;
1327 char const *kp, *dp;
1328 NYD2_IN;
1330 kp = n_shexp_quote_cp(key, TRU1);
1331 dp = n_shexp_quote_cp(su_S(char const*,dat), TRU1);
1332 kl = su_cs_len(kp);
1333 dl = su_cs_len(dp);
1334 cl = su_cs_len(cmdname);
1336 slp = n_STRLIST_AUTO_ALLOC(cl + 1 + kl + 1 + dl +1);
1337 slp->sl_next = NIL;
1338 cp = slp->sl_dat;
1339 su_mem_copy(cp, cmdname, cl);
1340 cp += cl;
1341 *cp++ = ' ';
1342 su_mem_copy(cp, kp, kl);
1343 cp += kl;
1344 *cp++ = ' ';
1345 su_mem_copy(cp, dp, dl);
1346 cp += dl;
1347 *cp = '\0';
1348 slp->sl_len = P2UZ(cp - slp->sl_dat);
1350 NYD2_OU;
1351 return slp;
1354 FL boole
1355 mx_page_or_print_strlist(char const *cmdname, struct n_strlist *slp,
1356 boole cnt_lines){
1357 uz lines;
1358 FILE *fp;
1359 boole rv;
1360 NYD_IN;
1362 rv = TRU1;
1364 if((fp = mx_fs_tmp_open(cmdname, (mx_FS_O_RDWR | mx_FS_O_UNLINK |
1365 mx_FS_O_REGISTER), NIL)) == NIL)
1366 fp = n_stdout;
1368 /* Create visual result */
1369 for(lines = 0; slp != NIL; slp = slp->sl_next){
1370 if(fputs(slp->sl_dat, fp) == EOF){
1371 rv = FAL0;
1372 break;
1375 if(!cnt_lines){
1376 jputnl:
1377 if(putc('\n', fp) == EOF){
1378 rv = FAL0;
1379 break;
1381 ++lines;
1382 }else{
1383 char *cp;
1384 boole lastnl;
1386 for(lastnl = FAL0, cp = slp->sl_dat; *cp != '\0'; ++cp)
1387 if((lastnl = (*cp == '\n')))
1388 ++lines;
1389 if(!lastnl)
1390 goto jputnl;
1394 if(rv && lines == 0){
1395 if(fprintf(fp, _("# `%s': no data available\n"), cmdname) < 0)
1396 rv = FAL0;
1397 else
1398 lines = 1;
1401 if(fp != n_stdout){
1402 page_or_print(fp, lines);
1404 mx_fs_close(fp);
1405 }else
1406 clearerr(fp);
1408 NYD_OU;
1409 return rv;
1412 #include "su/code-ou.h"
1413 /* s-it-mode */