/*
 * libexec.c
 *
 * Small wrapper for execve()
 *
 * (c) 2024 Dario Rodriguez <antartica@whereismybit.com>
 * This file is in the public domain.
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/ioctl.h>
#ifndef FIONREAD
#include <sys/filio.h>
#endif
#include <stdarg.h>
#include "libexec.h"

#ifndef RD
#define RD 0
#endif

#ifndef WR
#define WR 1
#endif

exec_t *
exec_init(char *name, char *cmdwithstringsub, char **envp)
{
        exec_t *exec;
        char *ptr;
        int n;
        if((exec=malloc(sizeof(exec_t)))==NULL)
                return(NULL); /* insuf. mem. */
        memset(exec,0,sizeof(exec_t));
        exec->pid=-1;
        exec->laststatus=-1;
        exec->fdin=exec->fdout=exec->fderr=-1;
        exec->envp=envp;
        if(name!=NULL && (exec->name=strdup(name))==NULL) {
                exec_free(exec),exec=NULL;
                return(NULL); /* insuf. mem. */
        }
        for(ptr=strchr(cmdwithstringsub,'%')
          ;ptr!=NULL && ptr[1]=='s'
          ;ptr=strchr(ptr+1,'%')
        ) {
                for(n=0;(ptr-(n+1))>=cmdwithstringsub && ptr[-(n+1)]=='%';) {
                        n++;
                }
                if(((n+1)%2)==0)
                        continue; /* this '%' is escaped (duplicated) */
                break; /* found the first %s */
        }
        exec->has_param=(ptr!=NULL)?1:0;
        if(!(exec->has_param)) {
                exec->prebuf=strdup(cmdwithstringsub);
                exec->postbuf=strdup("");
        } else {
                if((exec->prebuf=malloc((ptr-cmdwithstringsub)+1))!=NULL) {
                        memcpy(exec->prebuf,cmdwithstringsub,ptr-cmdwithstringsub);
                        exec->prebuf[ptr-cmdwithstringsub]='\0';
                }
                exec->postbuf=strdup(ptr+2);
        }
        if(exec->prebuf==NULL || exec->postbuf==NULL) {
                exec_free(exec),exec=NULL;
                return(NULL); /* insuf. mem. */
        }
        return(exec);
}

void
exec_free(exec_t *exec)
{
        if(exec==NULL)
                return;
        if(exec->pid!=-1)
                exec_close(exec);
        if(exec->name!=NULL)
                free(exec->name),exec->name=NULL;
        if(exec->prebuf!=NULL)
                free(exec->prebuf),exec->prebuf=NULL;
        if(exec->postbuf!=NULL)
                free(exec->postbuf),exec->postbuf=NULL;
        if(exec->fdin!=-1)
                close(exec->fdin),exec->fdin=-1;
        if(exec->fdout!=-1)
                close(exec->fdout),exec->fdout=-1;
        if(exec->fderr!=-1)
                close(exec->fderr),exec->fderr=-1;
        free(exec),exec=NULL;
        return;
}

int
exec_open(exec_t *exec, char *stringsub)
{
        char cmd[1024];
        int i,n;
        int pipein[2],pipeout[2],pipeerr[2];
        int in_error;
        if(exec==NULL)
                return(-1); /* sanity check error */
        if(exec->pid!=-1)
                return(-1); /* there is already a running child */
        snprintf(cmd,sizeof(cmd),"%s%s%s",exec->prebuf,(exec->has_param && stringsub!=NULL)?stringsub:"",exec->postbuf);
        cmd[sizeof(cmd)-1]='\0';
        pipein[RD]=pipein[WR]=-1;
        pipeout[RD]=pipeout[WR]=-1;
        pipeerr[RD]=pipeerr[WR]=-1;
        in_error=0;
        if(pipe(pipein)==-1 || pipe(pipeout)==-1 || pipe(pipeerr)==-1)
                in_error=1;
        if(in_error==0 && (exec->pid=fork())==0) {
                char *params[1024];
                int usedparams;
                char *ptr,*end,*realend;
                char endchar;
                realend=cmd+strlen(cmd);
                for(usedparams=0,ptr=cmd
                  ;ptr!=realend && usedparams<((sizeof(params)/sizeof(params[0]))-1)
                  ;
                ) {
                        endchar=(ptr[0]=='\"')?'\"':' ';
                        ptr+=(endchar=='\"')?1:0;
                        params[usedparams++]=ptr;
                        for(end=strchr(ptr,endchar)
                          ;end!=NULL
                          ;end=strchr(end+1,endchar)
                        ) {
                                for(n=0;(end-(n+1))>=ptr && end[-(n+1)]=='\\';) {
                                        n++;
                                }
                                if((n%2)==1)
                                        continue; /* this '\"' or ' ' is escaped */
                                break; /* found the first unescaped '\"' or ' ' */
                        }
                        end=(end==NULL)?realend:end;
                        *end='\0';
                        ptr=(end<realend)?end+1:realend;
                        if(endchar=='\"' && ptr[0]==' ')
                                ptr++;
                }
                params[usedparams]=NULL;
                if(usedparams==0)
                        exit(1); /* nothing to execute */
                dup2(pipein[RD],0);
                dup2(pipeout[WR],1);
                dup2(pipeerr[WR],2);
                for(i=3;i<1024;i++)
                        close(i);
                execve(params[0],params,exec->envp);
                exit(2); /* couldn't execve */
        } else if(in_error==0 && exec->pid>0) {
                close(pipein[RD]),pipein[RD]=-1;
                close(pipeout[WR]),pipeout[WR]=-1;
                close(pipeerr[WR]),pipeerr[WR]=-1;
                exec->fdin=pipein[WR],pipein[WR]=-1;
                exec->fdout=pipeout[RD],pipeout[RD]=-1;
                exec->fderr=pipeerr[RD],pipeerr[RD]=-1;
        } else {
                in_error=1;
        }
        if(in_error) {
                int *curfd[]={pipein,pipein+1,pipeout,pipeout+1,pipeerr,pipeerr+1};
                for(i=0;i<(sizeof(curfd)/sizeof(curfd[0]));i++) {
                        if(*(curfd[i])!=-1)
                                close(*(curfd[i])),*(curfd[i])=-1;
                }
                return(-1); /* was in error */
        }
        return(0);
}

int
exec_close(exec_t *exec)
{
        if(exec==NULL)
                return(-1); /* sanity check failed */
        if(exec->pid==-1)
                return(-1); /* nothing to do as child is not started */
        kill(exec->pid,SIGTERM);
        return(0);
}

int
exec_reap(exec_t *exec1, /* exec_t *exec2, */ ...)
{
        int status;
        int flag_reaped;
        exec_t *exec;
        va_list ap;
        flag_reaped=0;
        va_start(ap, exec1);
        for(exec=exec1;exec!=NULL;exec=va_arg(ap,exec_t *)) {
                if(exec->pid==-1)
                        continue;
                if(waitpid(exec->pid,&status,WNOHANG)==exec->pid) {
                        exec->pid=-1;
                        exec->laststatus=status;
                        close(exec->fdin),exec->fdin=-1;
                        close(exec->fdout),exec->fdout=-1;
                        close(exec->fderr),exec->fderr=-1;
                        flag_reaped=1;
                        continue;
                }
        }
        va_end(ap);
        return((flag_reaped==0)?-1:0); /* -1: no child has exited, 0: at least one child exited */
}

int
executil_installsignal(int sig, void (*f)(int))
{
        struct sigaction sa;
        sa.sa_handler=f;
        sigemptyset(&sa.sa_mask);
        sa.sa_flags=0;
        return(sigaction(sig,&sa,NULL));
}

int
executil_queued(int fd)
{
        int n;
        if(ioctl(fd,FIONREAD,&n)!=0)
                return(-1);
        return(n);
}

exec_t *
executil_fd2exec(int fd, exec_t *exec1, /* exec_t *exec2, */ ...)
{
        exec_t *exec;
        va_list ap;
        va_start(ap, exec1);
        for(exec=exec1;exec!=NULL;exec=va_arg(ap,exec_t *)) {
                if(exec->fdin==fd || exec->fdout==fd || exec->fderr==fd)
                        break;
        }
        va_end(ap);
        return(exec);
}