termcap.c, tty.c: Some does not's
[s-mailx.git] / cmd-misc.c
blobcaf54d927786cb698909a8735441a49b719f66ff
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 char const **argv, **ap, *cp;
102 int rv;
103 bool_t doerr;
104 NYD2_ENTER;
106 #ifdef HAVE_ERRORS
107 doerr = (fp == n_stderr && (n_psonce & n_PSO_INTERACTIVE));
108 #else
109 doerr = FAL0;
110 #endif
112 for(ap = argv = vp; *ap != NULL; ++ap){
113 if(ap != argv){
114 if(doerr)
115 n_err(" ");
116 else
117 putc(' ', fp);
119 if((cp = fexpand(*ap, FEXP_NSHORTCUT | FEXP_NVAR)) == NULL)
120 cp = *ap;
121 if(doerr)
122 n_err(cp);
123 else
124 fputs(cp, fp);
126 if(donl){
127 if(doerr)
128 n_err("\n");
129 else
130 putc('\n', fp);
133 rv = (fflush(fp) == EOF);
134 rv |= ferror(fp) ? 1 : 0;
135 NYD2_LEAVE;
136 return rv;
139 static bool_t
140 a_cmisc_read_set(char const *cp, char const *value){
141 bool_t rv;
142 NYD2_ENTER;
144 if(!n_shexp_is_valid_varname(cp))
145 value = N_("not a valid variable name");
146 else if(!n_var_is_user_writable(cp))
147 value = N_("variable is read-only");
148 else if(!n_var_vset(cp, (uintptr_t)value))
149 value = N_("failed to update variable value");
150 else{
151 rv = TRU1;
152 goto jleave;
154 n_err("`read': %s: %s\n", V_(value), n_shexp_quote_cp(cp, FAL0));
155 rv = FAL0;
156 jleave:
157 NYD2_LEAVE;
158 return rv;
161 static int
162 a_cmisc_version_cmp(void const *s1, void const *s2){
163 char const * const *cp1, * const *cp2;
164 int rv;
165 NYD2_ENTER;
167 cp1 = s1;
168 cp2 = s2;
169 rv = strcmp(&(*cp1)[1], &(*cp2)[1]);
170 NYD2_LEAVE;
171 return rv;
174 FL int
175 c_sleep(void *v){
176 uiz_t sec, msec;
177 char **argv;
178 NYD_ENTER;
180 argv = v;
182 if((n_idec_uiz_cp(&sec, argv[0], 0, NULL) &
183 (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
184 ) != n_IDEC_STATE_CONSUMED)
185 goto jesyn;
187 if(argv[1] == NULL)
188 msec = 0;
189 else if((n_idec_uiz_cp(&msec, argv[1], 0, NULL) &
190 (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
191 ) != n_IDEC_STATE_CONSUMED)
192 goto jesyn;
194 if(UIZ_MAX / n_DATE_MILLISSEC < sec)
195 goto jeover;
196 sec *= n_DATE_MILLISSEC;
198 if(UIZ_MAX - sec < msec)
199 goto jeover;
200 msec += sec;
202 n_pstate_err_no = (n_msleep(msec, (argv[2] == NULL)) > 0)
203 ? n_ERR_INTR : n_ERR_NONE;
204 jleave:
205 NYD_LEAVE;
206 return (argv == NULL);
207 jeover:
208 n_err(_("`sleep': argument(s) overflow(s) datatype\n"));
209 n_pstate_err_no = n_ERR_OVERFLOW;
210 argv = NULL;
211 goto jleave;
212 jesyn:
213 n_err(_("Synopsis: sleep: <seconds> [<milliseconds>] [uninterruptible]\n"));
214 n_pstate_err_no = n_ERR_INVAL;
215 argv = NULL;
216 goto jleave;
219 FL int
220 c_shell(void *v)
222 sigset_t mask;
223 int rv;
224 FILE *fp;
225 char const **argv, *varname, *varres, *cp;
226 NYD_ENTER;
228 n_pstate_err_no = n_ERR_NONE;
229 argv = v;
230 varname = (n_pstate & n_PS_ARGMOD_VPUT) ? *argv++ : NULL;
231 varres = n_empty;
232 fp = NULL;
234 if(varname != NULL &&
235 (fp = Ftmp(NULL, "shell", OF_RDWR | OF_UNLINK | OF_REGISTER)
236 ) == NULL){
237 n_pstate_err_no = n_ERR_CANCELED;
238 rv = -1;
239 }else{
240 cp = a_cmisc_bangexp(*argv);
242 sigemptyset(&mask);
243 if(n_child_run(ok_vlook(SHELL), &mask,
244 n_CHILD_FD_PASS, (fp != NULL ? fileno(fp) : n_CHILD_FD_PASS),
245 "-c", cp, NULL, NULL, &rv) < 0){
246 n_pstate_err_no = n_err_no;
247 rv = -1;
248 }else{
249 rv = WEXITSTATUS(rv);
251 if(fp != NULL){
252 int c;
253 char *x;
254 off_t l;
256 fflush_rewind(fp);
257 l = fsize(fp);
258 if(UICMP(64, l, >=, UIZ_MAX -42)){
259 n_pstate_err_no = n_ERR_NOMEM;
260 varres = n_empty;
261 }else{
262 varres = x = n_autorec_alloc(l +1);
264 for(; l > 0 && (c = getc(fp)) != EOF; --l)
265 *x++ = c;
266 *x++ = '\0';
267 if(l != 0){
268 n_pstate_err_no = n_err_no;
269 varres = n_empty; /* xxx hmmm */
276 if(fp != NULL)
277 Fclose(fp);
279 if(varname != NULL){
280 if(!n_var_vset(varname, (uintptr_t)varres)){
281 n_pstate_err_no = n_ERR_NOTSUP;
282 rv = -1;
284 }else if(rv >= 0){
285 fprintf(n_stdout, "!\n");
286 /* Line buffered fflush(n_stdout); */
288 NYD_LEAVE;
289 return rv;
292 FL int
293 c_dosh(void *v)
295 int rv;
296 NYD_ENTER;
297 n_UNUSED(v);
299 if(n_child_run(ok_vlook(SHELL), 0, n_CHILD_FD_PASS, n_CHILD_FD_PASS, NULL,
300 NULL, NULL, NULL, &rv) < 0){
301 n_pstate_err_no = n_err_no;
302 rv = -1;
303 }else{
304 putc('\n', n_stdout);
305 /* Line buffered fflush(n_stdout); */
306 n_pstate_err_no = n_ERR_NONE;
307 rv = WEXITSTATUS(rv);
309 NYD_LEAVE;
310 return rv;
313 FL int
314 c_cwd(void *v){
315 struct n_string s_b, *sp;
316 size_t l;
317 char const *varname;
318 NYD_ENTER;
320 sp = n_string_creat_auto(&s_b);
321 varname = (n_pstate & n_PS_ARGMOD_VPUT) ? *(char const**)v : NULL;
322 l = PATH_MAX;
324 for(;; l += PATH_MAX){
325 sp = n_string_resize(n_string_trunc(sp, 0), l);
327 if(getcwd(sp->s_dat, sp->s_len) == NULL){
328 int e;
330 e = n_err_no;
331 if(e == n_ERR_RANGE)
332 continue;
333 n_perr(_("Failed to getcwd(3)"), e);
334 v = NULL;
335 break;
338 if(varname != NULL){
339 if(!n_var_vset(varname, (uintptr_t)sp->s_dat))
340 v = NULL;
341 }else{
342 l = strlen(sp->s_dat);
343 sp = n_string_trunc(sp, l);
344 if(fwrite(sp->s_dat, 1, sp->s_len, n_stdout) == sp->s_len &&
345 putc('\n', n_stdout) == EOF)
346 v = NULL;
348 break;
350 NYD_LEAVE;
351 return (v == NULL);
354 FL int
355 c_chdir(void *v)
357 char **arglist = v;
358 char const *cp;
359 NYD_ENTER;
361 if (*arglist == NULL)
362 cp = ok_vlook(HOME);
363 else if ((cp = fexpand(*arglist, FEXP_LOCAL | FEXP_NOPROTO)) == NULL)
364 goto jleave;
365 if (chdir(cp) == -1) {
366 n_perr(cp, 0);
367 cp = NULL;
369 jleave:
370 NYD_LEAVE;
371 return (cp == NULL);
374 FL int
375 c_echo(void *v){
376 int rv;
377 NYD_ENTER;
379 rv = a_cmisc_echo(v, n_stdout, TRU1);
380 NYD_LEAVE;
381 return rv;
384 FL int
385 c_echoerr(void *v){
386 int rv;
387 NYD_ENTER;
389 rv = a_cmisc_echo(v, n_stderr, TRU1);
390 NYD_LEAVE;
391 return rv;
394 FL int
395 c_echon(void *v){
396 int rv;
397 NYD_ENTER;
399 rv = a_cmisc_echo(v, n_stdout, FAL0);
400 NYD_LEAVE;
401 return rv;
404 FL int
405 c_echoerrn(void *v){
406 int rv;
407 NYD_ENTER;
409 rv = a_cmisc_echo(v, n_stderr, FAL0);
410 NYD_LEAVE;
411 return rv;
414 FL int
415 c_read(void * volatile vp){
416 struct n_sigman sm;
417 struct str trim;
418 struct n_string s, *sp;
419 char *linebuf;
420 size_t linesize, i;
421 int rv;
422 char const *ifs, **argv, *cp;
423 NYD2_ENTER;
425 sp = n_string_creat_auto(&s);
426 sp = n_string_reserve(sp, 64 -1);
428 ifs = ok_vlook(ifs);
429 linesize = 0;
430 linebuf = NULL;
431 argv = vp;
433 n_SIGMAN_ENTER_SWITCH(&sm, n_SIGMAN_ALL){
434 case 0:
435 break;
436 default:
437 n_pstate_err_no = n_ERR_INTR;
438 rv = -1;
439 goto jleave;
442 n_pstate_err_no = n_ERR_NONE;
443 rv = n_go_input(((n_pstate & n_PS_COMPOSE_MODE
444 ? n_GO_INPUT_CTX_COMPOSE : n_GO_INPUT_CTX_DEFAULT) |
445 n_GO_INPUT_FORCE_STDIN | n_GO_INPUT_NL_ESC |
446 n_GO_INPUT_PROMPT_NONE /* XXX POSIX: PS2: yes! */),
447 NULL, &linebuf, &linesize, NULL, NULL);
448 if(rv < 0){
449 if(!n_go_input_is_eof())
450 n_pstate_err_no = n_ERR_BADF;
451 goto jleave;
452 }else if(rv == 0){
453 if(n_go_input_is_eof()){
454 rv = -1;
455 goto jleave;
457 }else{
458 trim.s = linebuf;
459 trim.l = linesize;
461 for(; *argv != NULL; ++argv){
462 if(trim.l == 0 || n_str_trim_ifs(&trim, FAL0)->l == 0)
463 break;
465 /* The last variable gets the remaining line less trailing IFS-WS */
466 if(argv[1] == NULL){
467 jitall:
468 sp = n_string_assign_buf(sp, trim.s, trim.l);
469 trim.l = 0;
470 }else for(cp = trim.s, i = 1;; ++cp, ++i){
471 if(strchr(ifs, *cp) != NULL){
472 sp = n_string_assign_buf(sp, trim.s, i - 1);
473 trim.s += i;
474 trim.l -= i;
475 break;
477 if(i == trim.l)
478 goto jitall;
481 if(!a_cmisc_read_set(*argv, n_string_cp(sp))){
482 n_pstate_err_no = n_ERR_NOTSUP;
483 rv = -1;
484 break;
489 /* Set the remains to the empty string */
490 jleave:
491 for(; *argv != NULL; ++argv)
492 if(!a_cmisc_read_set(*argv, n_empty)){
493 n_pstate_err_no = n_ERR_NOTSUP;
494 rv = -1;
495 break;
498 n_sigman_cleanup_ping(&sm);
500 if(linebuf != NULL)
501 n_free(linebuf);
502 NYD2_LEAVE;
503 n_sigman_leave(&sm, n_SIGMAN_VIPSIGS_NTTYOUT);
504 return rv;
507 FL int
508 c_version(void *vp){
509 int longest, rv;
510 char *iop;
511 char const *cp, **arr;
512 size_t i, i2;
513 NYD_ENTER;
514 n_UNUSED(vp);
516 fprintf(n_stdout,
517 _("%s %s, %s (%s)\nFeatures included (+) or not (-)\n"),
518 n_uagent, ok_vlook(version), ok_vlook(version_date),
519 ok_vlook(build_osenv));
521 /* *features* starts with dummy byte to avoid + -> *folder* expansions */
522 i = strlen(cp = &ok_vlook(features)[1]) +1;
523 iop = n_autorec_alloc(i);
524 memcpy(iop, cp, i);
526 arr = n_autorec_alloc(sizeof(cp) * VAL_FEATURES_CNT);
527 for(longest = 0, i = 0; (cp = n_strsep(&iop, ',', TRU1)) != NULL; ++i){
528 arr[i] = cp;
529 i2 = strlen(cp);
530 longest = n_MAX(longest, (int)i2);
532 qsort(arr, i, sizeof(cp), &a_cmisc_version_cmp);
534 /* We use aligned columns, so don't use n_SCRNWIDTH_FOR_LISTS */
535 for(++longest, i2 = 0; i-- > 0;){
536 cp = *(arr++);
537 fprintf(n_stdout, "%-*s ", longest, cp);
538 i2 += longest;
539 if(UICMP(z, ++i2 + longest, >=, n_scrnwidth) || i == 0){
540 i2 = 0;
541 putc('\n', n_stdout);
545 if((rv = ferror(n_stdout) != 0))
546 clearerr(n_stdout);
547 NYD_LEAVE;
548 return rv;
551 /* s-it-mode */