* Remove bell characted (\007) from status messages in Pico. This makes
[alpine.git] / pico / osdep / altedit.c
blob4a8f8db6ff394a5fa7dfb65f6d716d151e00c88d
1 #if !defined(lint) && !defined(DOS)
2 static char rcsid[] = "$Id: altedit.c 854 2007-12-07 17:44:43Z hubert@u.washington.edu $";
3 #endif
5 /*
6 * ========================================================================
7 * Copyright 2006-2007 University of Washington
8 * Copyright 2013-2017 Eduardo Chappa
10 * Licensed under the Apache License, Version 2.0 (the "License");
11 * you may not use this file except in compliance with the License.
12 * You may obtain a copy of the License at
14 * http://www.apache.org/licenses/LICENSE-2.0
16 * ========================================================================
20 #include <system.h>
21 #include <general.h>
23 #include "../../pith/osdep/pipe.h"
24 #include "../../pith/osdep/forkwait.h"
25 #include "../../pith/charconv/filesys.h"
27 #include "../estruct.h"
28 #include "../mode.h"
29 #include "../pico.h"
30 #include "../edef.h"
31 #include "../efunc.h"
32 #include "../keydefs.h"
33 #include "../utf8stub.h"
34 #include "tty.h"
35 #include "filesys.h"
37 #ifdef _WINDOWS
38 #include "mswin.h"
39 #endif
41 #include "altedit.h"
42 #ifndef _WINDOWS
43 #include "signals.h"
45 #ifdef SIGCHLD
46 static jmp_buf pico_child_state;
47 static short pico_child_jmp_ok, pico_child_done;
48 #endif /* SIGCHLD */
50 #endif /* !_WINDOWS */
53 /* internal prototypes */
54 #ifndef _WINDOWS
55 #ifdef SIGCHLD
56 RETSIGTYPE child_handler(int);
57 #endif /* SIGCHLD */
58 #else /* _WINDOWS */
59 int alt_editor_valid(char *, char *, size_t);
60 int alt_editor_valid_fp(char *);
61 #endif /* _WINDOWS */
64 * alt_editor - fork off an alternate editor for mail message composition
65 * if one is configured and passed from pine. If not, only
66 * ask for the editor if advanced user flag is set, and
67 * suggest environment's EDITOR value as default.
69 int
70 alt_editor(int f, int n)
72 #ifndef _WINDOWS
73 char eb[NLINE]; /* buf holding edit command */
74 char *fn; /* tmp holder for file name */
75 char result[128]; /* result string */
76 char prmpt[128];
77 int i, done = 0, ret = 0, rv, trv, beep;
78 pid_t child, pid;
79 RETSIGTYPE (*ohup)(int);
80 RETSIGTYPE (*oint)(int);
81 RETSIGTYPE (*osize)(int);
82 int status;
83 EML eml;
85 beep = 0;
86 eml.s = f ? "speller" : "editor";
88 if(gmode&MDSCUR){
89 emlwrite("Alternate %s not available in restricted mode", &eml);
90 return(-1);
93 strncpy(result, "Alternate %s complete.", sizeof(result));
94 result[sizeof(result)-1] = '\0';
96 if(f){
97 if(alt_speller){
98 strncpy(eb, alt_speller, sizeof(eb));
99 eb[sizeof(eb)-1] = '\0';
101 else
102 return(-1);
104 else if(Pmaster == NULL){
105 return(-1);
107 else{
108 eb[0] = '\0';
110 if(Pmaster->alt_ed){
111 char **lp, *wsp, *path, fname[MAXPATH+1];
112 int c;
114 for(lp = Pmaster->alt_ed; *lp && **lp; lp++){
115 if((wsp = strpbrk(*lp, " \t")) != NULL){
116 c = *wsp;
117 *wsp = '\0';
120 if(strchr(*lp, '/')){
121 rv = fexist(*lp, "x", (off_t *)NULL);
123 else{
124 if(!(path = getenv("PATH")))
125 path = ":/bin:/usr/bin";
127 rv = ~FIOSUC;
128 while(rv != FIOSUC && *path && pathcat(fname, &path, *lp))
129 rv = fexist(fname, "x", (off_t *)NULL);
132 if(wsp)
133 *wsp = c;
135 if(rv == FIOSUC){
136 strncpy(eb, *lp, sizeof(eb));
137 eb[sizeof(eb)-1] = '\0';
138 break;
143 if(!eb[0]){
144 if(!(gmode&MDADVN)){
145 unknown_command(0);
146 return(-1);
149 if(getenv("EDITOR")){
150 strncpy(eb, (char *)getenv("EDITOR"), sizeof(eb));
151 eb[sizeof(eb)-1] = '\0';
153 else
154 *eb = '\0';
156 while(!done){
157 pid = mlreplyd_utf8("Which alternate editor ? ", eb,
158 sizeof(eb), QDEFLT, NULL);
159 switch(pid){
160 case ABORT:
161 curwp->w_flag |= WFMODE;
162 return(-1);
163 case HELPCH:
164 emlwrite("no alternate editor help yet", NULL);
166 /* take sleep and break out after there's help */
167 sleep(3);
168 break;
169 case (CTRL|'L'):
170 sgarbf = TRUE;
171 update();
172 break;
173 case TRUE:
174 case FALSE: /* does editor exist ? */
175 if(*eb == '\0'){ /* leave silently? */
176 mlerase();
177 curwp->w_flag |= WFMODE;
178 return(-1);
181 done++;
182 break;
183 default:
184 break;
190 if((fn=writetmp(1, NULL)) == NULL){ /* get temp file */
191 emlwrite("Problem writing temp file for alt editor", NULL);
192 return(-1);
195 strncat(eb, " ", sizeof(eb)-strlen(eb)-1);
196 eb[sizeof(eb)-1] = '\0';
197 strncat(eb, fn, sizeof(eb)-strlen(eb)-1);
198 eb[sizeof(eb)-1] = '\0';
201 for(i = 0; i <= ((Pmaster) ? term.t_nrow : term.t_nrow - 1); i++){
202 movecursor(i, 0);
203 if(!i){
204 fputs("Invoking alternate ", stdout);
205 fputs(f ? "speller..." : "editor...", stdout);
208 peeol();
211 (*term.t_flush)();
212 if(Pmaster)
213 (*Pmaster->tty_fix)(0);
214 else
215 vttidy();
217 #ifdef SIGCHLD
218 if(Pmaster){
220 * The idea here is to keep any mail connections our caller
221 * may have open while our child's out running around...
223 pico_child_done = pico_child_jmp_ok = 0;
224 (void) signal(SIGCHLD, child_handler);
226 #endif
228 if((child = fork()) > 0){ /* wait for the child to finish */
229 ohup = signal(SIGHUP, SIG_IGN); /* ignore signals for now */
230 oint = signal(SIGINT, SIG_IGN);
231 #if defined(SIGWINCH) && defined(TIOCGWINSZ)
232 osize = signal(SIGWINCH, SIG_IGN);
233 #endif
235 #ifdef SIGCHLD
236 if(Pmaster){
237 while(!pico_child_done){
238 (*Pmaster->newmail)(0, 0);
239 if(!pico_child_done){
240 if(setjmp(pico_child_state) == 0){
241 pico_child_jmp_ok = 1;
242 sleep(600);
244 else
245 our_sigunblock(SIGCHLD);
248 pico_child_jmp_ok = 0;
251 #endif
253 trv = process_reap(child, &status, PR_NONE);
255 signal(SIGHUP, ohup); /* restore signals */
256 signal(SIGINT, oint);
257 #if defined(SIGWINCH) && defined(TIOCGWINSZ)
258 signal(SIGWINCH, osize);
259 #endif
262 * Report child's unnatural or unhappy exit...
264 if(trv > 0 && status == 0){
265 strncpy(result, "Alternate %s done", sizeof(result));
266 result[sizeof(result)-1] = '\0';
268 else {
269 snprintf(result, sizeof(result), "Alternate %%s terminated abnormally (%d)",
270 (trv == 0) ? status : -1);
271 if(f)
272 ret = -1;
273 else{
274 snprintf(prmpt, sizeof(prmpt), "Alt editor failed, use file %.20s of size %%ld chars anyway", fn);
275 ret = -2;
279 else if(child == 0){ /* spawn editor */
280 signal(SIGHUP, SIG_DFL); /* let editor handle signals */
281 signal(SIGINT, SIG_DFL);
282 #if defined(SIGWINCH) && defined(TIOCGWINSZ)
283 signal(SIGWINCH, SIG_DFL);
284 #endif
285 #ifdef SIGCHLD
286 (void) signal(SIGCHLD, SIG_DFL);
287 #endif
288 if(execl("/bin/sh", "sh", "-c", fname_to_locale(eb), (char *) NULL) < 0)
289 exit(-1);
291 else { /* error! */
292 snprintf(result, sizeof(result), _("Can't fork %%s: %s"), errstr(errno));
293 beep = 1;
294 ret = -1;
297 #ifdef SIGCHLD
298 (void) signal(SIGCHLD, SIG_DFL);
299 #endif
301 if(Pmaster)
302 (*Pmaster->tty_fix)(1);
305 * replace edited text with new text
307 curbp->b_flag &= ~BFCHG; /* make sure old text gets blasted */
309 if(ret == -2){
310 off_t filesize;
311 char prompt[128];
313 rv = fexist(fn, "r", &filesize);
314 if(rv == FIOSUC && filesize > 0){
315 snprintf(prompt, sizeof(prompt), prmpt, (long) filesize);
316 /* clear bottom 3 rows */
317 pclear(term.t_nrow-2, term.t_nrow);
318 i = mlyesno_utf8(prompt, FALSE);
319 if(i == TRUE){
320 ret = 0;
321 strncpy(result, "OK, alternate %s done", sizeof(result));
322 result[sizeof(result)-1] = '\0';
324 else
325 ret = -1;
327 else
328 ret = -1;
331 if(ret == 0)
332 readin(fn, 0, 0); /* read new text overwriting old */
334 our_unlink(fn); /* blast temp file */
335 curbp->b_flag |= BFCHG; /* mark dirty for packbuf() */
336 ttopen(); /* reset the signals */
337 pico_refresh(0, 1); /* redraw */
338 update();
339 if(beep)
340 emlwwrite(result, &eml);
341 else
342 emlwrite(result, &eml);
343 return(ret);
344 #else /* _WINDOWS */
345 char eb[2 * PATH_MAX]; /* buf holding edit command */
346 char *fn; /* tmp holder for file name */
347 char errbuf[128];
348 char **lp;
349 int status;
350 int done;
351 int rc;
354 if(Pmaster == NULL)
355 return(-1);
357 if(gmode&MDSCUR){
358 emlwrite("Alternate editor not available in restricted mode", NULL);
359 return(-1);
362 eb[0] = '\0';
364 if(Pmaster->alt_ed){
365 char *p;
367 for(lp = Pmaster->alt_ed; *lp && **lp; lp++)
368 if(*lp[0] == '\"'){
369 if(p = strchr(*lp + 1, '\"')){
370 *p = '\0';
371 rc = alt_editor_valid((*lp) + 1, eb, sizeof(eb));
372 *p = '\"';
373 if(rc){
374 strncat(eb, p + 1, sizeof(eb)-strlen(eb)-1);
375 eb[sizeof(eb)-1] = '\0';
376 break;
380 else if(p = strchr(*lp, ' ')){
381 for(rc = 0; !rc; ){
382 if(p)
383 *p = '\0';
385 rc = alt_editor_valid(*lp, eb, sizeof(eb));
387 if(p)
388 *p = ' ';
390 if(rc){
391 if(p){
392 strncat(eb, p, sizeof(eb)-strlen(eb)-1);
393 eb[sizeof(eb)-1] = '\0';
396 break;
398 else if(p)
399 p = strchr(p + 1, ' ');
400 else
401 break;
404 if(rc)
405 break;
407 else if(alt_editor_valid(*lp, eb, sizeof(eb)))
408 break;
411 if(!eb[0]){
412 if(!(gmode&MDADVN)){
413 unknown_command(0);
414 return(-1);
417 /* Guess which editor they want. */
418 if(getenv("EDITOR")){
419 strncpy(eb, (char *)getenv("EDITOR"), sizeof(eb));
420 eb[sizeof(eb)-1] = '\0';
422 else
423 *eb = '\0';
425 done = FALSE;
426 while(!done){
427 rc = mlreplyd_utf8("Which alternate editor ? ", eb,
428 sizeof(eb),QDEFLT,NULL);
430 switch(rc){
431 case ABORT:
432 curwp->w_flag |= WFMODE;
433 return(-1);
434 case HELPCH:
435 emlwrite("no alternate editor help yet", NULL);
437 /* take sleep and break out after there's help */
438 sleep(3);
439 break;
440 case (CTRL|'L'):
441 sgarbf = TRUE;
442 update();
443 break;
444 case TRUE:
445 case FALSE: /* does editor exist ? */
446 if(*eb == '\0'){ /* leave silently? */
447 mlerase();
448 curwp->w_flag |= WFMODE;
449 return(-1);
452 done++;
453 break;
454 default:
455 break;
460 if((fn=writetmp(1, NULL)) == NULL){ /* get temp file */
461 emlwrite("Problem writing temp file for alt editor", NULL);
462 return(-1);
465 emlwrite("Waiting for alternate editor to finish...", NULL);
467 #ifdef ALTED_DOT
468 if(eb[0] == '.' && eb[1] == '\0') {
469 status = mswin_exec_and_wait ("alternate editor", eb, fn, fn,
470 NULL, 0);
472 else
473 #endif /* ALTED_DOT */
474 { /* just here in case ALTED_DOT is defined */
475 strncat(eb, " ", sizeof(eb)-strlen(eb)-1);
476 eb[sizeof(eb)-1] = '\0';
477 strncat(eb, fn, sizeof(eb)-strlen(eb)-1);
478 eb[sizeof(eb)-1] = '\0';
480 status = mswin_exec_and_wait ("alternate editor", eb, NULL, NULL,
481 NULL, 0);
484 switch (status) {
486 case 0:
488 * Success: replace edited text with new text
490 curbp->b_flag &= ~BFCHG; /* make sure old text gets blasted */
491 readin(fn, 0, 0); /* read new text overwriting old */
492 our_unlink(fn); /* blast temp file */
493 curbp->b_flag |= BFCHG; /* mark dirty for packbuf() */
494 ttopen(); /* reset the signals */
495 pico_refresh(0, 1); /* redraw */
496 return(0);
500 * Possible errors.
502 case -1:
503 /* Failed to map return from WinExec to a HTASK. */
504 emlwrite("Problem finding alternate editor task handle.", NULL);
505 return (-1);
507 case -2:
508 /* User decided to abandon the alternate editor.*/
509 emlwrite("Alternate editor abandoned.", NULL);
510 return (-1);
512 default:
513 mswin_exec_err_msg ("alternate editor", status, errbuf, sizeof(errbuf));
514 emlwrite (errbuf, NULL);
515 return (-1);
517 return (-1);
518 #endif /*_WINDOWS */
523 #ifndef _WINDOWS
525 pathcat(char *buf, char **path, char *file)
527 register int n = 0;
529 while(**path && **path != ':'){
530 if(n++ > MAXPATH)
531 return(FALSE);
533 *buf++ = *(*path)++;
536 if(n){
537 if(n++ > MAXPATH)
538 return(FALSE);
540 *buf++ = '/';
543 while((*buf = *file++) != '\0'){
544 if(n++ > MAXPATH)
545 return(FALSE);
547 buf++;
550 if(**path == ':')
551 (*path)++;
553 return(TRUE);
556 #ifdef SIGCHLD
558 * child_handler - handle child status change signal
560 RETSIGTYPE
561 child_handler(int sig)
563 pico_child_done = 1;
564 if(pico_child_jmp_ok){
565 #ifdef sco
567 * Villy Kruse <vek@twincom.nl> says:
568 * This probably only affects older unix systems with a "broken" sleep
569 * function such as AT&T System V rel 3.2 and systems derived from
570 * that version. The problem is that the sleep function on these
571 * versions of unix will set up a signal handler for SIGALRM which
572 * will longjmp into the sleep function when the alarm goes off.
573 * This gives problems if another signal handler longjmps out of the
574 * sleep function, thus making the stack frame for the signal function
575 * invalid, and when the ALARM handler later longjmps back into the
576 * sleep function it does no longer have a valid stack frame.
577 * My sugested fix is to cancel the pending alarm in the SIGCHLD
578 * handler before longjmp'ing. This shouldn't hurt as there
579 * shouldn't be any ALARM pending at this point except possibly from
580 * the sleep call.
583 * [Even though it shouldn't hurt, we'll make it sco-specific for now.]
586 * The sleep call might have set up a signal handler which would
587 * longjmp back into the sleep code, and that would cause a crash.
589 signal(SIGALRM, SIG_IGN); /* Cancel signal handeler */
590 alarm(0); /* might longjmp back into sleep */
591 #endif
592 longjmp(pico_child_state, 1);
595 #endif /* SIGCHLD */
597 #else /* _WINDOWS */
599 * alt_editor_valid -- test the given exe name exists, and if so
600 * return full name in "cmdbuf".
603 alt_editor_valid(char *exe, char *cmdbuf, size_t ncmdbuf)
606 /****
607 **** This isn't the right way to do this. I believe we
608 **** should be doing all of this in TCHARs starting in
609 **** the alt_editor function instead of trying to convert
610 **** to char * here.
611 ****/
613 if(!exe)
614 return(FALSE);
616 #ifdef ALTED_DOT
617 if(exe[0] == '.' && exe[1] == '\0') {
618 cmdbuf[0] = '.';
619 cmdbuf[1] = '\0';
620 return(TRUE);
622 else
623 #endif /* ALTED_DOT */
625 if((isalpha((unsigned char) *exe) && *exe == ':') || strchr(exe, '\\')){
626 char *path;
628 if(alt_editor_valid_fp(path = exe)){
629 if(strchr(path, ' '))
630 snprintf(cmdbuf, ncmdbuf, "\"%s\"", path);
631 else{
632 strncpy(cmdbuf, path, ncmdbuf);
633 cmdbuf[ncmdbuf-1] = '\0';
636 return(TRUE);
639 else{
640 TCHAR pathbuflpt[PATH_MAX+1];
641 LPTSTR name, exelpt;
642 LPTSTR cmdbuflpt;
643 char *utf8;
645 cmdbuflpt = (LPTSTR) MemAlloc(ncmdbuf * sizeof(TCHAR));
646 cmdbuflpt[0] = L'0';
648 exelpt = utf8_to_lptstr(exe);
650 if(SearchPath(NULL, exelpt, NULL, PATH_MAX+1, pathbuflpt, &name)){
652 if(_tcschr(pathbuflpt, L' '))
653 _stprintf_s(cmdbuflpt, ncmdbuf, TEXT("\"%s\""), pathbuflpt);
654 else
655 _stprintf_s(cmdbuflpt, ncmdbuf, TEXT("%s"), pathbuflpt);
657 if(exelpt)
658 fs_give((void **) &exelpt);
660 utf8 = lptstr_to_utf8(cmdbuflpt);
661 if(utf8){
662 strncpy(cmdbuf, utf8, ncmdbuf);
663 cmdbuf[ncmdbuf-1] = '\0';
664 fs_give((void **) &utf8);
667 if(cmdbuflpt)
668 MemFree((void *) cmdbuflpt);
670 return(TRUE);
673 if(exelpt)
674 fs_give((void **) &exelpt);
676 if(cmdbuflpt)
677 MemFree((void *) cmdbuflpt);
680 return(FALSE);
685 * alt_editor_valid_fp -- test the given full path name exists
688 alt_editor_valid_fp(path)
689 char *path;
691 static char *exts[] = {".exe", ".com", ".bat", NULL};
692 char pathcopy[PATH_MAX + 1], *dot = NULL;
693 int i, j;
695 for(i = 0; pathcopy[i] = path[i]; i++)
696 if(path[i] == '.')
697 dot = &path[i];
699 if(dot && (!strucmp(dot, ".exe")
700 || !strucmp(dot, ".com") || !strucmp(dot, ".bat"))){
701 if(fexist(path, "x", (off_t *) NULL) == FIOSUC)
702 return(TRUE);
704 else{
705 for(j = 0; exts[j]; j++){
706 strncpy(&pathcopy[i], exts[j], sizeof(pathcopy)-i);
707 pathcopy[sizeof(pathcopy)-1] = '\0';
708 if(fexist(pathcopy, "x", (off_t *) NULL) == FIOSUC)
709 return(TRUE);
713 return(FALSE);
715 #endif