nail.1: variable chains: base name namespace is "reserved"
[s-mailx.git] / cmd-misc.c
blobf23062e6c49947c629e8d3fc76afa349498c2c22
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 - 2017 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 /* Expand the shell escape by expanding unescaped !'s into the last issued
43 * command where possible */
44 static char const *a_cmisc_bangexp(char const *cp);
46 /* c_n?echo(), c_n?echoerr() */
47 static int a_cmisc_echo(void *vp, FILE *fp, bool_t donl);
49 /* c_read() */
50 static bool_t a_cmisc_read_set(char const *cp, char const *value);
52 /* c_version() */
53 static int a_cmisc_version_cmp(void const *s1, void const *s2);
55 static char const *
56 a_cmisc_bangexp(char const *cp){
57 static struct str last_bang;
59 struct n_string xbang, *bang;
60 char c;
61 bool_t changed;
62 NYD_ENTER;
64 if(!ok_blook(bang))
65 goto jleave;
67 changed = FAL0;
69 for(bang = n_string_creat(&xbang); (c = *cp++) != '\0';){
70 if(c == '!'){
71 if(last_bang.l > 0)
72 bang = n_string_push_buf(bang, last_bang.s, last_bang.l);
73 changed = TRU1;
74 }else{
75 if(c == '\\' && *cp == '!'){
76 ++cp;
77 c = '!';
78 changed = TRU1;
80 bang = n_string_push_c(bang, c);
84 if(last_bang.s != NULL)
85 free(last_bang.s);
86 last_bang.s = n_string_cp(bang);
87 last_bang.l = bang->s_len;
88 bang = n_string_drop_ownership(bang);
89 n_string_gut(bang);
91 cp = last_bang.s;
92 if(changed)
93 fprintf(n_stdout, "!%s\n", cp);
94 jleave:
95 NYD_LEAVE;
96 return cp;
99 static int
100 a_cmisc_echo(void *vp, FILE *fp, bool_t donl){
101 struct n_string s, *sp;
102 int rv;
103 bool_t doerr;
104 char const **argv, *varname, **ap, *cp;
105 NYD2_ENTER;
107 argv = vp;
108 varname = (n_pstate & n_PS_ARGMOD_VPUT) ? *argv++ : NULL;
109 sp = n_string_reserve(n_string_creat_auto(&s), 121/* XXX */);
110 #ifdef HAVE_ERRORS
111 doerr = (fp == n_stderr && (n_psonce & n_PSO_INTERACTIVE));
112 #else
113 doerr = FAL0;
114 #endif
116 for(ap = argv; *ap != NULL; ++ap){
117 if(ap != argv)
118 sp = n_string_push_c(sp, ' ');
119 if((cp = fexpand(*ap, FEXP_NSHORTCUT | FEXP_NVAR)) == NULL)
120 cp = *ap;
121 sp = n_string_push_cp(sp, cp);
123 if(donl)
124 sp = n_string_push_c(sp, '\n');
125 cp = n_string_cp(sp);
127 if(varname == NULL){
128 si32_t e;
130 e = n_ERR_NONE;
131 if(doerr)
132 n_err("%s", cp);
133 else if(fputs(cp, fp) == EOF)
134 e = n_err_no;
135 if((rv = (fflush(fp) == EOF)))
136 e = n_err_no;
137 rv |= ferror(fp) ? 1 : 0;
138 n_pstate_err_no = e;
139 }else if(!n_var_vset(varname, (uintptr_t)cp)){
140 n_pstate_err_no = n_ERR_NOTSUP;
141 rv = -1;
142 }else{
143 n_pstate_err_no = n_ERR_NONE;
144 rv = (int)sp->s_len;
146 NYD2_LEAVE;
147 return rv;
150 static bool_t
151 a_cmisc_read_set(char const *cp, char const *value){
152 bool_t rv;
153 NYD2_ENTER;
155 if(!n_shexp_is_valid_varname(cp))
156 value = N_("not a valid variable name");
157 else if(!n_var_is_user_writable(cp))
158 value = N_("variable is read-only");
159 else if(!n_var_vset(cp, (uintptr_t)value))
160 value = N_("failed to update variable value");
161 else{
162 rv = TRU1;
163 goto jleave;
165 n_err("`read': %s: %s\n", V_(value), n_shexp_quote_cp(cp, FAL0));
166 rv = FAL0;
167 jleave:
168 NYD2_LEAVE;
169 return rv;
172 static int
173 a_cmisc_version_cmp(void const *s1, void const *s2){
174 char const * const *cp1, * const *cp2;
175 int rv;
176 NYD2_ENTER;
178 cp1 = s1;
179 cp2 = s2;
180 rv = strcmp(&(*cp1)[1], &(*cp2)[1]);
181 NYD2_LEAVE;
182 return rv;
185 FL int
186 c_sleep(void *v){
187 uiz_t sec, msec;
188 char **argv;
189 NYD_ENTER;
191 argv = v;
193 if((n_idec_uiz_cp(&sec, argv[0], 0, NULL) &
194 (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
195 ) != n_IDEC_STATE_CONSUMED)
196 goto jesyn;
198 if(argv[1] == NULL)
199 msec = 0;
200 else if((n_idec_uiz_cp(&msec, argv[1], 0, NULL) &
201 (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
202 ) != n_IDEC_STATE_CONSUMED)
203 goto jesyn;
205 if(UIZ_MAX / n_DATE_MILLISSEC < sec)
206 goto jeover;
207 sec *= n_DATE_MILLISSEC;
209 if(UIZ_MAX - sec < msec)
210 goto jeover;
211 msec += sec;
213 n_pstate_err_no = (n_msleep(msec, (argv[2] == NULL)) > 0)
214 ? n_ERR_INTR : n_ERR_NONE;
215 jleave:
216 NYD_LEAVE;
217 return (argv == NULL);
218 jeover:
219 n_err(_("`sleep': argument(s) overflow(s) datatype\n"));
220 n_pstate_err_no = n_ERR_OVERFLOW;
221 argv = NULL;
222 goto jleave;
223 jesyn:
224 n_err(_("Synopsis: sleep: <seconds> [<milliseconds>] [uninterruptible]\n"));
225 n_pstate_err_no = n_ERR_INVAL;
226 argv = NULL;
227 goto jleave;
230 FL int
231 c_shell(void *v)
233 sigset_t mask;
234 int rv;
235 FILE *fp;
236 char const **argv, *varname, *varres, *cp;
237 NYD_ENTER;
239 n_pstate_err_no = n_ERR_NONE;
240 argv = v;
241 varname = (n_pstate & n_PS_ARGMOD_VPUT) ? *argv++ : NULL;
242 varres = n_empty;
243 fp = NULL;
245 if(varname != NULL &&
246 (fp = Ftmp(NULL, "shell", OF_RDWR | OF_UNLINK | OF_REGISTER)
247 ) == NULL){
248 n_pstate_err_no = n_ERR_CANCELED;
249 rv = -1;
250 }else{
251 cp = a_cmisc_bangexp(*argv);
253 sigemptyset(&mask);
254 if(n_child_run(ok_vlook(SHELL), &mask,
255 n_CHILD_FD_PASS, (fp != NULL ? fileno(fp) : n_CHILD_FD_PASS),
256 "-c", cp, NULL, NULL, &rv) < 0){
257 n_pstate_err_no = n_err_no;
258 rv = -1;
259 }else{
260 rv = WEXITSTATUS(rv);
262 if(fp != NULL){
263 int c;
264 char *x;
265 off_t l;
267 fflush_rewind(fp);
268 l = fsize(fp);
269 if(UICMP(64, l, >=, UIZ_MAX -42)){
270 n_pstate_err_no = n_ERR_NOMEM;
271 varres = n_empty;
272 }else{
273 varres = x = n_autorec_alloc(l +1);
275 for(; l > 0 && (c = getc(fp)) != EOF; --l)
276 *x++ = c;
277 *x++ = '\0';
278 if(l != 0){
279 n_pstate_err_no = n_err_no;
280 varres = n_empty; /* xxx hmmm */
287 if(fp != NULL)
288 Fclose(fp);
290 if(varname != NULL){
291 if(!n_var_vset(varname, (uintptr_t)varres)){
292 n_pstate_err_no = n_ERR_NOTSUP;
293 rv = -1;
295 }else if(rv >= 0 && (n_psonce & n_PSO_INTERACTIVE)){
296 fprintf(n_stdout, "!\n");
297 /* Line buffered fflush(n_stdout); */
299 NYD_LEAVE;
300 return rv;
303 FL int
304 c_dosh(void *v)
306 int rv;
307 NYD_ENTER;
308 n_UNUSED(v);
310 if(n_child_run(ok_vlook(SHELL), 0, n_CHILD_FD_PASS, n_CHILD_FD_PASS, NULL,
311 NULL, NULL, NULL, &rv) < 0){
312 n_pstate_err_no = n_err_no;
313 rv = -1;
314 }else{
315 putc('\n', n_stdout);
316 /* Line buffered fflush(n_stdout); */
317 n_pstate_err_no = n_ERR_NONE;
318 rv = WEXITSTATUS(rv);
320 NYD_LEAVE;
321 return rv;
324 FL int
325 c_cwd(void *v){
326 struct n_string s_b, *sp;
327 size_t l;
328 char const *varname;
329 NYD_ENTER;
331 sp = n_string_creat_auto(&s_b);
332 varname = (n_pstate & n_PS_ARGMOD_VPUT) ? *(char const**)v : NULL;
333 l = PATH_MAX;
335 for(;; l += PATH_MAX){
336 sp = n_string_resize(n_string_trunc(sp, 0), l);
338 if(getcwd(sp->s_dat, sp->s_len) == NULL){
339 int e;
341 e = n_err_no;
342 if(e == n_ERR_RANGE)
343 continue;
344 n_perr(_("Failed to getcwd(3)"), e);
345 v = NULL;
346 break;
349 if(varname != NULL){
350 if(!n_var_vset(varname, (uintptr_t)sp->s_dat))
351 v = NULL;
352 }else{
353 l = strlen(sp->s_dat);
354 sp = n_string_trunc(sp, l);
355 if(fwrite(sp->s_dat, 1, sp->s_len, n_stdout) == sp->s_len &&
356 putc('\n', n_stdout) == EOF)
357 v = NULL;
359 break;
361 NYD_LEAVE;
362 return (v == NULL);
365 FL int
366 c_chdir(void *v)
368 char **arglist = v;
369 char const *cp;
370 NYD_ENTER;
372 if (*arglist == NULL)
373 cp = ok_vlook(HOME);
374 else if ((cp = fexpand(*arglist, FEXP_LOCAL | FEXP_NOPROTO)) == NULL)
375 goto jleave;
376 if (chdir(cp) == -1) {
377 n_perr(cp, 0);
378 cp = NULL;
380 jleave:
381 NYD_LEAVE;
382 return (cp == NULL);
385 FL int
386 c_echo(void *v){
387 int rv;
388 NYD_ENTER;
390 rv = a_cmisc_echo(v, n_stdout, TRU1);
391 NYD_LEAVE;
392 return rv;
395 FL int
396 c_echoerr(void *v){
397 int rv;
398 NYD_ENTER;
400 rv = a_cmisc_echo(v, n_stderr, TRU1);
401 NYD_LEAVE;
402 return rv;
405 FL int
406 c_echon(void *v){
407 int rv;
408 NYD_ENTER;
410 rv = a_cmisc_echo(v, n_stdout, FAL0);
411 NYD_LEAVE;
412 return rv;
415 FL int
416 c_echoerrn(void *v){
417 int rv;
418 NYD_ENTER;
420 rv = a_cmisc_echo(v, n_stderr, FAL0);
421 NYD_LEAVE;
422 return rv;
425 FL int
426 c_read(void * volatile vp){
427 struct n_sigman sm;
428 struct str trim;
429 struct n_string s, *sp;
430 char *linebuf;
431 size_t linesize, i;
432 int rv;
433 char const *ifs, **argv, *cp;
434 NYD2_ENTER;
436 sp = n_string_creat_auto(&s);
437 sp = n_string_reserve(sp, 64 -1);
439 ifs = ok_vlook(ifs);
440 linesize = 0;
441 linebuf = NULL;
442 argv = vp;
444 n_SIGMAN_ENTER_SWITCH(&sm, n_SIGMAN_ALL){
445 case 0:
446 break;
447 default:
448 n_pstate_err_no = n_ERR_INTR;
449 rv = -1;
450 goto jleave;
453 n_pstate_err_no = n_ERR_NONE;
454 rv = n_go_input(((n_pstate & n_PS_COMPOSE_MODE
455 ? n_GO_INPUT_CTX_COMPOSE : n_GO_INPUT_CTX_DEFAULT) |
456 n_GO_INPUT_FORCE_STDIN | n_GO_INPUT_NL_ESC |
457 n_GO_INPUT_PROMPT_NONE /* XXX POSIX: PS2: yes! */),
458 NULL, &linebuf, &linesize, NULL, NULL);
459 if(rv < 0){
460 if(!n_go_input_is_eof())
461 n_pstate_err_no = n_ERR_BADF;
462 goto jleave;
463 }else if(rv == 0){
464 if(n_go_input_is_eof()){
465 rv = -1;
466 goto jleave;
468 }else{
469 trim.s = linebuf;
470 trim.l = rv;
472 for(; *argv != NULL; ++argv){
473 if(trim.l == 0 || n_str_trim_ifs(&trim, FAL0)->l == 0)
474 break;
476 /* The last variable gets the remaining line less trailing IFS-WS */
477 if(argv[1] == NULL){
478 jitall:
479 sp = n_string_assign_buf(sp, trim.s, trim.l);
480 trim.l = 0;
481 }else for(cp = trim.s, i = 1;; ++cp, ++i){
482 if(strchr(ifs, *cp) != NULL){
483 sp = n_string_assign_buf(sp, trim.s, i - 1);
484 trim.s += i;
485 trim.l -= i;
486 break;
488 if(i == trim.l)
489 goto jitall;
492 if(!a_cmisc_read_set(*argv, n_string_cp(sp))){
493 n_pstate_err_no = n_ERR_NOTSUP;
494 rv = -1;
495 break;
500 /* Set the remains to the empty string */
501 for(; *argv != NULL; ++argv)
502 if(!a_cmisc_read_set(*argv, n_empty)){
503 n_pstate_err_no = n_ERR_NOTSUP;
504 rv = -1;
505 break;
508 n_sigman_cleanup_ping(&sm);
509 jleave:
510 if(linebuf != NULL)
511 n_free(linebuf);
512 NYD2_LEAVE;
513 n_sigman_leave(&sm, n_SIGMAN_VIPSIGS_NTTYOUT);
514 return rv;
517 FL int
518 c_readall(void * vp){ /* TODO 64-bit retval */
519 struct n_sigman sm;
520 struct n_string s, *sp;
521 char *linebuf;
522 size_t linesize;
523 int rv;
524 char const **argv;
525 NYD2_ENTER;
527 sp = n_string_creat_auto(&s);
528 sp = n_string_reserve(sp, 64 -1);
530 linesize = 0;
531 linebuf = NULL;
532 argv = vp;
534 n_SIGMAN_ENTER_SWITCH(&sm, n_SIGMAN_ALL){
535 case 0:
536 break;
537 default:
538 n_pstate_err_no = n_ERR_INTR;
539 rv = -1;
540 goto jleave;
543 n_pstate_err_no = n_ERR_NONE;
545 for(;;){
546 rv = n_go_input(((n_pstate & n_PS_COMPOSE_MODE
547 ? n_GO_INPUT_CTX_COMPOSE : n_GO_INPUT_CTX_DEFAULT) |
548 n_GO_INPUT_FORCE_STDIN | /*n_GO_INPUT_NL_ESC |*/
549 n_GO_INPUT_PROMPT_NONE),
550 NULL, &linebuf, &linesize, NULL, NULL);
551 if(rv < 0){
552 if(!n_go_input_is_eof()){
553 n_pstate_err_no = n_ERR_BADF;
554 goto jleave;
556 if(sp->s_len == 0)
557 goto jleave;
558 break;
559 }else if(rv == 0){ /* xxx will not get*/
560 if(n_go_input_is_eof()){
561 if(sp->s_len == 0){
562 rv = -1;
563 goto jleave;
565 break;
567 }else if(UICMP(32, SI32_MAX - sp->s_len, <=, rv)){
568 n_pstate_err_no = n_ERR_OVERFLOW;
569 rv = -1;
570 goto jleave;
571 }else{
572 sp = n_string_push_buf(sp, linebuf, rv);
573 if(n_pstate & n_PS_READLINE_NL)
574 sp = n_string_push_c(sp, '\n');
578 if(!a_cmisc_read_set(argv[0], n_string_cp(sp))){
579 n_pstate_err_no = n_ERR_NOTSUP;
580 rv = -1;
581 goto jleave;
583 rv = sp->s_len;
585 n_sigman_cleanup_ping(&sm);
586 jleave:
587 if(linebuf != NULL)
588 n_free(linebuf);
589 NYD2_LEAVE;
590 n_sigman_leave(&sm, n_SIGMAN_VIPSIGS_NTTYOUT);
591 return rv;
594 FL int
595 c_version(void *vp){
596 int longest, rv;
597 char *iop;
598 char const *cp, **arr;
599 size_t i, i2;
600 NYD_ENTER;
601 n_UNUSED(vp);
603 fprintf(n_stdout,
604 _("%s %s, %s (%s)\nFeatures included (+) or not (-)\n"),
605 n_uagent, ok_vlook(version), ok_vlook(version_date),
606 ok_vlook(build_osenv));
608 /* *features* starts with dummy byte to avoid + -> *folder* expansions */
609 i = strlen(cp = &ok_vlook(features)[1]) +1;
610 iop = n_autorec_alloc(i);
611 memcpy(iop, cp, i);
613 arr = n_autorec_alloc(sizeof(cp) * VAL_FEATURES_CNT);
614 for(longest = 0, i = 0; (cp = n_strsep(&iop, ',', TRU1)) != NULL; ++i){
615 arr[i] = cp;
616 i2 = strlen(cp);
617 longest = n_MAX(longest, (int)i2);
619 qsort(arr, i, sizeof(cp), &a_cmisc_version_cmp);
621 /* We use aligned columns, so don't use n_SCRNWIDTH_FOR_LISTS */
622 for(++longest, i2 = 0; i-- > 0;){
623 cp = *(arr++);
624 fprintf(n_stdout, "%-*s ", longest, cp);
625 i2 += longest;
626 if(UICMP(z, ++i2 + longest, >=, n_scrnwidth) || i == 0){
627 i2 = 0;
628 putc('\n', n_stdout);
632 if((rv = ferror(n_stdout) != 0))
633 clearerr(n_stdout);
634 NYD_LEAVE;
635 return rv;
638 /* s-it-mode */