/*
 * BRLTTY - A background process providing access to the console screen (when in
 *          text mode) for a blind person using a refreshable braille display.
 *
 * Copyright (C) 1995-2024 by The BRLTTY Developers.
 *
 * BRLTTY comes with ABSOLUTELY NO WARRANTY.
 *
 * This is free software, placed under the terms of the
 * GNU Lesser General Public License, as published by the Free Software
 * Foundation; either version 2.1 of the License, or (at your option) any
 * later version. Please see the file LICENSE-LGPL for details.
 *
 * Web Page: http://brltty.app/
 *
 * This software is maintained by Dave Mielke <dave@mielke.cc>.
 */

#include "prologue.h"

#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/wait.h>

#include "log.h"
#include "hostcmd_unix.h"
#include "hostcmd_internal.h"

static int *
getPipeDescriptor (HostCommandStream *hcs, unsigned int index) {
  return &hcs->package.pipe[index];
}

static int *
getInputDescriptor (HostCommandStream *hcs) {
  return getPipeDescriptor(hcs, 0);
}

static int *
getOutputDescriptor (HostCommandStream *hcs) {
  return getPipeDescriptor(hcs, 1);
}

static int *
getParentDescriptor (HostCommandStream *hcs) {
  return hcs->isInput? getOutputDescriptor(hcs): getInputDescriptor(hcs);
}

static int *
getChildDescriptor (HostCommandStream *hcs) {
  return hcs->isInput? getInputDescriptor(hcs): getOutputDescriptor(hcs);
}

int
constructHostCommandPackageData (HostCommandPackageData *pkg) {
  pkg->pipe[0] = pkg->pipe[1] = -1;
  return 1;
}

void
destructHostCommandPackageData (HostCommandPackageData *pkg) {
  {
    int *fileDescriptor = pkg->pipe;
    const int *end = fileDescriptor + 2;

    while (fileDescriptor < end) {
      if (*fileDescriptor != -1) {
        close(*fileDescriptor);
        *fileDescriptor = -1;
      }

      fileDescriptor += 1;
    }
  }
}

int
prepareHostCommandStream (HostCommandStream *hcs, void *data) {
  if (pipe(hcs->package.pipe) == -1) {
    logSystemError("pipe");
    return 0;
  }

  return 1;
}

static int
finishParentHostCommandStream (HostCommandStream *hcs, void *data) {
  int *local = getParentDescriptor(hcs);
  int *remote = getChildDescriptor(hcs);

  close(*remote);
  *remote = -1;

  if (!finishHostCommandStream(hcs, *local)) return 0;
  *local = -1;

  return 1;
}

static int
finishChildHostCommandStream (HostCommandStream *hcs, void *data) {
  int *local = getChildDescriptor(hcs);
  int *remote = getParentDescriptor(hcs);

  close(*remote);
  *remote = -1;

  if (close(hcs->fileDescriptor) == -1) {
    logSystemError("close");
    return 0;
  }

  if (fcntl(*local, F_DUPFD, hcs->fileDescriptor) == -1) {
    logSystemError("fcntl[F_DUPFD]");
    return 0;
  }

  close(*local);
  *local = -1;

  return 1;
}

int
runCommand (
  int *result,
  const char *const *command,
  HostCommandStream *streams,
  int asynchronous
) {
  int ok = 0;
  sigset_t newMask, oldMask;
  pid_t pid;

  sigemptyset(&newMask);
  sigaddset(&newMask, SIGCHLD);
  sigprocmask(SIG_BLOCK, &newMask, &oldMask);

  switch ((pid = fork())) {
    case -1: /* error */
      logSystemError("fork");
      break;

    case 0: /* child */
      sigprocmask(SIG_SETMASK, &oldMask, NULL);

      if (processHostCommandStreams(streams, finishChildHostCommandStream, NULL)) {
        execvp(command[0], (char *const*)command);

        switch (errno) {
          case ENOENT:
            logMessage(LOG_ERR, "command not found: %s", command[0]);
            break;

          default:
            logSystemError("execvp");
            break;
        }
      }

      _exit(1);

    default: /* parent */
      if (processHostCommandStreams(streams, finishParentHostCommandStream, NULL)) {
        ok = 1;

        if (asynchronous) {
          *result = 0;
        } else {
          int status;

          if (waitpid(pid, &status, 0) == -1) {
            logSystemError("waitpid");
          } else if (WIFEXITED(status)) {
            *result = WEXITSTATUS(status);
            logMessage(LOG_DEBUG, "host command exit status: %d: %s",
                       *result, command[0]);
          } else if (WIFSIGNALED(status)) {
            *result = WTERMSIG(status);
            logMessage(LOG_DEBUG, "host command termination signal: %d: %s",
                       *result, command[0]);
            *result += 0X80;
          } else if (WIFSTOPPED(status)) {
            *result = WSTOPSIG(status);
            logMessage(LOG_DEBUG, "host command stop signal: %d: %s",
                       *result, command[0]);
            *result += 0X80;
          } else {
            logMessage(LOG_DEBUG, "unknown host command status: 0X%X: %s",
                       status, command[0]);
          }
        }
      }

      break;
  }

  sigprocmask(SIG_SETMASK, &oldMask, NULL);
  return ok;
}
