/*
    zserial_proc_pipe - portable serial port API
	Module for unix pipe process
    Copyright (C) 2012-2023 Ladislav Vaiz <ok1zia@nagano.cz>

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
    version 2 as published by the Free Software Foundation.

*/

#include <zserial.h>

#include <zdebug.h>
#include <zerror.h>
#include <zsock.h>
#include <ztime.h>

#include <errno.h>
#include <stdlib.h>
#include <string.h>

#ifdef Z_HAVE_UNISTD_H
#include <unistd.h>
#endif

static int zserial_proc_pipe_open(struct zserial *zser){
    int pipe1[2], pipe2[2];
    int ret, err;
    char *c;

    //dbg("zserial_open pid=%d read_fd=%d write_fd=%d\n", zser->pid, zser->read_fd, zser->write_fd);
    if (zser->pid > 0) return 0;
    if (pipe(pipe1) < 0) return -1;
    if (pipe(pipe2) < 0) return -1;
    //dbg("zserial_proc_pipe_open: pipe1=r%d w%d  pipe2=r%d w%d\n", pipe1[0], pipe1[1], pipe2[0], pipe2[1]);

    zser->pid = fork();
    if (zser->pid < 0) return -1;
    if (zser->pid == 0){ /* child */
        int i;
        //error("child alive, closing\n");
        for (i = 0; i < 3; i++){
            if (i == pipe1[0] || i==pipe2[1]) continue;
            //error("close(%d)\n", i);
            close(i);
        }
        //error("duping\n");
        if (dup(pipe1[0]) < 0) exit(-1);
        if (dup(pipe2[1]) < 0) exit(-1);
        if (dup(pipe2[1]) < 0) exit(-1);
        //error("closing\n");
        closesocket(pipe1[0]);
        closesocket(pipe2[1]);
        //error("closing up to FD_SETSIZE\n");
        for (i = 3; i < FD_SETSIZE; i++) {
#ifdef Z_ANDROID
            if (i == 3) continue;
#endif
           //error("close(%d)\n", i);
            close(i);
        }
#ifdef Z_ANDROID
        execlp("/system/bin/sh", "/system/bin/sh", NULL);
        error("execlp failed, ret=%d errno=%d\n", ret, errno);
#else
        ret = execlp(zser->cmd, zser->cmd, zser->arg, NULL);
        error("execlp failed, ret=%d errno=%d\n", ret, errno);
#endif
        err = errno;
        c = z_strdup_strerror(err);
        error("*** failed exec '%s' errno=%d %s\n", zser->cmd, errno, c);
        exit(-1);
    }
    else{ /* parent */
        //dbg("fork() for zserial process %d\n", zser->pid);
        closesocket(pipe1[0]);
        closesocket(pipe2[1]);
        zser->write_fd = pipe1[1]; /* sdin of child, parent must write */
        zser->read_fd  = pipe2[0]; /* stdout and stderr of child, parent must read */
    }
    zser->opened = 1;
    return 0;
}

static int zserial_proc_pipe_read(struct zserial *zser, void *data, int len, int timeout_ms){
    int ret;
    
    ret = read(zser->read_fd, data, len);
    if (ret < 0){
        g_string_printf(zser->errorstr, "Can't read from %s (pipe fd %d): ", zser->id, zser->read_fd);
        z_strerror(zser->errorstr, errno);
        zserial_close(zser);
    }
    return ret;                        //
}

static int zserial_proc_pipe_write(struct zserial *zser, void *data, int len){
    int ret;

    ret = write(zser->write_fd, data, len);
    //dbg("zserial_proc_pipe_write pipe(%d) = %d\n", zser->write_fd, len);
    if (ret < 0){
        g_string_printf(zser->errorstr, "Can't write to %s: ", zser->id);
        z_strerror(zser->errorstr, errno);
        zserial_close(zser);
    }
    return ret;
}

static int zserial_proc_pipe_close(struct zserial *zser){
    int ret = 0;
    //dbg("zserial_proc_pipe_close() pipe pid=%d\n");
    if (zser->pid > 0){
        //dbg("kill(%d, %d)\n", zser->pid, SIGTERM);
        kill(zser->pid, SIGTERM);
        zser->pid = 0;
    }
    if (zser->read_fd >= 0) ret |= closesocket(zser->read_fd);
    if (zser->write_fd >= 0) ret |= closesocket(zser->write_fd);
    return ret;
}

static int zserial_proc_pipe_dtr(struct zserial *zser, int on){
    return -1;
}

static int zserial_proc_pipe_rts(struct zserial *zser, int on){
    return -1;
}


struct zserial *zserial_init_proc_pipe(const char *cmd, const char *arg){
	char *c;
	struct zserial *zser = zserial_init();
	zser->type = ZSERTYPE_PROC_PIPE;
	zser->id = g_strdup(cmd);
	c = strchr(zser->id, ' ');
	if (c != NULL) *c = '\0';
	zser->cmd = g_strdup(cmd);
    zser->arg = g_strdup(arg);
    //dbg("zserial_init_proc_pipe (%s)\n", cmd);

    zser->zs_open = zserial_proc_pipe_open;
    zser->zs_read = zserial_proc_pipe_read;
    zser->zs_write = zserial_proc_pipe_write;
    zser->zs_close = zserial_proc_pipe_close;
    zser->zs_dtr = zserial_proc_pipe_dtr;
    zser->zs_rts = zserial_proc_pipe_rts;
    return zser;
}

