Add *forward-inject-tail*, *quote-inject-{head,tail}*, `~Q'..
[s-mailx.git] / cmd-misc.c
blobbdc6e8191e7b98ac8d628fe2ecbe700191b79284
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Miscellaneous user commands, like `echo', `pwd', etc.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2018 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
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 cmd_misc
38 #ifndef HAVE_AMALGAMATION
39 # include "nail.h"
40 #endif
42 #include <sys/utsname.h>
44 /* Expand the shell escape by expanding unescaped !'s into the last issued
45 * command where possible */
46 static char const *a_cmisc_bangexp(char const *cp);
48 /* c_n?echo(), c_n?echoerr() */
49 static int a_cmisc_echo(void *vp, FILE *fp, bool_t donl);
51 /* c_read() */
52 static bool_t a_cmisc_read_set(char const *cp, char const *value);
54 /* c_version() */
55 static int a_cmisc_version_cmp(void const *s1, void const *s2);
57 static char const *
58 a_cmisc_bangexp(char const *cp){
59 static struct str last_bang;
61 struct n_string xbang, *bang;
62 char c;
63 bool_t changed;
64 NYD_ENTER;
66 if(!ok_blook(bang))
67 goto jleave;
69 changed = FAL0;
71 for(bang = n_string_creat(&xbang); (c = *cp++) != '\0';){
72 if(c == '!'){
73 if(last_bang.l > 0)
74 bang = n_string_push_buf(bang, last_bang.s, last_bang.l);
75 changed = TRU1;
76 }else{
77 if(c == '\\' && *cp == '!'){
78 ++cp;
79 c = '!';
80 changed = TRU1;
82 bang = n_string_push_c(bang, c);
86 if(last_bang.s != NULL)
87 n_free(last_bang.s);
88 last_bang.s = n_string_cp(bang);
89 last_bang.l = bang->s_len;
90 bang = n_string_drop_ownership(bang);
91 n_string_gut(bang);
93 cp = last_bang.s;
94 if(changed)
95 fprintf(n_stdout, "!%s\n", cp);
96 jleave:
97 NYD_LEAVE;
98 return cp;
101 static int
102 a_cmisc_echo(void *vp, FILE *fp, bool_t donl){
103 struct n_string s, *sp;
104 int rv;
105 bool_t doerr;
106 char const **argv, *varname, **ap, *cp;
107 NYD2_ENTER;
109 argv = vp;
110 varname = (n_pstate & n_PS_ARGMOD_VPUT) ? *argv++ : NULL;
111 sp = n_string_reserve(n_string_creat_auto(&s), 121/* XXX */);
112 #ifdef HAVE_ERRORS
113 doerr = (fp == n_stderr && (n_psonce & n_PSO_INTERACTIVE));
114 #else
115 doerr = FAL0;
116 #endif
118 for(ap = argv; *ap != NULL; ++ap){
119 if(ap != argv)
120 sp = n_string_push_c(sp, ' ');
121 if((cp = fexpand(*ap, FEXP_NSHORTCUT | FEXP_NVAR)) == NULL)
122 cp = *ap;
123 sp = n_string_push_cp(sp, cp);
125 if(donl)
126 sp = n_string_push_c(sp, '\n');
127 cp = n_string_cp(sp);
129 if(varname == NULL){
130 si32_t e;
132 e = n_ERR_NONE;
133 if(doerr){
134 /* xxx Ensure *log-prefix* will be placed by n_err() for next msg */
135 if(donl)
136 cp = n_string_cp(n_string_trunc(sp, sp->s_len - 1));
137 n_err((donl ? "%s\n" : "%s"), cp);
138 }else if(fputs(cp, fp) == EOF)
139 e = n_err_no;
140 if((rv = (fflush(fp) == EOF)))
141 e = n_err_no;
142 rv |= ferror(fp) ? 1 : 0;
143 n_pstate_err_no = e;
144 }else if(!n_var_vset(varname, (uintptr_t)cp)){
145 n_pstate_err_no = n_ERR_NOTSUP;
146 rv = -1;
147 }else{
148 n_pstate_err_no = n_ERR_NONE;
149 rv = (int)sp->s_len;
151 NYD2_LEAVE;
152 return rv;
155 static bool_t
156 a_cmisc_read_set(char const *cp, char const *value){
157 bool_t rv;
158 NYD2_ENTER;
160 if(!n_shexp_is_valid_varname(cp))
161 value = N_("not a valid variable name");
162 else if(!n_var_is_user_writable(cp))
163 value = N_("variable is read-only");
164 else if(!n_var_vset(cp, (uintptr_t)value))
165 value = N_("failed to update variable value");
166 else{
167 rv = TRU1;
168 goto jleave;
170 n_err("`read': %s: %s\n", V_(value), n_shexp_quote_cp(cp, FAL0));
171 rv = FAL0;
172 jleave:
173 NYD2_LEAVE;
174 return rv;
177 static int
178 a_cmisc_version_cmp(void const *s1, void const *s2){
179 char const * const *cp1, * const *cp2;
180 int rv;
181 NYD2_ENTER;
183 cp1 = s1;
184 cp2 = s2;
185 rv = strcmp(&(*cp1)[1], &(*cp2)[1]);
186 NYD2_LEAVE;
187 return rv;
190 FL int
191 c_sleep(void *v){
192 uiz_t sec, msec;
193 char **argv;
194 NYD_ENTER;
196 argv = v;
198 if((n_idec_uiz_cp(&sec, argv[0], 0, NULL) &
199 (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
200 ) != n_IDEC_STATE_CONSUMED)
201 goto jesyn;
203 if(argv[1] == NULL)
204 msec = 0;
205 else if((n_idec_uiz_cp(&msec, argv[1], 0, NULL) &
206 (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
207 ) != n_IDEC_STATE_CONSUMED)
208 goto jesyn;
210 if(UIZ_MAX / n_DATE_MILLISSEC < sec)
211 goto jeover;
212 sec *= n_DATE_MILLISSEC;
214 if(UIZ_MAX - sec < msec)
215 goto jeover;
216 msec += sec;
218 n_pstate_err_no = (n_msleep(msec, (argv[2] == NULL)) > 0)
219 ? n_ERR_INTR : n_ERR_NONE;
220 jleave:
221 NYD_LEAVE;
222 return (argv == NULL);
223 jeover:
224 n_err(_("`sleep': argument(s) overflow(s) datatype\n"));
225 n_pstate_err_no = n_ERR_OVERFLOW;
226 argv = NULL;
227 goto jleave;
228 jesyn:
229 n_err(_("Synopsis: sleep: <seconds> [<milliseconds>] [uninterruptible]\n"));
230 n_pstate_err_no = n_ERR_INVAL;
231 argv = NULL;
232 goto jleave;
235 FL int
236 c_shell(void *v)
238 sigset_t mask;
239 int rv;
240 FILE *fp;
241 char const **argv, *varname, *varres, *cp;
242 NYD_ENTER;
244 n_pstate_err_no = n_ERR_NONE;
245 argv = v;
246 varname = (n_pstate & n_PS_ARGMOD_VPUT) ? *argv++ : NULL;
247 varres = n_empty;
248 fp = NULL;
250 if(varname != NULL &&
251 (fp = Ftmp(NULL, "shell", OF_RDWR | OF_UNLINK | OF_REGISTER)
252 ) == NULL){
253 n_pstate_err_no = n_ERR_CANCELED;
254 rv = -1;
255 }else{
256 cp = a_cmisc_bangexp(*argv);
258 sigemptyset(&mask);
259 if(n_child_run(ok_vlook(SHELL), &mask,
260 n_CHILD_FD_PASS, (fp != NULL ? fileno(fp) : n_CHILD_FD_PASS),
261 "-c", cp, NULL, NULL, &rv) < 0){
262 n_pstate_err_no = n_err_no;
263 rv = -1;
264 }else{
265 rv = WEXITSTATUS(rv);
267 if(fp != NULL){
268 int c;
269 char *x;
270 off_t l;
272 fflush_rewind(fp);
273 l = fsize(fp);
274 if(UICMP(64, l, >=, UIZ_MAX -42)){
275 n_pstate_err_no = n_ERR_NOMEM;
276 varres = n_empty;
277 }else{
278 varres = x = n_autorec_alloc(l +1);
280 for(; l > 0 && (c = getc(fp)) != EOF; --l)
281 *x++ = c;
282 *x++ = '\0';
283 if(l != 0){
284 n_pstate_err_no = n_err_no;
285 varres = n_empty; /* xxx hmmm */
292 if(fp != NULL)
293 Fclose(fp);
295 if(varname != NULL){
296 if(!n_var_vset(varname, (uintptr_t)varres)){
297 n_pstate_err_no = n_ERR_NOTSUP;
298 rv = -1;
300 }else if(rv >= 0 && (n_psonce & n_PSO_INTERACTIVE)){
301 fprintf(n_stdout, "!\n");
302 /* Line buffered fflush(n_stdout); */
304 NYD_LEAVE;
305 return rv;
308 FL int
309 c_dosh(void *v)
311 int rv;
312 NYD_ENTER;
313 n_UNUSED(v);
315 if(n_child_run(ok_vlook(SHELL), 0, n_CHILD_FD_PASS, n_CHILD_FD_PASS, NULL,
316 NULL, NULL, NULL, &rv) < 0){
317 n_pstate_err_no = n_err_no;
318 rv = -1;
319 }else{
320 putc('\n', n_stdout);
321 /* Line buffered fflush(n_stdout); */
322 n_pstate_err_no = n_ERR_NONE;
323 rv = WEXITSTATUS(rv);
325 NYD_LEAVE;
326 return rv;
329 FL int
330 c_cwd(void *v){
331 struct n_string s_b, *sp;
332 size_t l;
333 char const *varname;
334 NYD_ENTER;
336 sp = n_string_creat_auto(&s_b);
337 varname = (n_pstate & n_PS_ARGMOD_VPUT) ? *(char const**)v : NULL;
338 l = PATH_MAX;
340 for(;; l += PATH_MAX){
341 sp = n_string_resize(n_string_trunc(sp, 0), l);
343 if(getcwd(sp->s_dat, sp->s_len) == NULL){
344 int e;
346 e = n_err_no;
347 if(e == n_ERR_RANGE)
348 continue;
349 n_perr(_("Failed to getcwd(3)"), e);
350 v = NULL;
351 break;
354 if(varname != NULL){
355 if(!n_var_vset(varname, (uintptr_t)sp->s_dat))
356 v = NULL;
357 }else{
358 l = strlen(sp->s_dat);
359 sp = n_string_trunc(sp, l);
360 if(fwrite(sp->s_dat, 1, sp->s_len, n_stdout) == sp->s_len &&
361 putc('\n', n_stdout) == EOF)
362 v = NULL;
364 break;
366 NYD_LEAVE;
367 return (v == NULL);
370 FL int
371 c_chdir(void *v)
373 char **arglist = v;
374 char const *cp;
375 NYD_ENTER;
377 if (*arglist == NULL)
378 cp = ok_vlook(HOME);
379 else if ((cp = fexpand(*arglist, FEXP_LOCAL | FEXP_NOPROTO)) == NULL)
380 goto jleave;
381 if (chdir(cp) == -1) {
382 n_perr(cp, 0);
383 cp = NULL;
385 jleave:
386 NYD_LEAVE;
387 return (cp == NULL);
390 FL int
391 c_echo(void *v){
392 int rv;
393 NYD_ENTER;
395 rv = a_cmisc_echo(v, n_stdout, TRU1);
396 NYD_LEAVE;
397 return rv;
400 FL int
401 c_echoerr(void *v){
402 int rv;
403 NYD_ENTER;
405 rv = a_cmisc_echo(v, n_stderr, TRU1);
406 NYD_LEAVE;
407 return rv;
410 FL int
411 c_echon(void *v){
412 int rv;
413 NYD_ENTER;
415 rv = a_cmisc_echo(v, n_stdout, FAL0);
416 NYD_LEAVE;
417 return rv;
420 FL int
421 c_echoerrn(void *v){
422 int rv;
423 NYD_ENTER;
425 rv = a_cmisc_echo(v, n_stderr, FAL0);
426 NYD_LEAVE;
427 return rv;
430 FL int
431 c_read(void * volatile vp){
432 struct n_sigman sm;
433 struct str trim;
434 struct n_string s, *sp;
435 char *linebuf;
436 size_t linesize, i;
437 int rv;
438 char const *ifs, **argv, *cp;
439 NYD2_ENTER;
441 sp = n_string_creat_auto(&s);
442 sp = n_string_reserve(sp, 64 -1);
444 ifs = ok_vlook(ifs);
445 linesize = 0;
446 linebuf = NULL;
447 argv = vp;
449 n_SIGMAN_ENTER_SWITCH(&sm, n_SIGMAN_ALL){
450 case 0:
451 break;
452 default:
453 n_pstate_err_no = n_ERR_INTR;
454 rv = -1;
455 goto jleave;
458 n_pstate_err_no = n_ERR_NONE;
459 rv = n_go_input(((n_pstate & n_PS_COMPOSE_MODE
460 ? n_GO_INPUT_CTX_COMPOSE : n_GO_INPUT_CTX_DEFAULT) |
461 n_GO_INPUT_FORCE_STDIN | n_GO_INPUT_NL_ESC |
462 n_GO_INPUT_PROMPT_NONE /* XXX POSIX: PS2: yes! */),
463 NULL, &linebuf, &linesize, NULL, NULL);
464 if(rv < 0){
465 if(!n_go_input_is_eof())
466 n_pstate_err_no = n_ERR_BADF;
467 goto jleave;
468 }else if(rv == 0){
469 if(n_go_input_is_eof()){
470 rv = -1;
471 goto jleave;
473 }else{
474 trim.s = linebuf;
475 trim.l = rv;
477 for(; *argv != NULL; ++argv){
478 if(trim.l == 0 || n_str_trim_ifs(&trim, FAL0)->l == 0)
479 break;
481 /* The last variable gets the remaining line less trailing IFS-WS */
482 if(argv[1] == NULL){
483 jitall:
484 sp = n_string_assign_buf(sp, trim.s, trim.l);
485 trim.l = 0;
486 }else for(cp = trim.s, i = 1;; ++cp, ++i){
487 if(strchr(ifs, *cp) != NULL){
488 sp = n_string_assign_buf(sp, trim.s, i - 1);
489 trim.s += i;
490 trim.l -= i;
491 break;
493 if(i == trim.l)
494 goto jitall;
497 if(!a_cmisc_read_set(*argv, n_string_cp(sp))){
498 n_pstate_err_no = n_ERR_NOTSUP;
499 rv = -1;
500 break;
505 /* Set the remains to the empty string */
506 for(; *argv != NULL; ++argv)
507 if(!a_cmisc_read_set(*argv, n_empty)){
508 n_pstate_err_no = n_ERR_NOTSUP;
509 rv = -1;
510 break;
513 n_sigman_cleanup_ping(&sm);
514 jleave:
515 if(linebuf != NULL)
516 n_free(linebuf);
517 NYD2_LEAVE;
518 n_sigman_leave(&sm, n_SIGMAN_VIPSIGS_NTTYOUT);
519 return rv;
522 FL int
523 c_readall(void * vp){ /* TODO 64-bit retval */
524 struct n_sigman sm;
525 struct n_string s, *sp;
526 char *linebuf;
527 size_t linesize;
528 int rv;
529 char const **argv;
530 NYD2_ENTER;
532 sp = n_string_creat_auto(&s);
533 sp = n_string_reserve(sp, 64 -1);
535 linesize = 0;
536 linebuf = NULL;
537 argv = vp;
539 n_SIGMAN_ENTER_SWITCH(&sm, n_SIGMAN_ALL){
540 case 0:
541 break;
542 default:
543 n_pstate_err_no = n_ERR_INTR;
544 rv = -1;
545 goto jleave;
548 n_pstate_err_no = n_ERR_NONE;
550 for(;;){
551 rv = n_go_input(((n_pstate & n_PS_COMPOSE_MODE
552 ? n_GO_INPUT_CTX_COMPOSE : n_GO_INPUT_CTX_DEFAULT) |
553 n_GO_INPUT_FORCE_STDIN | /*n_GO_INPUT_NL_ESC |*/
554 n_GO_INPUT_PROMPT_NONE),
555 NULL, &linebuf, &linesize, NULL, NULL);
556 if(rv < 0){
557 if(!n_go_input_is_eof()){
558 n_pstate_err_no = n_ERR_BADF;
559 goto jleave;
561 if(sp->s_len == 0)
562 goto jleave;
563 break;
566 if(n_pstate & n_PS_READLINE_NL)
567 linebuf[rv++] = '\n'; /* Replace NUL with it */
569 if(n_UNLIKELY(rv == 0)){ /* xxx will not get*/
570 if(n_go_input_is_eof()){
571 if(sp->s_len == 0){
572 rv = -1;
573 goto jleave;
575 break;
577 }else if(n_LIKELY(UICMP(32, SI32_MAX - sp->s_len, >, rv)))
578 sp = n_string_push_buf(sp, linebuf, rv);
579 else{
580 n_pstate_err_no = n_ERR_OVERFLOW;
581 rv = -1;
582 goto jleave;
586 if(!a_cmisc_read_set(argv[0], n_string_cp(sp))){
587 n_pstate_err_no = n_ERR_NOTSUP;
588 rv = -1;
589 goto jleave;
591 rv = sp->s_len;
593 n_sigman_cleanup_ping(&sm);
594 jleave:
595 if(linebuf != NULL)
596 n_free(linebuf);
597 NYD2_LEAVE;
598 n_sigman_leave(&sm, n_SIGMAN_VIPSIGS_NTTYOUT);
599 return rv;
602 FL int
603 c_version(void *vp){
604 struct utsname ut;
605 struct n_string s, *sp = &s;
606 int rv;
607 char *iop;
608 char const *cp, **arr;
609 size_t i, lnlen, j;
610 NYD_ENTER;
612 sp = n_string_creat_auto(sp);
613 sp = n_string_book(sp, 1024);
615 /* First two lines */
616 sp = n_string_push_cp(sp, n_uagent);
617 sp = n_string_push_c(sp, ' ');
618 sp = n_string_push_cp(sp, ok_vlook(version));
619 sp = n_string_push_c(sp, ',');
620 sp = n_string_push_c(sp, ' ');
621 sp = n_string_push_cp(sp, ok_vlook(version_date));
622 sp = n_string_push_c(sp, ' ');
623 sp = n_string_push_c(sp, '(');
624 sp = n_string_push_cp(sp, _("build for "));
625 sp = n_string_push_cp(sp, ok_vlook(build_os));
626 sp = n_string_push_c(sp, ')');
627 sp = n_string_push_cp(sp, _("\nFeatures included (+) or not (-):\n"));
629 /* Some lines with the features.
630 * *features* starts with dummy byte to avoid + -> *folder* expansions */
631 i = strlen(cp = &ok_vlook(features)[1]) +1;
632 iop = n_autorec_alloc(i);
633 memcpy(iop, cp, i);
635 arr = n_autorec_alloc(sizeof(cp) * VAL_FEATURES_CNT);
636 for(i = 0; (cp = n_strsep(&iop, ',', TRU1)) != NULL; ++i)
637 arr[i] = cp;
638 qsort(arr, i, sizeof(cp), &a_cmisc_version_cmp);
640 for(lnlen = 0; i-- > 0;){
641 cp = *(arr++);
642 j = strlen(cp);
644 if((lnlen += j + 1) > 72){
645 sp = n_string_push_c(sp, '\n');
646 lnlen = j + 1;
648 sp = n_string_push_c(sp, ' ');
649 sp = n_string_push_buf(sp, cp, j);
651 sp = n_string_push_c(sp, '\n');
653 /* */
654 if(n_poption & n_PO_VERB){
655 sp = n_string_push_cp(sp, "Compile: ");
656 sp = n_string_push_cp(sp, ok_vlook(build_cc));
657 sp = n_string_push_cp(sp, "\nLink: ");
658 sp = n_string_push_cp(sp, ok_vlook(build_ld));
659 if(*(cp = ok_vlook(build_rest)) != '\0'){
660 sp = n_string_push_cp(sp, "\nRest: ");
661 sp = n_string_push_cp(sp, cp);
663 sp = n_string_push_c(sp, '\n');
665 /* A trailing line with info of the running machine */
666 uname(&ut);
667 sp = n_string_push_c(sp, '@');
668 sp = n_string_push_cp(sp, ut.sysname);
669 sp = n_string_push_c(sp, ' ');
670 sp = n_string_push_cp(sp, ut.release);
671 sp = n_string_push_c(sp, ' ');
672 sp = n_string_push_cp(sp, ut.version);
673 sp = n_string_push_c(sp, ' ');
674 sp = n_string_push_cp(sp, ut.machine);
675 sp = n_string_push_c(sp, '\n');
678 /* Done */
679 cp = n_string_cp(sp);
681 if(n_pstate & n_PS_ARGMOD_VPUT){
682 if(n_var_vset(*(char const**)vp, (uintptr_t)cp))
683 rv = 0;
684 else
685 rv = -1;
686 }else{
687 if(fputs(cp, n_stdout) != EOF)
688 rv = 0;
689 else{
690 clearerr(n_stdout);
691 rv = 1;
694 NYD_LEAVE;
695 return rv;
698 /* s-it-mode */