NEWS: v14.9.8 ("Marsh tit savours first spring sun")
[s-mailx.git] / cmd-misc.c
blob9584e3e426777582201f4cb3571f0c2d97d09c27
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 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 n_err("%s", cp);
135 else if(fputs(cp, fp) == EOF)
136 e = n_err_no;
137 if((rv = (fflush(fp) == EOF)))
138 e = n_err_no;
139 rv |= ferror(fp) ? 1 : 0;
140 n_pstate_err_no = e;
141 }else if(!n_var_vset(varname, (uintptr_t)cp)){
142 n_pstate_err_no = n_ERR_NOTSUP;
143 rv = -1;
144 }else{
145 n_pstate_err_no = n_ERR_NONE;
146 rv = (int)sp->s_len;
148 NYD2_LEAVE;
149 return rv;
152 static bool_t
153 a_cmisc_read_set(char const *cp, char const *value){
154 bool_t rv;
155 NYD2_ENTER;
157 if(!n_shexp_is_valid_varname(cp))
158 value = N_("not a valid variable name");
159 else if(!n_var_is_user_writable(cp))
160 value = N_("variable is read-only");
161 else if(!n_var_vset(cp, (uintptr_t)value))
162 value = N_("failed to update variable value");
163 else{
164 rv = TRU1;
165 goto jleave;
167 n_err("`read': %s: %s\n", V_(value), n_shexp_quote_cp(cp, FAL0));
168 rv = FAL0;
169 jleave:
170 NYD2_LEAVE;
171 return rv;
174 static int
175 a_cmisc_version_cmp(void const *s1, void const *s2){
176 char const * const *cp1, * const *cp2;
177 int rv;
178 NYD2_ENTER;
180 cp1 = s1;
181 cp2 = s2;
182 rv = strcmp(&(*cp1)[1], &(*cp2)[1]);
183 NYD2_LEAVE;
184 return rv;
187 FL int
188 c_sleep(void *v){
189 uiz_t sec, msec;
190 char **argv;
191 NYD_ENTER;
193 argv = v;
195 if((n_idec_uiz_cp(&sec, argv[0], 0, NULL) &
196 (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
197 ) != n_IDEC_STATE_CONSUMED)
198 goto jesyn;
200 if(argv[1] == NULL)
201 msec = 0;
202 else if((n_idec_uiz_cp(&msec, argv[1], 0, NULL) &
203 (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
204 ) != n_IDEC_STATE_CONSUMED)
205 goto jesyn;
207 if(UIZ_MAX / n_DATE_MILLISSEC < sec)
208 goto jeover;
209 sec *= n_DATE_MILLISSEC;
211 if(UIZ_MAX - sec < msec)
212 goto jeover;
213 msec += sec;
215 n_pstate_err_no = (n_msleep(msec, (argv[2] == NULL)) > 0)
216 ? n_ERR_INTR : n_ERR_NONE;
217 jleave:
218 NYD_LEAVE;
219 return (argv == NULL);
220 jeover:
221 n_err(_("`sleep': argument(s) overflow(s) datatype\n"));
222 n_pstate_err_no = n_ERR_OVERFLOW;
223 argv = NULL;
224 goto jleave;
225 jesyn:
226 n_err(_("Synopsis: sleep: <seconds> [<milliseconds>] [uninterruptible]\n"));
227 n_pstate_err_no = n_ERR_INVAL;
228 argv = NULL;
229 goto jleave;
232 FL int
233 c_shell(void *v)
235 sigset_t mask;
236 int rv;
237 FILE *fp;
238 char const **argv, *varname, *varres, *cp;
239 NYD_ENTER;
241 n_pstate_err_no = n_ERR_NONE;
242 argv = v;
243 varname = (n_pstate & n_PS_ARGMOD_VPUT) ? *argv++ : NULL;
244 varres = n_empty;
245 fp = NULL;
247 if(varname != NULL &&
248 (fp = Ftmp(NULL, "shell", OF_RDWR | OF_UNLINK | OF_REGISTER)
249 ) == NULL){
250 n_pstate_err_no = n_ERR_CANCELED;
251 rv = -1;
252 }else{
253 cp = a_cmisc_bangexp(*argv);
255 sigemptyset(&mask);
256 if(n_child_run(ok_vlook(SHELL), &mask,
257 n_CHILD_FD_PASS, (fp != NULL ? fileno(fp) : n_CHILD_FD_PASS),
258 "-c", cp, NULL, NULL, &rv) < 0){
259 n_pstate_err_no = n_err_no;
260 rv = -1;
261 }else{
262 rv = WEXITSTATUS(rv);
264 if(fp != NULL){
265 int c;
266 char *x;
267 off_t l;
269 fflush_rewind(fp);
270 l = fsize(fp);
271 if(UICMP(64, l, >=, UIZ_MAX -42)){
272 n_pstate_err_no = n_ERR_NOMEM;
273 varres = n_empty;
274 }else{
275 varres = x = n_autorec_alloc(l +1);
277 for(; l > 0 && (c = getc(fp)) != EOF; --l)
278 *x++ = c;
279 *x++ = '\0';
280 if(l != 0){
281 n_pstate_err_no = n_err_no;
282 varres = n_empty; /* xxx hmmm */
289 if(fp != NULL)
290 Fclose(fp);
292 if(varname != NULL){
293 if(!n_var_vset(varname, (uintptr_t)varres)){
294 n_pstate_err_no = n_ERR_NOTSUP;
295 rv = -1;
297 }else if(rv >= 0 && (n_psonce & n_PSO_INTERACTIVE)){
298 fprintf(n_stdout, "!\n");
299 /* Line buffered fflush(n_stdout); */
301 NYD_LEAVE;
302 return rv;
305 FL int
306 c_dosh(void *v)
308 int rv;
309 NYD_ENTER;
310 n_UNUSED(v);
312 if(n_child_run(ok_vlook(SHELL), 0, n_CHILD_FD_PASS, n_CHILD_FD_PASS, NULL,
313 NULL, NULL, NULL, &rv) < 0){
314 n_pstate_err_no = n_err_no;
315 rv = -1;
316 }else{
317 putc('\n', n_stdout);
318 /* Line buffered fflush(n_stdout); */
319 n_pstate_err_no = n_ERR_NONE;
320 rv = WEXITSTATUS(rv);
322 NYD_LEAVE;
323 return rv;
326 FL int
327 c_cwd(void *v){
328 struct n_string s_b, *sp;
329 size_t l;
330 char const *varname;
331 NYD_ENTER;
333 sp = n_string_creat_auto(&s_b);
334 varname = (n_pstate & n_PS_ARGMOD_VPUT) ? *(char const**)v : NULL;
335 l = PATH_MAX;
337 for(;; l += PATH_MAX){
338 sp = n_string_resize(n_string_trunc(sp, 0), l);
340 if(getcwd(sp->s_dat, sp->s_len) == NULL){
341 int e;
343 e = n_err_no;
344 if(e == n_ERR_RANGE)
345 continue;
346 n_perr(_("Failed to getcwd(3)"), e);
347 v = NULL;
348 break;
351 if(varname != NULL){
352 if(!n_var_vset(varname, (uintptr_t)sp->s_dat))
353 v = NULL;
354 }else{
355 l = strlen(sp->s_dat);
356 sp = n_string_trunc(sp, l);
357 if(fwrite(sp->s_dat, 1, sp->s_len, n_stdout) == sp->s_len &&
358 putc('\n', n_stdout) == EOF)
359 v = NULL;
361 break;
363 NYD_LEAVE;
364 return (v == NULL);
367 FL int
368 c_chdir(void *v)
370 char **arglist = v;
371 char const *cp;
372 NYD_ENTER;
374 if (*arglist == NULL)
375 cp = ok_vlook(HOME);
376 else if ((cp = fexpand(*arglist, FEXP_LOCAL | FEXP_NOPROTO)) == NULL)
377 goto jleave;
378 if (chdir(cp) == -1) {
379 n_perr(cp, 0);
380 cp = NULL;
382 jleave:
383 NYD_LEAVE;
384 return (cp == NULL);
387 FL int
388 c_echo(void *v){
389 int rv;
390 NYD_ENTER;
392 rv = a_cmisc_echo(v, n_stdout, TRU1);
393 NYD_LEAVE;
394 return rv;
397 FL int
398 c_echoerr(void *v){
399 int rv;
400 NYD_ENTER;
402 rv = a_cmisc_echo(v, n_stderr, TRU1);
403 NYD_LEAVE;
404 return rv;
407 FL int
408 c_echon(void *v){
409 int rv;
410 NYD_ENTER;
412 rv = a_cmisc_echo(v, n_stdout, FAL0);
413 NYD_LEAVE;
414 return rv;
417 FL int
418 c_echoerrn(void *v){
419 int rv;
420 NYD_ENTER;
422 rv = a_cmisc_echo(v, n_stderr, FAL0);
423 NYD_LEAVE;
424 return rv;
427 FL int
428 c_read(void * volatile vp){
429 struct n_sigman sm;
430 struct str trim;
431 struct n_string s, *sp;
432 char *linebuf;
433 size_t linesize, i;
434 int rv;
435 char const *ifs, **argv, *cp;
436 NYD2_ENTER;
438 sp = n_string_creat_auto(&s);
439 sp = n_string_reserve(sp, 64 -1);
441 ifs = ok_vlook(ifs);
442 linesize = 0;
443 linebuf = NULL;
444 argv = vp;
446 n_SIGMAN_ENTER_SWITCH(&sm, n_SIGMAN_ALL){
447 case 0:
448 break;
449 default:
450 n_pstate_err_no = n_ERR_INTR;
451 rv = -1;
452 goto jleave;
455 n_pstate_err_no = n_ERR_NONE;
456 rv = n_go_input(((n_pstate & n_PS_COMPOSE_MODE
457 ? n_GO_INPUT_CTX_COMPOSE : n_GO_INPUT_CTX_DEFAULT) |
458 n_GO_INPUT_FORCE_STDIN | n_GO_INPUT_NL_ESC |
459 n_GO_INPUT_PROMPT_NONE /* XXX POSIX: PS2: yes! */),
460 NULL, &linebuf, &linesize, NULL, NULL);
461 if(rv < 0){
462 if(!n_go_input_is_eof())
463 n_pstate_err_no = n_ERR_BADF;
464 goto jleave;
465 }else if(rv == 0){
466 if(n_go_input_is_eof()){
467 rv = -1;
468 goto jleave;
470 }else{
471 trim.s = linebuf;
472 trim.l = rv;
474 for(; *argv != NULL; ++argv){
475 if(trim.l == 0 || n_str_trim_ifs(&trim, FAL0)->l == 0)
476 break;
478 /* The last variable gets the remaining line less trailing IFS-WS */
479 if(argv[1] == NULL){
480 jitall:
481 sp = n_string_assign_buf(sp, trim.s, trim.l);
482 trim.l = 0;
483 }else for(cp = trim.s, i = 1;; ++cp, ++i){
484 if(strchr(ifs, *cp) != NULL){
485 sp = n_string_assign_buf(sp, trim.s, i - 1);
486 trim.s += i;
487 trim.l -= i;
488 break;
490 if(i == trim.l)
491 goto jitall;
494 if(!a_cmisc_read_set(*argv, n_string_cp(sp))){
495 n_pstate_err_no = n_ERR_NOTSUP;
496 rv = -1;
497 break;
502 /* Set the remains to the empty string */
503 for(; *argv != NULL; ++argv)
504 if(!a_cmisc_read_set(*argv, n_empty)){
505 n_pstate_err_no = n_ERR_NOTSUP;
506 rv = -1;
507 break;
510 n_sigman_cleanup_ping(&sm);
511 jleave:
512 if(linebuf != NULL)
513 n_free(linebuf);
514 NYD2_LEAVE;
515 n_sigman_leave(&sm, n_SIGMAN_VIPSIGS_NTTYOUT);
516 return rv;
519 FL int
520 c_readall(void * vp){ /* TODO 64-bit retval */
521 struct n_sigman sm;
522 struct n_string s, *sp;
523 char *linebuf;
524 size_t linesize;
525 int rv;
526 char const **argv;
527 NYD2_ENTER;
529 sp = n_string_creat_auto(&s);
530 sp = n_string_reserve(sp, 64 -1);
532 linesize = 0;
533 linebuf = NULL;
534 argv = vp;
536 n_SIGMAN_ENTER_SWITCH(&sm, n_SIGMAN_ALL){
537 case 0:
538 break;
539 default:
540 n_pstate_err_no = n_ERR_INTR;
541 rv = -1;
542 goto jleave;
545 n_pstate_err_no = n_ERR_NONE;
547 for(;;){
548 rv = n_go_input(((n_pstate & n_PS_COMPOSE_MODE
549 ? n_GO_INPUT_CTX_COMPOSE : n_GO_INPUT_CTX_DEFAULT) |
550 n_GO_INPUT_FORCE_STDIN | /*n_GO_INPUT_NL_ESC |*/
551 n_GO_INPUT_PROMPT_NONE),
552 NULL, &linebuf, &linesize, NULL, NULL);
553 if(rv < 0){
554 if(!n_go_input_is_eof()){
555 n_pstate_err_no = n_ERR_BADF;
556 goto jleave;
558 if(sp->s_len == 0)
559 goto jleave;
560 break;
561 }else if(rv == 0){ /* xxx will not get*/
562 if(n_go_input_is_eof()){
563 if(sp->s_len == 0){
564 rv = -1;
565 goto jleave;
567 break;
569 }else if(UICMP(32, SI32_MAX - sp->s_len, <=, rv)){
570 n_pstate_err_no = n_ERR_OVERFLOW;
571 rv = -1;
572 goto jleave;
573 }else{
574 sp = n_string_push_buf(sp, linebuf, rv);
575 if(n_pstate & n_PS_READLINE_NL)
576 sp = n_string_push_c(sp, '\n');
580 if(!a_cmisc_read_set(argv[0], n_string_cp(sp))){
581 n_pstate_err_no = n_ERR_NOTSUP;
582 rv = -1;
583 goto jleave;
585 rv = sp->s_len;
587 n_sigman_cleanup_ping(&sm);
588 jleave:
589 if(linebuf != NULL)
590 n_free(linebuf);
591 NYD2_LEAVE;
592 n_sigman_leave(&sm, n_SIGMAN_VIPSIGS_NTTYOUT);
593 return rv;
596 FL int
597 c_version(void *vp){
598 struct utsname ut;
599 struct n_string s, *sp = &s;
600 int rv;
601 char *iop;
602 char const *cp, **arr;
603 size_t i, lnlen, j;
604 NYD_ENTER;
606 sp = n_string_creat_auto(sp);
607 sp = n_string_book(sp, 1024);
609 /* First line */
610 sp = n_string_push_cp(sp, n_uagent);
611 sp = n_string_push_c(sp, ' ');
612 sp = n_string_push_cp(sp, ok_vlook(version));
613 sp = n_string_push_c(sp, ',');
614 sp = n_string_push_c(sp, ' ');
615 sp = n_string_push_cp(sp, ok_vlook(version_date));
616 sp = n_string_push_c(sp, ' ');
617 sp = n_string_push_c(sp, '(');
618 sp = n_string_push_cp(sp, _("build on "));
619 sp = n_string_push_cp(sp, ok_vlook(build_osenv));
620 sp = n_string_push_c(sp, ')');
621 sp = n_string_push_cp(sp, _("\nFeatures included (+) or not (-)\n"));
623 /* Some lines with the features.
624 * *features* starts with dummy byte to avoid + -> *folder* expansions */
625 i = strlen(cp = &ok_vlook(features)[1]) +1;
626 iop = n_autorec_alloc(i);
627 memcpy(iop, cp, i);
629 arr = n_autorec_alloc(sizeof(cp) * VAL_FEATURES_CNT);
630 for(i = 0; (cp = n_strsep(&iop, ',', TRU1)) != NULL; ++i)
631 arr[i] = cp;
632 qsort(arr, i, sizeof(cp), &a_cmisc_version_cmp);
634 for(lnlen = 0; i-- > 0;){
635 cp = *(arr++);
636 j = strlen(cp);
638 if((lnlen += j + 1) > 72){
639 sp = n_string_push_c(sp, '\n');
640 lnlen = j + 1;
642 sp = n_string_push_c(sp, ' ');
643 sp = n_string_push_buf(sp, cp, j);
645 sp = n_string_push_c(sp, '\n');
647 /* Trailing line with info of running machine */
648 uname(&ut);
649 sp = n_string_push_c(sp, '@');
650 sp = n_string_push_cp(sp, ut.sysname);
651 sp = n_string_push_c(sp, ' ');
652 sp = n_string_push_cp(sp, ut.release);
653 sp = n_string_push_c(sp, ' ');
654 sp = n_string_push_cp(sp, ut.version);
655 sp = n_string_push_c(sp, ' ');
656 sp = n_string_push_cp(sp, ut.machine);
657 sp = n_string_push_c(sp, '\n');
659 /* Done */
660 cp = n_string_cp(sp);
662 if(n_pstate & n_PS_ARGMOD_VPUT){
663 if(n_var_vset(*(char const**)vp, (uintptr_t)cp))
664 rv = 0;
665 else
666 rv = -1;
667 }else{
668 if(fputs(cp, n_stdout) != EOF)
669 rv = 0;
670 else{
671 clearerr(n_stdout);
672 rv = 1;
675 NYD_LEAVE;
676 return rv;
679 /* s-it-mode */