* Update to version 2.19.5
[alpine.git] / pico / osdep / altedit.c
blob737fbf9e42d393ba2cdc404f07f5338d74a701ca
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-2014 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 */
65 * alt_editor - fork off an alternate editor for mail message composition
66 * if one is configured and passed from pine. If not, only
67 * ask for the editor if advanced user flag is set, and
68 * suggest environment's EDITOR value as default.
70 int
71 alt_editor(int f, int n)
73 #ifndef _WINDOWS
74 char eb[NLINE]; /* buf holding edit command */
75 char *fn; /* tmp holder for file name */
76 char result[128]; /* result string */
77 char prmpt[128];
78 int i, done = 0, ret = 0, rv, trv;
79 pid_t child, pid;
80 RETSIGTYPE (*ohup)(int);
81 RETSIGTYPE (*oint)(int);
82 RETSIGTYPE (*osize)(int);
83 int status;
84 EML eml;
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), "\007Can't fork %%s: %s", errstr(errno));
293 ret = -1;
296 #ifdef SIGCHLD
297 (void) signal(SIGCHLD, SIG_DFL);
298 #endif
300 if(Pmaster)
301 (*Pmaster->tty_fix)(1);
304 * replace edited text with new text
306 curbp->b_flag &= ~BFCHG; /* make sure old text gets blasted */
308 if(ret == -2){
309 off_t filesize;
310 char prompt[128];
312 rv = fexist(fn, "r", &filesize);
313 if(rv == FIOSUC && filesize > 0){
314 snprintf(prompt, sizeof(prompt), prmpt, (long) filesize);
315 /* clear bottom 3 rows */
316 pclear(term.t_nrow-2, term.t_nrow);
317 i = mlyesno_utf8(prompt, FALSE);
318 if(i == TRUE){
319 ret = 0;
320 strncpy(result, "OK, alternate %s done", sizeof(result));
321 result[sizeof(result)-1] = '\0';
323 else
324 ret = -1;
326 else
327 ret = -1;
330 if(ret == 0)
331 readin(fn, 0, 0); /* read new text overwriting old */
333 our_unlink(fn); /* blast temp file */
334 curbp->b_flag |= BFCHG; /* mark dirty for packbuf() */
335 ttopen(); /* reset the signals */
336 pico_refresh(0, 1); /* redraw */
337 update();
338 emlwrite(result, &eml);
339 return(ret);
340 #else /* _WINDOWS */
341 char eb[2 * PATH_MAX]; /* buf holding edit command */
342 char *fn; /* tmp holder for file name */
343 char errbuf[128];
344 char **lp;
345 int status;
346 int done;
347 int rc;
350 if(Pmaster == NULL)
351 return(-1);
353 if(gmode&MDSCUR){
354 emlwrite("Alternate editor not available in restricted mode", NULL);
355 return(-1);
358 eb[0] = '\0';
360 if(Pmaster->alt_ed){
361 char *p;
363 for(lp = Pmaster->alt_ed; *lp && **lp; lp++)
364 if(*lp[0] == '\"'){
365 if(p = strchr(*lp + 1, '\"')){
366 *p = '\0';
367 rc = alt_editor_valid((*lp) + 1, eb, sizeof(eb));
368 *p = '\"';
369 if(rc){
370 strncat(eb, p + 1, sizeof(eb)-strlen(eb)-1);
371 eb[sizeof(eb)-1] = '\0';
372 break;
376 else if(p = strchr(*lp, ' ')){
377 for(rc = 0; !rc; ){
378 if(p)
379 *p = '\0';
381 rc = alt_editor_valid(*lp, eb, sizeof(eb));
383 if(p)
384 *p = ' ';
386 if(rc){
387 if(p){
388 strncat(eb, p, sizeof(eb)-strlen(eb)-1);
389 eb[sizeof(eb)-1] = '\0';
392 break;
394 else if(p)
395 p = strchr(p + 1, ' ');
396 else
397 break;
400 if(rc)
401 break;
403 else if(alt_editor_valid(*lp, eb, sizeof(eb)))
404 break;
407 if(!eb[0]){
408 if(!(gmode&MDADVN)){
409 unknown_command(0);
410 return(-1);
413 /* Guess which editor they want. */
414 if(getenv("EDITOR")){
415 strncpy(eb, (char *)getenv("EDITOR"), sizeof(eb));
416 eb[sizeof(eb)-1] = '\0';
418 else
419 *eb = '\0';
421 done = FALSE;
422 while(!done){
423 rc = mlreplyd_utf8("Which alternate editor ? ", eb,
424 sizeof(eb),QDEFLT,NULL);
426 switch(rc){
427 case ABORT:
428 curwp->w_flag |= WFMODE;
429 return(-1);
430 case HELPCH:
431 emlwrite("no alternate editor help yet", NULL);
433 /* take sleep and break out after there's help */
434 sleep(3);
435 break;
436 case (CTRL|'L'):
437 sgarbf = TRUE;
438 update();
439 break;
440 case TRUE:
441 case FALSE: /* does editor exist ? */
442 if(*eb == '\0'){ /* leave silently? */
443 mlerase();
444 curwp->w_flag |= WFMODE;
445 return(-1);
448 done++;
449 break;
450 default:
451 break;
456 if((fn=writetmp(1, NULL)) == NULL){ /* get temp file */
457 emlwrite("Problem writing temp file for alt editor", NULL);
458 return(-1);
461 emlwrite("Waiting for alternate editor to finish...", NULL);
463 #ifdef ALTED_DOT
464 if(eb[0] == '.' && eb[1] == '\0') {
465 status = mswin_exec_and_wait ("alternate editor", eb, fn, fn,
466 NULL, 0);
468 else
469 #endif /* ALTED_DOT */
470 { /* just here in case ALTED_DOT is defined */
471 strncat(eb, " ", sizeof(eb)-strlen(eb)-1);
472 eb[sizeof(eb)-1] = '\0';
473 strncat(eb, fn, sizeof(eb)-strlen(eb)-1);
474 eb[sizeof(eb)-1] = '\0';
476 status = mswin_exec_and_wait ("alternate editor", eb, NULL, NULL,
477 NULL, 0);
480 switch (status) {
482 case 0:
484 * Success: replace edited text with new text
486 curbp->b_flag &= ~BFCHG; /* make sure old text gets blasted */
487 readin(fn, 0, 0); /* read new text overwriting old */
488 our_unlink(fn); /* blast temp file */
489 curbp->b_flag |= BFCHG; /* mark dirty for packbuf() */
490 ttopen(); /* reset the signals */
491 pico_refresh(0, 1); /* redraw */
492 return(0);
496 * Possible errors.
498 case -1:
499 /* Failed to map return from WinExec to a HTASK. */
500 emlwrite("Problem finding alternate editor task handle.", NULL);
501 return (-1);
503 case -2:
504 /* User decided to abandon the alternate editor.*/
505 emlwrite("Alternate editor abandoned.", NULL);
506 return (-1);
508 default:
509 mswin_exec_err_msg ("alternate editor", status, errbuf, sizeof(errbuf));
510 emlwrite (errbuf, NULL);
511 return (-1);
513 return (-1);
514 #endif /*_WINDOWS */
519 #ifndef _WINDOWS
521 pathcat(char *buf, char **path, char *file)
523 register int n = 0;
525 while(**path && **path != ':'){
526 if(n++ > MAXPATH)
527 return(FALSE);
529 *buf++ = *(*path)++;
532 if(n){
533 if(n++ > MAXPATH)
534 return(FALSE);
536 *buf++ = '/';
539 while((*buf = *file++) != '\0'){
540 if(n++ > MAXPATH)
541 return(FALSE);
543 buf++;
546 if(**path == ':')
547 (*path)++;
549 return(TRUE);
553 #ifdef SIGCHLD
555 * child_handler - handle child status change signal
557 RETSIGTYPE
558 child_handler(int sig)
560 pico_child_done = 1;
561 if(pico_child_jmp_ok){
562 #ifdef sco
564 * Villy Kruse <vek@twincom.nl> says:
565 * This probably only affects older unix systems with a "broken" sleep
566 * function such as AT&T System V rel 3.2 and systems derived from
567 * that version. The problem is that the sleep function on these
568 * versions of unix will set up a signal handler for SIGALRM which
569 * will longjmp into the sleep function when the alarm goes off.
570 * This gives problems if another signal handler longjmps out of the
571 * sleep function, thus making the stack frame for the signal function
572 * invalid, and when the ALARM handler later longjmps back into the
573 * sleep function it does no longer have a valid stack frame.
574 * My sugested fix is to cancel the pending alarm in the SIGCHLD
575 * handler before longjmp'ing. This shouldn't hurt as there
576 * shouldn't be any ALARM pending at this point except possibly from
577 * the sleep call.
580 * [Even though it shouldn't hurt, we'll make it sco-specific for now.]
583 * The sleep call might have set up a signal handler which would
584 * longjmp back into the sleep code, and that would cause a crash.
586 signal(SIGALRM, SIG_IGN); /* Cancel signal handeler */
587 alarm(0); /* might longjmp back into sleep */
588 #endif
589 longjmp(pico_child_state, 1);
592 #endif /* SIGCHLD */
594 #else /* _WINDOWS */
596 * alt_editor_valid -- test the given exe name exists, and if so
597 * return full name in "cmdbuf".
600 alt_editor_valid(char *exe, char *cmdbuf, size_t ncmdbuf)
603 /****
604 **** This isn't the right way to do this. I believe we
605 **** should be doing all of this in TCHARs starting in
606 **** the alt_editor function instead of trying to convert
607 **** to char * here.
608 ****/
610 if(!exe)
611 return(FALSE);
613 #ifdef ALTED_DOT
614 if(exe[0] == '.' && exe[1] == '\0') {
615 cmdbuf[0] = '.';
616 cmdbuf[1] = '\0';
617 return(TRUE);
619 else
620 #endif /* ALTED_DOT */
622 if((isalpha((unsigned char) *exe) && *exe == ':') || strchr(exe, '\\')){
623 char *path;
625 if(alt_editor_valid_fp(path = exe)){
626 if(strchr(path, ' '))
627 snprintf(cmdbuf, ncmdbuf, "\"%s\"", path);
628 else{
629 strncpy(cmdbuf, path, ncmdbuf);
630 cmdbuf[ncmdbuf-1] = '\0';
633 return(TRUE);
636 else{
637 TCHAR pathbuflpt[PATH_MAX+1];
638 LPTSTR name, exelpt;
639 LPTSTR cmdbuflpt;
640 char *utf8;
642 cmdbuflpt = (LPTSTR) MemAlloc(ncmdbuf * sizeof(TCHAR));
643 cmdbuflpt[0] = L'0';
645 exelpt = utf8_to_lptstr(exe);
647 if(SearchPath(NULL, exelpt, NULL, PATH_MAX+1, pathbuflpt, &name)){
649 if(_tcschr(pathbuflpt, L' '))
650 _stprintf_s(cmdbuflpt, ncmdbuf, TEXT("\"%s\""), pathbuflpt);
651 else
652 _stprintf_s(cmdbuflpt, ncmdbuf, TEXT("%s"), pathbuflpt);
654 if(exelpt)
655 fs_give((void **) &exelpt);
657 utf8 = lptstr_to_utf8(cmdbuflpt);
658 if(utf8){
659 strncpy(cmdbuf, utf8, ncmdbuf);
660 cmdbuf[ncmdbuf-1] = '\0';
661 fs_give((void **) &utf8);
664 if(cmdbuflpt)
665 MemFree((void *) cmdbuflpt);
667 return(TRUE);
670 if(exelpt)
671 fs_give((void **) &exelpt);
673 if(cmdbuflpt)
674 MemFree((void *) cmdbuflpt);
677 return(FALSE);
682 * alt_editor_valid_fp -- test the given full path name exists
685 alt_editor_valid_fp(path)
686 char *path;
688 static char *exts[] = {".exe", ".com", ".bat", NULL};
689 char pathcopy[PATH_MAX + 1], *dot = NULL;
690 int i, j;
692 for(i = 0; pathcopy[i] = path[i]; i++)
693 if(path[i] == '.')
694 dot = &path[i];
696 if(dot && (!strucmp(dot, ".exe")
697 || !strucmp(dot, ".com") || !strucmp(dot, ".bat"))){
698 if(fexist(path, "x", (off_t *) NULL) == FIOSUC)
699 return(TRUE);
701 else{
702 for(j = 0; exts[j]; j++){
703 strncpy(&pathcopy[i], exts[j], sizeof(pathcopy)-i);
704 pathcopy[sizeof(pathcopy)-1] = '\0';
705 if(fexist(pathcopy, "x", (off_t *) NULL) == FIOSUC)
706 return(TRUE);
710 return(FALSE);
712 #endif