/*
 * ========================================================================
 * Copyright 2006-2007 University of Washington
 * Copyright 2013-2026 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 <system.h>
#include <general.h>

#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 <vek@twincom.nl> 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
