/* * ======================================================================== * Copyright 2006-2007 University of Washington * Copyright 2013-2021 Eduardo Chappa * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * ======================================================================== */ #include #include #include "../../pith/osdep/pipe.h" #include "../../pith/osdep/forkwait.h" #include "../../pith/charconv/filesys.h" #include "../estruct.h" #include "../mode.h" #include "../pico.h" #include "../edef.h" #include "../efunc.h" #include "../keydefs.h" #include "../utf8stub.h" #include "tty.h" #include "filesys.h" #ifdef _WINDOWS #include "mswin.h" #endif #include "altedit.h" #ifndef _WINDOWS #include "signals.h" #ifdef SIGCHLD static jmp_buf pico_child_state; static short pico_child_jmp_ok, pico_child_done; #endif /* SIGCHLD */ #endif /* !_WINDOWS */ /* internal prototypes */ #ifndef _WINDOWS #ifdef SIGCHLD RETSIGTYPE child_handler(int); #endif /* SIGCHLD */ #else /* _WINDOWS */ int alt_editor_valid(char *, char *, size_t); int alt_editor_valid_fp(char *); #endif /* _WINDOWS */ /* * alt_editor - fork off an alternate editor for mail message composition * if one is configured and passed from pine. If not, only * ask for the editor if advanced user flag is set, and * suggest environment's EDITOR value as default. */ int alt_editor(int f, int n) { #ifndef _WINDOWS char eb[NLINE]; /* buf holding edit command */ char *fn; /* tmp holder for file name */ char result[128]; /* result string */ char prmpt[128]; int i, done = 0, ret = 0, rv, trv, beep; pid_t child, pid; RETSIGTYPE (*ohup)(int); RETSIGTYPE (*oint)(int); RETSIGTYPE (*osize)(int); int status; EML eml; beep = 0; eml.s = f ? "speller" : "editor"; if(gmode&MDSCUR){ emlwrite("Alternate %s not available in restricted mode", &eml); return(-1); } strncpy(result, "Alternate %s complete.", sizeof(result)); result[sizeof(result)-1] = '\0'; if(f){ if(alt_speller){ strncpy(eb, alt_speller, sizeof(eb)); eb[sizeof(eb)-1] = '\0'; } else return(-1); } else if(Pmaster == NULL){ return(-1); } else{ eb[0] = '\0'; if(Pmaster->alt_ed){ char **lp, *wsp, *path, fname[MAXPATH+1]; int c = '\0'; for(lp = Pmaster->alt_ed; *lp && **lp; lp++){ if((wsp = strpbrk(*lp, " \t")) != NULL){ c = *wsp; *wsp = '\0'; } if(strchr(*lp, '/')){ rv = fexist(*lp, "x", (off_t *)NULL); } else{ if(!(path = getenv("PATH"))) path = ":/bin:/usr/bin"; rv = ~FIOSUC; while(rv != FIOSUC && *path && pathcat(fname, &path, *lp)) rv = fexist(fname, "x", (off_t *)NULL); } if(wsp) *wsp = c; if(rv == FIOSUC){ strncpy(eb, *lp, sizeof(eb)); eb[sizeof(eb)-1] = '\0'; break; } } } if(!eb[0]){ if(!(gmode&MDADVN)){ unknown_command(0); return(-1); } if(getenv("EDITOR")){ strncpy(eb, (char *)getenv("EDITOR"), sizeof(eb)); eb[sizeof(eb)-1] = '\0'; } else *eb = '\0'; while(!done){ pid = mlreplyd_utf8("Which alternate editor ? ", eb, sizeof(eb), QDEFLT, NULL); switch(pid){ case ABORT: curwp->w_flag |= WFMODE; return(-1); case HELPCH: emlwrite("no alternate editor help yet", NULL); /* take sleep and break out after there's help */ sleep(3); break; case (CTRL|'L'): sgarbf = TRUE; update(); break; case TRUE: case FALSE: /* does editor exist ? */ if(*eb == '\0'){ /* leave silently? */ mlerase(); curwp->w_flag |= WFMODE; return(-1); } done++; break; default: break; } } } } if((fn=writetmp(1, NULL)) == NULL){ /* get temp file */ emlwrite("Problem writing temp file for alt editor", NULL); return(-1); } strncat(eb, " ", sizeof(eb)-strlen(eb)-1); eb[sizeof(eb)-1] = '\0'; strncat(eb, fn, sizeof(eb)-strlen(eb)-1); eb[sizeof(eb)-1] = '\0'; for(i = 0; i <= ((Pmaster) ? term.t_nrow : term.t_nrow - 1); i++){ movecursor(i, 0); if(!i){ fputs("Invoking alternate ", stdout); fputs(f ? "speller..." : "editor...", stdout); } peeol(); } (*term.t_flush)(); if(Pmaster) (*Pmaster->tty_fix)(0); else vttidy(); #ifdef SIGCHLD if(Pmaster){ /* * The idea here is to keep any mail connections our caller * may have open while our child's out running around... */ pico_child_done = pico_child_jmp_ok = 0; (void) signal(SIGCHLD, child_handler); } #endif if((child = fork()) > 0){ /* wait for the child to finish */ ohup = signal(SIGHUP, SIG_IGN); /* ignore signals for now */ oint = signal(SIGINT, SIG_IGN); #if defined(SIGWINCH) && defined(TIOCGWINSZ) osize = signal(SIGWINCH, SIG_IGN); #endif #ifdef SIGCHLD if(Pmaster){ while(!pico_child_done){ (*Pmaster->newmail)(0, 0); if(!pico_child_done){ if(setjmp(pico_child_state) == 0){ pico_child_jmp_ok = 1; sleep(600); } else our_sigunblock(SIGCHLD); } pico_child_jmp_ok = 0; } } #endif trv = process_reap(child, &status, PR_NONE); signal(SIGHUP, ohup); /* restore signals */ signal(SIGINT, oint); #if defined(SIGWINCH) && defined(TIOCGWINSZ) signal(SIGWINCH, osize); #endif /* * Report child's unnatural or unhappy exit... */ if(trv > 0 && status == 0){ strncpy(result, "Alternate %s done", sizeof(result)); result[sizeof(result)-1] = '\0'; } else { snprintf(result, sizeof(result), "Alternate %%s terminated abnormally (%d)", (trv == 0) ? status : -1); if(f) ret = -1; else{ snprintf(prmpt, sizeof(prmpt), "Alt editor failed, use file %.20s of size %%ld chars anyway", fn); ret = -2; } } } else if(child == 0){ /* spawn editor */ signal(SIGHUP, SIG_DFL); /* let editor handle signals */ signal(SIGINT, SIG_DFL); #if defined(SIGWINCH) && defined(TIOCGWINSZ) signal(SIGWINCH, SIG_DFL); #endif #ifdef SIGCHLD (void) signal(SIGCHLD, SIG_DFL); #endif if(execl("/bin/sh", "sh", "-c", fname_to_locale(eb), (char *) NULL) < 0) exit(-1); } else { /* error! */ snprintf(result, sizeof(result), _("Can't fork %%s: %s"), errstr(errno)); beep = 1; ret = -1; } #ifdef SIGCHLD (void) signal(SIGCHLD, SIG_DFL); #endif if(Pmaster) (*Pmaster->tty_fix)(1); /* * replace edited text with new text */ curbp->b_flag &= ~BFCHG; /* make sure old text gets blasted */ if(ret == -2){ off_t filesize; char prompt[128]; rv = fexist(fn, "r", &filesize); if(rv == FIOSUC && filesize > 0){ snprintf(prompt, sizeof(prompt), prmpt, (long) filesize); /* clear bottom 3 rows */ pclear(term.t_nrow-2, term.t_nrow); i = mlyesno_utf8(prompt, FALSE); if(i == TRUE){ ret = 0; strncpy(result, "OK, alternate %s done", sizeof(result)); result[sizeof(result)-1] = '\0'; } else ret = -1; } else ret = -1; } if(ret == 0) readin(fn, 0, 0); /* read new text overwriting old */ our_unlink(fn); /* blast temp file */ curbp->b_flag |= BFCHG; /* mark dirty for packbuf() */ ttopen(); /* reset the signals */ pico_refresh(0, 1); /* redraw */ update(); if(beep) emlwwrite(result, &eml); else emlwrite(result, &eml); return(ret); #else /* _WINDOWS */ char eb[2 * PATH_MAX]; /* buf holding edit command */ char *fn; /* tmp holder for file name */ char errbuf[128]; char **lp; int status; int done; int rc; if(Pmaster == NULL) return(-1); if(gmode&MDSCUR){ emlwrite("Alternate editor not available in restricted mode", NULL); return(-1); } eb[0] = '\0'; if(Pmaster->alt_ed){ char *p; for(lp = Pmaster->alt_ed; *lp && **lp; lp++) if(*lp[0] == '\"'){ if(p = strchr(*lp + 1, '\"')){ *p = '\0'; rc = alt_editor_valid((*lp) + 1, eb, sizeof(eb)); *p = '\"'; if(rc){ strncat(eb, p + 1, sizeof(eb)-strlen(eb)-1); eb[sizeof(eb)-1] = '\0'; break; } } } else if(p = strchr(*lp, ' ')){ for(rc = 0; !rc; ){ if(p) *p = '\0'; rc = alt_editor_valid(*lp, eb, sizeof(eb)); if(p) *p = ' '; if(rc){ if(p){ strncat(eb, p, sizeof(eb)-strlen(eb)-1); eb[sizeof(eb)-1] = '\0'; } break; } else if(p) p = strchr(p + 1, ' '); else break; } if(rc) break; } else if(alt_editor_valid(*lp, eb, sizeof(eb))) break; } if(!eb[0]){ if(!(gmode&MDADVN)){ unknown_command(0); return(-1); } /* Guess which editor they want. */ if(getenv("EDITOR")){ strncpy(eb, (char *)getenv("EDITOR"), sizeof(eb)); eb[sizeof(eb)-1] = '\0'; } else *eb = '\0'; done = FALSE; while(!done){ rc = mlreplyd_utf8("Which alternate editor ? ", eb, sizeof(eb),QDEFLT,NULL); switch(rc){ case ABORT: curwp->w_flag |= WFMODE; return(-1); case HELPCH: emlwrite("no alternate editor help yet", NULL); /* take sleep and break out after there's help */ sleep(3); break; case (CTRL|'L'): sgarbf = TRUE; update(); break; case TRUE: case FALSE: /* does editor exist ? */ if(*eb == '\0'){ /* leave silently? */ mlerase(); curwp->w_flag |= WFMODE; return(-1); } done++; break; default: break; } } } if((fn=writetmp(1, NULL)) == NULL){ /* get temp file */ emlwrite("Problem writing temp file for alt editor", NULL); return(-1); } emlwrite("Waiting for alternate editor to finish...", NULL); #ifdef ALTED_DOT if(eb[0] == '.' && eb[1] == '\0') { status = mswin_exec_and_wait ("alternate editor", eb, fn, fn, NULL, 0); } else #endif /* ALTED_DOT */ { /* just here in case ALTED_DOT is defined */ strncat(eb, " ", sizeof(eb)-strlen(eb)-1); eb[sizeof(eb)-1] = '\0'; strncat(eb, fn, sizeof(eb)-strlen(eb)-1); eb[sizeof(eb)-1] = '\0'; status = mswin_exec_and_wait ("alternate editor", eb, NULL, NULL, NULL, 0); } switch (status) { case 0: /* * Success: replace edited text with new text */ curbp->b_flag &= ~BFCHG; /* make sure old text gets blasted */ readin(fn, 0, 0); /* read new text overwriting old */ our_unlink(fn); /* blast temp file */ curbp->b_flag |= BFCHG; /* mark dirty for packbuf() */ ttopen(); /* reset the signals */ pico_refresh(0, 1); /* redraw */ return(0); /* * Possible errors. */ case -1: /* Failed to map return from WinExec to a HTASK. */ emlwrite("Problem finding alternate editor task handle.", NULL); return (-1); case -2: /* User decided to abandon the alternate editor.*/ emlwrite("Alternate editor abandoned.", NULL); return (-1); default: mswin_exec_err_msg ("alternate editor", status, errbuf, sizeof(errbuf)); emlwrite (errbuf, NULL); return (-1); } return (-1); #endif /*_WINDOWS */ } #ifndef _WINDOWS int pathcat(char *buf, char **path, char *file) { register int n = 0; while(**path && **path != ':'){ if(n++ > MAXPATH) return(FALSE); *buf++ = *(*path)++; } if(n){ if(n++ > MAXPATH) return(FALSE); *buf++ = '/'; } while((*buf = *file++) != '\0'){ if(n++ > MAXPATH) return(FALSE); buf++; } if(**path == ':') (*path)++; return(TRUE); } #ifdef SIGCHLD /* * child_handler - handle child status change signal */ RETSIGTYPE child_handler(int sig) { pico_child_done = 1; if(pico_child_jmp_ok){ #ifdef sco /* * Villy Kruse says: * This probably only affects older unix systems with a "broken" sleep * function such as AT&T System V rel 3.2 and systems derived from * that version. The problem is that the sleep function on these * versions of unix will set up a signal handler for SIGALRM which * will longjmp into the sleep function when the alarm goes off. * This gives problems if another signal handler longjmps out of the * sleep function, thus making the stack frame for the signal function * invalid, and when the ALARM handler later longjmps back into the * sleep function it does no longer have a valid stack frame. * My suggested fix is to cancel the pending alarm in the SIGCHLD * handler before longjmp'ing. This shouldn't hurt as there * shouldn't be any ALARM pending at this point except possibly from * the sleep call. * * * [Even though it shouldn't hurt, we'll make it sco-specific for now.] * * * The sleep call might have set up a signal handler which would * longjmp back into the sleep code, and that would cause a crash. */ signal(SIGALRM, SIG_IGN); /* Cancel signal handler */ alarm(0); /* might longjmp back into sleep */ #endif longjmp(pico_child_state, 1); } } #endif /* SIGCHLD */ #else /* _WINDOWS */ /* * alt_editor_valid -- test the given exe name exists, and if so * return full name in "cmdbuf". */ int alt_editor_valid(char *exe, char *cmdbuf, size_t ncmdbuf) { /**** **** This isn't the right way to do this. I believe we **** should be doing all of this in TCHARs starting in **** the alt_editor function instead of trying to convert **** to char * here. ****/ if(!exe) return(FALSE); #ifdef ALTED_DOT if(exe[0] == '.' && exe[1] == '\0') { cmdbuf[0] = '.'; cmdbuf[1] = '\0'; return(TRUE); } else #endif /* ALTED_DOT */ if((isalpha((unsigned char) *exe) && *exe == ':') || strchr(exe, '\\')){ char *path; if(alt_editor_valid_fp(path = exe)){ if(strchr(path, ' ')) snprintf(cmdbuf, ncmdbuf, "\"%s\"", path); else{ strncpy(cmdbuf, path, ncmdbuf); cmdbuf[ncmdbuf-1] = '\0'; } return(TRUE); } } else{ TCHAR pathbuflpt[PATH_MAX+1]; LPTSTR name, exelpt; LPTSTR cmdbuflpt; char *utf8; cmdbuflpt = (LPTSTR) MemAlloc(ncmdbuf * sizeof(TCHAR)); cmdbuflpt[0] = L'0'; exelpt = utf8_to_lptstr(exe); if(SearchPath(NULL, exelpt, NULL, PATH_MAX+1, pathbuflpt, &name)){ if(_tcschr(pathbuflpt, L' ')) _stprintf_s(cmdbuflpt, ncmdbuf, TEXT("\"%s\""), pathbuflpt); else _stprintf_s(cmdbuflpt, ncmdbuf, TEXT("%s"), pathbuflpt); if(exelpt) fs_give((void **) &exelpt); utf8 = lptstr_to_utf8(cmdbuflpt); if(utf8){ strncpy(cmdbuf, utf8, ncmdbuf); cmdbuf[ncmdbuf-1] = '\0'; fs_give((void **) &utf8); } if(cmdbuflpt) MemFree((void *) cmdbuflpt); return(TRUE); } if(exelpt) fs_give((void **) &exelpt); if(cmdbuflpt) MemFree((void *) cmdbuflpt); } return(FALSE); } /* * alt_editor_valid_fp -- test the given full path name exists */ int alt_editor_valid_fp(path) char *path; { static char *exts[] = {".exe", ".com", ".bat", NULL}; char pathcopy[PATH_MAX + 1], *dot = NULL; int i, j; for(i = 0; pathcopy[i] = path[i]; i++) if(path[i] == '.') dot = &path[i]; if(dot && (!strucmp(dot, ".exe") || !strucmp(dot, ".com") || !strucmp(dot, ".bat"))){ if(fexist(path, "x", (off_t *) NULL) == FIOSUC) return(TRUE); } else{ for(j = 0; exts[j]; j++){ strncpy(&pathcopy[i], exts[j], sizeof(pathcopy)-i); pathcopy[sizeof(pathcopy)-1] = '\0'; if(fexist(pathcopy, "x", (off_t *) NULL) == FIOSUC) return(TRUE); } } return(FALSE); } #endif