/* openvt.c - Run a program on a new VT
 *
 * Copyright 2014 Vivek Kumar Bhagat <vivek.bhagat89@gmail.com>
 *
 * No Standard

USE_OPENVT(NEWTOY(openvt, "c#<1>63sw", TOYFLAG_BIN|TOYFLAG_NEEDROOT))
USE_DEALLOCVT(NEWTOY(deallocvt, ">1", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_NEEDROOT))

config OPENVT
  bool "openvt"
  default n
  help
    usage: openvt [-c N] [-sw] [command [command_options]]

    start a program on a new virtual terminal (VT)

    -c N  Use VT N
    -s    Switch to new VT
    -w    Wait for command to exit

    if -sw used together, switch back to originating VT when command completes

config DEALLOCVT
  bool "deallocvt"
  default n
  help
    usage: deallocvt [N]

    Deallocate unused virtual terminal /dev/ttyN, or all unused consoles.
*/

#define FOR_openvt
#include "toys.h"
#include <linux/vt.h>
#include <linux/kd.h>

GLOBALS(
  unsigned long vt_num;
)

int open_console(void)
{
  char arg, *console_name[] = {"/dev/tty", "/dev/tty0", "/dev/console"};
  int i, fd;

  for (i = 0; i < ARRAY_LEN(console_name); i++) {
    fd = open(console_name[i], O_RDWR);
    if (fd >= 0) {
      arg = 0;
      if (!ioctl(fd, KDGKBTYPE, &arg)) return fd;
      close(fd);
    }
  }

  /* check std fd 0, 1 and 2 */
  for (fd = 0; fd < 3; fd++) {
    arg = 0;
    if (0 == ioctl(fd, KDGKBTYPE, &arg)) return fd;
  }

  return -1;
}

int xvtnum(int fd)
{
  int ret;

  ret = ioctl(fd, VT_OPENQRY, (int *)&TT.vt_num);
  if (ret != 0 || TT.vt_num <= 0) perror_exit("can't find open VT");

  return TT.vt_num;
}

void openvt_main(void)
{
  int fd, vt_fd, ret = 0;
  struct vt_stat vstate;
  pid_t pid;

  if (!(toys.optflags & FLAG_c)) {
    // check if fd 0,1 or 2 is already opened
    for (fd = 0; fd < 3; fd++)
      if (!ioctl(fd, VT_GETSTATE, &vstate)) {
        ret = xvtnum(fd);
        break;
      }

    // find VT number using /dev/console
    if (!ret) {
      fd = xopen("/dev/console", O_RDONLY | O_NONBLOCK);
      xioctl(fd, VT_GETSTATE, &vstate);
      xvtnum(fd);
    }
  }

  sprintf(toybuf, "/dev/tty%lu", TT.vt_num);
  fd = open_console();
  xioctl(fd, VT_GETSTATE, &vstate);

  close(0);  //new vt becomes stdin
  vt_fd = xopen(toybuf, O_RDWR);
  if (toys.optflags & FLAG_s) {
    ioctl(vt_fd, VT_ACTIVATE, TT.vt_num);
    ioctl(vt_fd, VT_WAITACTIVE, TT.vt_num);
  }

  close(1);
  close(2);
  dup2(vt_fd, 1);
  dup2(vt_fd, 2);
  while (vt_fd > 2)
    close(vt_fd--);

  pid = xfork();
  if (!pid) {
    setsid();
    ioctl(vt_fd, TIOCSCTTY, 0);
    xexec(toys.optargs);
  }

  if (toys.optflags & FLAG_w) {
    while (-1 == waitpid(pid, NULL, 0) && errno == EINTR)
      ;
    if (toys.optflags & FLAG_s) {
      ioctl(fd, VT_ACTIVATE, vstate.v_active);
      ioctl(fd, VT_WAITACTIVE, vstate.v_active);
      //check why deallocate isn't working here
      xioctl(fd, VT_DISALLOCATE, (void *)(ptrdiff_t)TT.vt_num); 
    }
  }
}

void deallocvt_main(void)
{
  long vt_num = 0; // 0 deallocates all unused consoles
  int fd;

  if (*toys.optargs) vt_num = atolx_range(*toys.optargs, 1, 63);

  if ((fd = open_console()) < 0) error_exit("can't open console");
  xioctl(fd, VT_DISALLOCATE, (void *)vt_num);
  if (CFG_TOYBOX_FREE) close(fd);
}