/* $XdotOrg$ */
/*
 * Copyright 2001-2005 by Kean Johnston <jkj@sco.com>
 * Copyright 1990,91 by Thomas Roell, Dinkelscherben, Germany
 * Copyright 1993 by David Wexelblat <dwex@goblin.org>
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation, and that the names of Thomas Roell, David Wexelblat 
 * and Kean Johnston not be used in advertising or publicity pertaining to
 * distribution of the software without specific, written prior permission.
 * Thomas Roell, David Wexelblat and Kean Johnston make no representations
 * about the suitability of this software for any purpose.  It is provided
 * "as is" without express or implied warranty.
 *
 * THOMAS ROELL, DAVID WEXELBLAT AND KEAN JOHNSTON DISCLAIM ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL THOMAS ROELLm DAVID WEXELBLAT
 * OR KEAN JOHNSTON BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL
 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
 * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
 * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
 * THIS SOFTWARE.
 *
 */
/* $XConsortium$ */

#include "X.h"
#include "Xmd.h"

#include "compiler.h"

#include "xf86.h"
#include "xf86Priv.h"
#include "xf86_OSlib.h"

#include <sys/utsname.h>

static Bool KeepTty = FALSE;
static Bool Protect0 = FALSE;
static Bool CRTSpecified = FALSE;
static int VTnum = -1;
static char vtdevice[48];

int usl_is_osr6 = -1;

static Bool
IsConsoleDevice(const char *dev)
{
  if ((!strcmp (dev, "/dev/console")) ||
      (!strcmp (dev, "/dev/syscon")) ||
      (!strcmp (dev, "/dev/systty")))
    return TRUE;

  return FALSE;
}

static int
is_osr6 (void)
{
  struct utsname uts;

  if (usl_is_osr6 == -1) {
    if (uname (&uts) < 0) {
      FatalError ("get_usl_ver: Failed to determine UNIX name (%s)\n",
	strerror (errno));
    }

    if (uts.version[0] == '6')
      usl_is_osr6 = 1;
    else
      usl_is_osr6 = 0;
  }

  return usl_is_osr6;
}


void
xf86OpenConsole(void)
{
  int fd, i, ioctl_ret;
  struct vt_mode VT;
  struct vt_stat vts;
  MessageType from = X_PROBED;
  struct sigaction sigvtsw;
  char *ttn;

  if (serverGeneration == 1) {
    int isconsole = 0, consdev = 0;

    /* check if we're run with euid==0 */
    if (geteuid() != 0) {
      FatalError("xf86OpenConsole: Server must be suid root\n");
    }

    /* If we are run in the background we will get SIGTTOU. Ignore it. */
    OsSignal (SIGTTOU, SIG_IGN);

    /* Protect page 0 to help find NULL dereferencing */
    /* mprotect() doesn't seem to work */
    if (Protect0) {
      int fd = -1;

      if ((fd = open("/dev/zero", O_RDONLY, 0)) < 0) {
	xf86Msg(X_WARNING, "xf86OpenConsole: cannot open /dev/zero (%s)\n",
	  strerror(errno));
      } else {
	if ((int)mmap(0, 0x1000, PROT_NONE,
	    MAP_FIXED | MAP_SHARED, fd, 0) == -1) {
	  xf86Msg(X_WARNING, "xf86OpenConsole: failed to protect page 0 (%s)\n",
	    strerror(errno));
	}
	close(fd);
      }
    }

    /*
     * setup the virtual terminal manager
     */
    if (VTnum == -1) {
      /*
       * No device was specified. We need to query the kernel to see which
       * console device we are on (and in fact if we are on a console at all).
       */
      if (ioctl (0, VT_GETSTATE, &vts) < 0) {
	FatalError("xf86OpenConsole: Could not query active VT: %s\n",
	  strerror(errno));
      }
      VTnum = vts.v_active;
      if (is_osr6())
	snprintf (vtdevice, sizeof(vtdevice), "/dev/tty%02d", VTnum + 1);
      else
	snprintf (vtdevice, sizeof(vtdevice), "/dev/vt%02d", VTnum);
    } else {
      from = X_CMDLINE;
      if (is_osr6())
	snprintf (vtdevice, sizeof(vtdevice), "/dev/tty%02d", VTnum + 1);
      else
	snprintf (vtdevice, sizeof(vtdevice), "/dev/vt%02d", VTnum);
    }

    if (IsConsoleDevice(vtdevice)) {
      isconsole = 1;
      CRTSpecified = FALSE;	/* Dont honour -crt /dev/console */
    }

    if (ioctl (0, KIOCINFO, 0) >= 0)
      consdev = 1 + isconsole;

    if ((!CRTSpecified) && (isconsole || (!consdev))) {
      /*
       * Need to find a free VT
       */
      if ((fd = open ("/dev/console", O_WRONLY | O_NOCTTY)) < 0) {
	FatalError ("xf86OpenConsole: Could not open /dev/console: %s\n",
	  strerror (errno));
      }

      if (ioctl (fd, VT_OPENQRY, &VTnum) < 0) {
	FatalError ("xf86OpenConsole: Cannot find a free VT: %s\n",
	  strerror(errno));
      }
      close (fd);
      if (usl_is_osr6)
	snprintf (vtdevice, sizeof(vtdevice), "/dev/tty%02d", VTnum + 1);
      else
	snprintf (vtdevice, sizeof(vtdevice), "/dev/vt%02d", VTnum);
    }

    /*
     * Now we can dispose of stdin/stdout
     */
    fclose (stdin);
    fclose (stdout);

    if (CRTSpecified || isconsole || consdev != 1) {
      if (!KeepTty) {
	setpgrp();
      }
    }

    if ((xf86Info.consoleFd = open(vtdevice, O_RDWR | O_NONBLOCK, 0)) < 0) {
      FatalError("xf86OpenConsole: Cannot open %s: %s\n", vtdevice,
	strerror(errno));
    }

    xf86Msg (from, "using VT number %d (%s)\n\n", VTnum, vtdevice);
    xf86Info.vtno = VTnum;

    /* change ownership of the vt */
    chown(vtdevice, getuid(), getgid());

    /*
     * now get the VT
     */
    if (ioctl(xf86Info.consoleFd, VT_ACTIVATE, xf86Info.vtno) != 0) {
      FatalError("xf86OpenConsole: VT_ACTIVATE failed: %s\n",
	strerror(errno));
    }
    if (ioctl(xf86Info.consoleFd, VT_WAITACTIVE, xf86Info.vtno) != 0) {
      FatalError("xf86OpenConsole: VT_WAITACTIVE failed: %s\n",strerror(errno));
    }

    if (ioctl(xf86Info.consoleFd, VT_GETMODE, &VT) < 0) {
      FatalError("xf86OpenConsole: VT_GETMODE failed: %s\n", strerror(errno));
    }

    sigvtsw.sa_handler = xf86VTRequest;
    sigfillset(&sigvtsw.sa_mask);
    sigvtsw.sa_flags = 0;
    sigaction(SIGUSR1, &sigvtsw, NULL);

    VT.mode = VT_PROCESS;
    VT.relsig = SIGUSR1;
    VT.acqsig = SIGUSR1;

    ioctl_ret = ioctl(xf86Info.consoleFd, VT_SETMODE, &VT);
    if (ioctl_ret < 0) {
      FatalError("xf86OpenConsole: VT_SETMODE failed: %s\n", strerror(errno));
    }

    if (ioctl(xf86Info.consoleFd, KDSETMODE, KD_GRAPHICS) < 0) {
      FatalError("xf86OpenConsole: KD_GRAPHICS failed: %s\n", strerror(errno));
    }
  } else { /* serverGeneration != 1 */
    /*
     * now get the VT
     */
    if (ioctl(xf86Info.consoleFd, VT_ACTIVATE, xf86Info.vtno) != 0) {
      FatalError("xf86OpenConsole: VT_ACTIVATE failed: %s\n", strerror(errno));
    }
    if (ioctl(xf86Info.consoleFd, VT_WAITACTIVE, xf86Info.vtno) != 0) {
      FatalError("xf86OpenConsole: VT_WAITACTIVE failed: %s\n",strerror(errno));
    }
    /*
     * If the server doesn't have the VT when the reset occurs,
     * this is to make sure we don't continue until the activate
     * signal is received.
     */
    if (!xf86Screens[0]->vtSema)
      sleep(5);
  }
  return;
}

void
xf86CloseConsole(void)
{
  struct vt_mode   VT;
  struct sigaction sigvtsw;

  ioctl(xf86Info.consoleFd, KDSETMODE, KD_TEXT);  /* Back to text mode ... */

  sigvtsw.sa_handler = SIG_DFL;
  sigfillset(&sigvtsw.sa_mask);
  sigvtsw.sa_flags = 0;
  sigaction(SIGUSR1, &sigvtsw, NULL);

  if (ioctl(xf86Info.consoleFd, VT_GETMODE, &VT) != -1) {
    VT.mode = VT_AUTO;
    VT.waitv = 0;
    ioctl(xf86Info.consoleFd, VT_SETMODE, &VT); /* set dflt vt handling */
  }
  close(xf86Info.consoleFd);                 /* make the vt-manager happy */
  return;
}

int
xf86ProcessArgument(int argc, char *argv[], int i)
{
  /*
   * Keep server from detaching from controlling tty.  This is useful 
   * when debugging (so the server can receive keyboard signals.
   */
  if (!strcmp(argv[i], "-keeptty")) {
    KeepTty = TRUE;
    return(1);
  }

  /*
   * Undocumented flag to protect page 0 from read/write to help
   * catch NULL pointer dereferences.  This is purely a debugging
   * flag.
   */
  if (!strcmp(argv[i], "-protect0")) {
    Protect0 = TRUE;
    return(1);
  }

  if ((argv[i][0] == 'v') && (argv[i][1] == 't')) {
    if (sscanf(argv[i], "vt%2d", &VTnum) == 0) {
      UseMsg();
      VTnum = -1;
      return(0);
    }
    VTnum -= is_osr6();
    CRTSpecified = TRUE;
    return(1);
  }

  /*
   * Use a device the user specifies.
   */
  if (!strcmp(argv[i], "-crt")) {
    if (++i > argc) {
      UseMsg();
      VTnum = -1;
      return(0);
    } else {
      char *mytty = ttyname(0);
      char *arg = argv[i];

      if (!mytty)
	mytty = "\1";
      if (!arg[0])
	arg = "\2";	/* Prevent idiots from using -crt "" */

      if (strcmp (mytty, arg) != 0) {
	char *fmt;

	if (is_osr6())
	  fmt = "/dev/tty%02d";
	else
	  fmt = "/dev/vt%02d";

	if (sscanf(arg, fmt, &VTnum) == 0) {
	  UseMsg();
	  VTnum = -1;
	  return(0);
	}

	/* OSR6 devices start names at 1, UW7 starts at 0 */
	VTnum -= is_osr6();
	CRTSpecified = TRUE;
      }
      return(2);
    }
  }
  return(0);
}

void
xf86UseMsg(void)
{
  if (is_osr6()) {
    ErrorF("-crt /dev/ttyXX        use the specified VT device\n");
    ErrorF("vtXX                   use the specified VT number (01-16)\n");
  } else {
    ErrorF("-crt /dev/vtXX         use the specified VT device\n");
    ErrorF("vtXX                   use the specified VT number (00-15)\n");
  }

  ErrorF("-keeptty               ");
  ErrorF("don't detach controlling tty (for debugging only)\n");
  return;
}
