README: review
[s-mailx.git] / cmd-misc.c
blob4bdf91aea6825fc8e786f073324d470bc9b92d74
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 * 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 n_FILE
37 #define n_FILE cmd_misc
39 #ifndef HAVE_AMALGAMATION
40 # include "nail.h"
41 #endif
43 #include <sys/utsname.h>
45 /* Expand the shell escape by expanding unescaped !'s into the last issued
46 * command where possible */
47 static char const *a_cmisc_bangexp(char const *cp);
49 /* c_n?echo(), c_n?echoerr() */
50 static int a_cmisc_echo(void *vp, FILE *fp, bool_t donl);
52 /* c_read() */
53 static bool_t a_cmisc_read_set(char const *cp, char const *value);
55 /* c_version() */
56 static int a_cmisc_version_cmp(void const *s1, void const *s2);
58 static char const *
59 a_cmisc_bangexp(char const *cp){
60 static struct str last_bang;
62 struct n_string xbang, *bang;
63 char c;
64 bool_t changed;
65 NYD_ENTER;
67 if(!ok_blook(bang))
68 goto jleave;
70 changed = FAL0;
72 for(bang = n_string_creat(&xbang); (c = *cp++) != '\0';){
73 if(c == '!'){
74 if(last_bang.l > 0)
75 bang = n_string_push_buf(bang, last_bang.s, last_bang.l);
76 changed = TRU1;
77 }else{
78 if(c == '\\' && *cp == '!'){
79 ++cp;
80 c = '!';
81 changed = TRU1;
83 bang = n_string_push_c(bang, c);
87 if(last_bang.s != NULL)
88 n_free(last_bang.s);
89 last_bang.s = n_string_cp(bang);
90 last_bang.l = bang->s_len;
91 bang = n_string_drop_ownership(bang);
92 n_string_gut(bang);
94 cp = last_bang.s;
95 if(changed)
96 fprintf(n_stdout, "!%s\n", cp);
97 jleave:
98 NYD_LEAVE;
99 return cp;
102 static int
103 a_cmisc_echo(void *vp, FILE *fp, bool_t donl){
104 struct n_string s, *sp;
105 int rv;
106 bool_t doerr;
107 char const **argv, *varname, **ap, *cp;
108 NYD2_ENTER;
110 argv = vp;
111 varname = (n_pstate & n_PS_ARGMOD_VPUT) ? *argv++ : NULL;
112 sp = n_string_reserve(n_string_creat_auto(&s), 121/* XXX */);
113 #ifdef HAVE_ERRORS
114 doerr = (fp == n_stderr && (n_psonce & n_PSO_INTERACTIVE));
115 #else
116 doerr = FAL0;
117 #endif
119 for(ap = argv; *ap != NULL; ++ap){
120 if(ap != argv)
121 sp = n_string_push_c(sp, ' ');
122 if((cp = fexpand(*ap, FEXP_NSHORTCUT | FEXP_NVAR)) == NULL)
123 cp = *ap;
124 sp = n_string_push_cp(sp, cp);
126 if(donl)
127 sp = n_string_push_c(sp, '\n');
128 cp = n_string_cp(sp);
130 if(varname == NULL){
131 si32_t e;
133 e = n_ERR_NONE;
134 if(doerr){
135 /* xxx Ensure *log-prefix* will be placed by n_err() for next msg */
136 if(donl)
137 cp = n_string_cp(n_string_trunc(sp, sp->s_len - 1));
138 n_err((donl ? "%s\n" : "%s"), cp);
139 }else if(fputs(cp, fp) == EOF)
140 e = n_err_no;
141 if((rv = (fflush(fp) == EOF)))
142 e = n_err_no;
143 rv |= ferror(fp) ? 1 : 0;
144 n_pstate_err_no = e;
145 }else if(!n_var_vset(varname, (uintptr_t)cp)){
146 n_pstate_err_no = n_ERR_NOTSUP;
147 rv = -1;
148 }else{
149 n_pstate_err_no = n_ERR_NONE;
150 rv = (int)sp->s_len;
152 NYD2_LEAVE;
153 return rv;
156 static bool_t
157 a_cmisc_read_set(char const *cp, char const *value){
158 bool_t rv;
159 NYD2_ENTER;
161 if(!n_shexp_is_valid_varname(cp))
162 value = N_("not a valid variable name");
163 else if(!n_var_is_user_writable(cp))
164 value = N_("variable is read-only");
165 else if(!n_var_vset(cp, (uintptr_t)value))
166 value = N_("failed to update variable value");
167 else{
168 rv = TRU1;
169 goto jleave;
171 n_err("`read': %s: %s\n", V_(value), n_shexp_quote_cp(cp, FAL0));
172 rv = FAL0;
173 jleave:
174 NYD2_LEAVE;
175 return rv;
178 static int
179 a_cmisc_version_cmp(void const *s1, void const *s2){
180 char const * const *cp1, * const *cp2;
181 int rv;
182 NYD2_ENTER;
184 cp1 = s1;
185 cp2 = s2;
186 rv = strcmp(&(*cp1)[1], &(*cp2)[1]);
187 NYD2_LEAVE;
188 return rv;
191 FL int
192 c_sleep(void *v){
193 uiz_t sec, msec;
194 char **argv;
195 NYD_ENTER;
197 argv = v;
199 if((n_idec_uiz_cp(&sec, argv[0], 0, NULL) &
200 (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
201 ) != n_IDEC_STATE_CONSUMED)
202 goto jesyn;
204 if(argv[1] == NULL)
205 msec = 0;
206 else if((n_idec_uiz_cp(&msec, argv[1], 0, NULL) &
207 (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
208 ) != n_IDEC_STATE_CONSUMED)
209 goto jesyn;
211 if(UIZ_MAX / n_DATE_MILLISSEC < sec)
212 goto jeover;
213 sec *= n_DATE_MILLISSEC;
215 if(UIZ_MAX - sec < msec)
216 goto jeover;
217 msec += sec;
219 n_pstate_err_no = (n_msleep(msec, (argv[2] == NULL)) > 0)
220 ? n_ERR_INTR : n_ERR_NONE;
221 jleave:
222 NYD_LEAVE;
223 return (argv == NULL);
224 jeover:
225 n_err(_("`sleep': argument(s) overflow(s) datatype\n"));
226 n_pstate_err_no = n_ERR_OVERFLOW;
227 argv = NULL;
228 goto jleave;
229 jesyn:
230 n_err(_("Synopsis: sleep: <seconds> [<milliseconds>] [uninterruptible]\n"));
231 n_pstate_err_no = n_ERR_INVAL;
232 argv = NULL;
233 goto jleave;
236 FL int
237 c_shell(void *v)
239 sigset_t mask;
240 int rv;
241 FILE *fp;
242 char const **argv, *varname, *varres, *cp;
243 NYD_ENTER;
245 n_pstate_err_no = n_ERR_NONE;
246 argv = v;
247 varname = (n_pstate & n_PS_ARGMOD_VPUT) ? *argv++ : NULL;
248 varres = n_empty;
249 fp = NULL;
251 if(varname != NULL &&
252 (fp = Ftmp(NULL, "shell", OF_RDWR | OF_UNLINK | OF_REGISTER)
253 ) == NULL){
254 n_pstate_err_no = n_ERR_CANCELED;
255 rv = -1;
256 }else{
257 cp = a_cmisc_bangexp(*argv);
259 sigemptyset(&mask);
260 if(n_child_run(ok_vlook(SHELL), &mask,
261 n_CHILD_FD_PASS, (fp != NULL ? fileno(fp) : n_CHILD_FD_PASS),
262 "-c", cp, NULL, NULL, &rv) < 0){
263 n_pstate_err_no = n_err_no;
264 rv = -1;
265 }else{
266 rv = WEXITSTATUS(rv);
268 if(fp != NULL){
269 int c;
270 char *x;
271 off_t l;
273 fflush_rewind(fp);
274 l = fsize(fp);
275 if(UICMP(64, l, >=, UIZ_MAX -42)){
276 n_pstate_err_no = n_ERR_NOMEM;
277 varres = n_empty;
278 }else{
279 varres = x = n_autorec_alloc(l +1);
281 for(; l > 0 && (c = getc(fp)) != EOF; --l)
282 *x++ = c;
283 *x++ = '\0';
284 if(l != 0){
285 n_pstate_err_no = n_err_no;
286 varres = n_empty; /* xxx hmmm */
293 if(fp != NULL)
294 Fclose(fp);
296 if(varname != NULL){
297 if(!n_var_vset(varname, (uintptr_t)varres)){
298 n_pstate_err_no = n_ERR_NOTSUP;
299 rv = -1;
301 }else if(rv >= 0 && (n_psonce & n_PSO_INTERACTIVE)){
302 fprintf(n_stdout, "!\n");
303 /* Line buffered fflush(n_stdout); */
305 NYD_LEAVE;
306 return rv;
309 FL int
310 c_dosh(void *v)
312 int rv;
313 NYD_ENTER;
314 n_UNUSED(v);
316 if(n_child_run(ok_vlook(SHELL), 0, n_CHILD_FD_PASS, n_CHILD_FD_PASS, NULL,
317 NULL, NULL, NULL, &rv) < 0){
318 n_pstate_err_no = n_err_no;
319 rv = -1;
320 }else{
321 putc('\n', n_stdout);
322 /* Line buffered fflush(n_stdout); */
323 n_pstate_err_no = n_ERR_NONE;
324 rv = WEXITSTATUS(rv);
326 NYD_LEAVE;
327 return rv;
330 FL int
331 c_cwd(void *v){
332 struct n_string s_b, *sp;
333 size_t l;
334 char const *varname;
335 NYD_ENTER;
337 sp = n_string_creat_auto(&s_b);
338 varname = (n_pstate & n_PS_ARGMOD_VPUT) ? *(char const**)v : NULL;
339 l = PATH_MAX;
341 for(;; l += PATH_MAX){
342 sp = n_string_resize(n_string_trunc(sp, 0), l);
344 if(getcwd(sp->s_dat, sp->s_len) == NULL){
345 int e;
347 e = n_err_no;
348 if(e == n_ERR_RANGE)
349 continue;
350 n_perr(_("Failed to getcwd(3)"), e);
351 v = NULL;
352 break;
355 if(varname != NULL){
356 if(!n_var_vset(varname, (uintptr_t)sp->s_dat))
357 v = NULL;
358 }else{
359 l = strlen(sp->s_dat);
360 sp = n_string_trunc(sp, l);
361 if(fwrite(sp->s_dat, 1, sp->s_len, n_stdout) == sp->s_len &&
362 putc('\n', n_stdout) == EOF)
363 v = NULL;
365 break;
367 NYD_LEAVE;
368 return (v == NULL);
371 FL int
372 c_chdir(void *v)
374 char **arglist = v;
375 char const *cp;
376 NYD_ENTER;
378 if (*arglist == NULL)
379 cp = ok_vlook(HOME);
380 else if ((cp = fexpand(*arglist, FEXP_LOCAL | FEXP_NOPROTO)) == NULL)
381 goto jleave;
382 if (chdir(cp) == -1) {
383 n_perr(cp, 0);
384 cp = NULL;
386 jleave:
387 NYD_LEAVE;
388 return (cp == NULL);
391 FL int
392 c_echo(void *v){
393 int rv;
394 NYD_ENTER;
396 rv = a_cmisc_echo(v, n_stdout, TRU1);
397 NYD_LEAVE;
398 return rv;
401 FL int
402 c_echoerr(void *v){
403 int rv;
404 NYD_ENTER;
406 rv = a_cmisc_echo(v, n_stderr, TRU1);
407 NYD_LEAVE;
408 return rv;
411 FL int
412 c_echon(void *v){
413 int rv;
414 NYD_ENTER;
416 rv = a_cmisc_echo(v, n_stdout, FAL0);
417 NYD_LEAVE;
418 return rv;
421 FL int
422 c_echoerrn(void *v){
423 int rv;
424 NYD_ENTER;
426 rv = a_cmisc_echo(v, n_stderr, FAL0);
427 NYD_LEAVE;
428 return rv;
431 FL int
432 c_read(void * volatile vp){
433 struct n_sigman sm;
434 struct str trim;
435 struct n_string s, *sp;
436 char *linebuf;
437 size_t linesize, i;
438 int rv;
439 char const *ifs, **argv, *cp;
440 NYD2_ENTER;
442 sp = n_string_creat_auto(&s);
443 sp = n_string_reserve(sp, 64 -1);
445 ifs = ok_vlook(ifs);
446 linesize = 0;
447 linebuf = NULL;
448 argv = vp;
450 n_SIGMAN_ENTER_SWITCH(&sm, n_SIGMAN_ALL){
451 case 0:
452 break;
453 default:
454 n_pstate_err_no = n_ERR_INTR;
455 rv = -1;
456 goto jleave;
459 n_pstate_err_no = n_ERR_NONE;
460 rv = n_go_input(((n_pstate & n_PS_COMPOSE_MODE
461 ? n_GO_INPUT_CTX_COMPOSE : n_GO_INPUT_CTX_DEFAULT) |
462 n_GO_INPUT_FORCE_STDIN | n_GO_INPUT_NL_ESC |
463 n_GO_INPUT_PROMPT_NONE /* XXX POSIX: PS2: yes! */),
464 NULL, &linebuf, &linesize, NULL, NULL);
465 if(rv < 0){
466 if(!n_go_input_is_eof())
467 n_pstate_err_no = n_ERR_BADF;
468 goto jleave;
469 }else if(rv == 0){
470 if(n_go_input_is_eof()){
471 rv = -1;
472 goto jleave;
474 }else{
475 trim.s = linebuf;
476 trim.l = rv;
478 for(; *argv != NULL; ++argv){
479 if(trim.l == 0 || n_str_trim_ifs(&trim, FAL0)->l == 0)
480 break;
482 /* The last variable gets the remaining line less trailing IFS-WS */
483 if(argv[1] == NULL){
484 jitall:
485 sp = n_string_assign_buf(sp, trim.s, trim.l);
486 trim.l = 0;
487 }else for(cp = trim.s, i = 1;; ++cp, ++i){
488 if(strchr(ifs, *cp) != NULL){
489 sp = n_string_assign_buf(sp, trim.s, i - 1);
490 trim.s += i;
491 trim.l -= i;
492 break;
494 if(i == trim.l)
495 goto jitall;
498 if(!a_cmisc_read_set(*argv, n_string_cp(sp))){
499 n_pstate_err_no = n_ERR_NOTSUP;
500 rv = -1;
501 break;
506 /* Set the remains to the empty string */
507 for(; *argv != NULL; ++argv)
508 if(!a_cmisc_read_set(*argv, n_empty)){
509 n_pstate_err_no = n_ERR_NOTSUP;
510 rv = -1;
511 break;
514 n_sigman_cleanup_ping(&sm);
515 jleave:
516 if(linebuf != NULL)
517 n_free(linebuf);
518 NYD2_LEAVE;
519 n_sigman_leave(&sm, n_SIGMAN_VIPSIGS_NTTYOUT);
520 return rv;
523 FL int
524 c_readall(void * vp){ /* TODO 64-bit retval */
525 struct n_sigman sm;
526 struct n_string s, *sp;
527 char *linebuf;
528 size_t linesize;
529 int rv;
530 char const **argv;
531 NYD2_ENTER;
533 sp = n_string_creat_auto(&s);
534 sp = n_string_reserve(sp, 64 -1);
536 linesize = 0;
537 linebuf = NULL;
538 argv = vp;
540 n_SIGMAN_ENTER_SWITCH(&sm, n_SIGMAN_ALL){
541 case 0:
542 break;
543 default:
544 n_pstate_err_no = n_ERR_INTR;
545 rv = -1;
546 goto jleave;
549 n_pstate_err_no = n_ERR_NONE;
551 for(;;){
552 rv = n_go_input(((n_pstate & n_PS_COMPOSE_MODE
553 ? n_GO_INPUT_CTX_COMPOSE : n_GO_INPUT_CTX_DEFAULT) |
554 n_GO_INPUT_FORCE_STDIN | /*n_GO_INPUT_NL_ESC |*/
555 n_GO_INPUT_PROMPT_NONE),
556 NULL, &linebuf, &linesize, NULL, NULL);
557 if(rv < 0){
558 if(!n_go_input_is_eof()){
559 n_pstate_err_no = n_ERR_BADF;
560 goto jleave;
562 if(sp->s_len == 0)
563 goto jleave;
564 break;
567 if(n_pstate & n_PS_READLINE_NL)
568 linebuf[rv++] = '\n'; /* Replace NUL with it */
570 if(n_UNLIKELY(rv == 0)){ /* xxx will not get*/
571 if(n_go_input_is_eof()){
572 if(sp->s_len == 0){
573 rv = -1;
574 goto jleave;
576 break;
578 }else if(n_LIKELY(UICMP(32, SI32_MAX - sp->s_len, >, rv)))
579 sp = n_string_push_buf(sp, linebuf, rv);
580 else{
581 n_pstate_err_no = n_ERR_OVERFLOW;
582 rv = -1;
583 goto jleave;
587 if(!a_cmisc_read_set(argv[0], n_string_cp(sp))){
588 n_pstate_err_no = n_ERR_NOTSUP;
589 rv = -1;
590 goto jleave;
592 rv = sp->s_len;
594 n_sigman_cleanup_ping(&sm);
595 jleave:
596 if(linebuf != NULL)
597 n_free(linebuf);
598 NYD2_LEAVE;
599 n_sigman_leave(&sm, n_SIGMAN_VIPSIGS_NTTYOUT);
600 return rv;
603 FL int
604 c_version(void *vp){
605 struct utsname ut;
606 struct n_string s, *sp = &s;
607 int rv;
608 char *iop;
609 char const *cp, **arr;
610 size_t i, lnlen, j;
611 NYD_ENTER;
613 sp = n_string_creat_auto(sp);
614 sp = n_string_book(sp, 1024);
616 /* First two lines */
617 sp = n_string_push_cp(sp, n_uagent);
618 sp = n_string_push_c(sp, ' ');
619 sp = n_string_push_cp(sp, ok_vlook(version));
620 sp = n_string_push_c(sp, ',');
621 sp = n_string_push_c(sp, ' ');
622 sp = n_string_push_cp(sp, ok_vlook(version_date));
623 sp = n_string_push_c(sp, ' ');
624 sp = n_string_push_c(sp, '(');
625 sp = n_string_push_cp(sp, _("build for "));
626 sp = n_string_push_cp(sp, ok_vlook(build_os));
627 sp = n_string_push_c(sp, ')');
628 sp = n_string_push_cp(sp, _("\nFeatures included (+) or not (-):\n"));
630 /* Some lines with the features.
631 * *features* starts with dummy byte to avoid + -> *folder* expansions */
632 i = strlen(cp = &ok_vlook(features)[1]) +1;
633 iop = n_autorec_alloc(i);
634 memcpy(iop, cp, i);
636 arr = n_autorec_alloc(sizeof(cp) * VAL_FEATURES_CNT);
637 for(i = 0; (cp = n_strsep(&iop, ',', TRU1)) != NULL; ++i)
638 arr[i] = cp;
639 qsort(arr, i, sizeof(cp), &a_cmisc_version_cmp);
641 for(lnlen = 0; i-- > 0;){
642 cp = *(arr++);
643 j = strlen(cp);
645 if((lnlen += j + 1) > 72){
646 sp = n_string_push_c(sp, '\n');
647 lnlen = j + 1;
649 sp = n_string_push_c(sp, ' ');
650 sp = n_string_push_buf(sp, cp, j);
652 sp = n_string_push_c(sp, '\n');
654 /* */
655 if(n_poption & n_PO_VERB){
656 sp = n_string_push_cp(sp, "Compile: ");
657 sp = n_string_push_cp(sp, ok_vlook(build_cc));
658 sp = n_string_push_cp(sp, "\nLink: ");
659 sp = n_string_push_cp(sp, ok_vlook(build_ld));
660 if(*(cp = ok_vlook(build_rest)) != '\0'){
661 sp = n_string_push_cp(sp, "\nRest: ");
662 sp = n_string_push_cp(sp, cp);
664 sp = n_string_push_c(sp, '\n');
666 /* A trailing line with info of the running machine */
667 uname(&ut);
668 sp = n_string_push_c(sp, '@');
669 sp = n_string_push_cp(sp, ut.sysname);
670 sp = n_string_push_c(sp, ' ');
671 sp = n_string_push_cp(sp, ut.release);
672 sp = n_string_push_c(sp, ' ');
673 sp = n_string_push_cp(sp, ut.version);
674 sp = n_string_push_c(sp, ' ');
675 sp = n_string_push_cp(sp, ut.machine);
676 sp = n_string_push_c(sp, '\n');
679 /* Done */
680 cp = n_string_cp(sp);
682 if(n_pstate & n_PS_ARGMOD_VPUT){
683 if(n_var_vset(*(char const**)vp, (uintptr_t)cp))
684 rv = 0;
685 else
686 rv = -1;
687 }else{
688 if(fputs(cp, n_stdout) != EOF)
689 rv = 0;
690 else{
691 clearerr(n_stdout);
692 rv = 1;
695 NYD_LEAVE;
696 return rv;
699 /* s-it-mode */