/*
   shutdown.c --- shutdown command.

   (c) Copyright 1995 SHW Wabnitz
   Written by Bernhard Fastenrath (fasten@shw.com)

   This file may be distributed under the terms
   of the GNU General Public License.

   History:
	12-12-95, bf: fflush (stdout) added (for Syslog)
	10-30-96, bf: shutdown delay
        11-28-96, bf: message format changed
*/

#include <dos.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <exec/types.h>
#include <exec/memory.h>
#include <intuition/intuition.h>
#include <proto/exec.h>
#include <proto/intuition.h>

#include "queue/queue.h"
#include "queue/shutdown.h"
#include "shutdown_cmd.h"
#include "shutdownbase.h"

#if defined (__GNUC__)
#include "queue/queue_inline.h"
#elif defined (__SASC)
#include "queue/queue_pragmas.h"
#endif

#if defined (__GNUC__)
#include "shutdown_inline.h"
#elif defined (__SASC)
#include "shutdown_lib/shutdown_pragma.h"
#endif

#define SIGBREAKF_MASK ( SIGBREAKF_CTRL_C | SIGBREAKF_CTRL_D | \
			 SIGBREAKF_CTRL_E | SIGBREAKF_CTRL_F )

int Unmount (char *filesystem, int mode);
LONG Shutdown (ULONG mode);

void GoDown (int time);
void CleanUp (char *error);
QMessage *BroadcastShutdownMsg (ULONG status, ULONG time);

struct IntuitionBase *IntuitionBase = NULL;
struct Library *QueueBase = NULL;
struct Library *ShutdownBase = NULL;
struct timerequest *TimerReq = NULL;
struct MsgPort *TimerPort = NULL;
struct MsgPort *ShutdownPort = NULL;
QHandle ShutdownHandle = NULL;
ULONG TimerMask;
ULONG TimerErr = 1;
ULONG ShutdownMask;
ULONG WaitMask;
ULONG QueueSignal, QueueMask;
int point_of_no_return = 0;
char *ShutdownPortname = "Shutdown";
int SpNameLen;
char *CantAllocatePort = "Can't allocate signal or message port.\n";
char *ShutdownCancelled = "shutdown cancelled.\n";
char *OutOfMemory = "Out of memory.\n";
char *SorryTooLate = "Too late to stop shutdown.\n";
int MessageTimedOut = 0;
int HaltFlag = HFLG_HALT;
int SyncTime = 5;
int MaxTime = 60;
int ShutdownTime = 10;
int UseShutdownLibFlag = 0;
int AbortableFlag = 1;
int FastBoot = 0;
int UnmountMode = UMNT_INHIBIT;
int Error;
char *optarg = NULL;

int
getopt (int argc, char *argv[], char *opts)
{
  static int pos = 0;
  char *c;

  while (++pos < argc && argv[pos][0] != '-');
  if (pos >= argc)
    return -1;
  if ((c = strchr (opts, (int) argv[pos][1])) == 0)
    return (int) '?';
  if (*(c+1) != ':')
    return (int) *c;
  if (strlen (argv[pos]) > 2)
    optarg = argv[pos] + 2;
  else if (pos+1 < argc)
    optarg = argv[pos+1];
  else
    return (int) '?';
  return (int) *c;
}

ULONG
PutAndGet (Message *msg, char *portname)
{
  struct MsgPort *prt, *reply_port;

  if (!(reply_port = CreateMsgPort ()))
    return 1;
  msg -> mn_ReplyPort = reply_port;

  Forbid ();
  if (prt = FindPort (portname))
  {
    PutMsg (prt, msg);
    Permit ();
    do
      WaitPort (reply_port);
    while (!GetMsg (reply_port));
  }
  else
    Permit ();

  DeleteMsgPort (reply_port);
  return (ULONG) (prt ? 0 : 2);
}

void
CancelRunningShutdown (void)
{
  Message *msg;

  if (!(msg = AllocMem (sizeof (Message), MEMF_PUBLIC | MEMF_CLEAR)))
    CleanUp (OutOfMemory);

  switch (PutAndGet (msg, ShutdownPortname))
  {
    case 0:
      if (msg -> mn_Node.ln_Name == (APTR) -1)
        CleanUp (SorryTooLate);
      CleanUp (ShutdownCancelled);
    case 1:
      CleanUp (CantAllocatePort);
    case 2:
      CleanUp ("Shutdown is not running.\n");
  }
  FreeMem (msg, sizeof (Message));
}

void
CleanUp (char *error)
{
  QMessage *msg;

  if (error)
    printf (error);
  if (!TimerErr)
    CloseDevice ((struct IORequest *) TimerReq);
  if (TimerReq)
    DeleteIORequest (TimerReq);
  if (TimerPort)
    DeleteMsgPort (TimerPort);
  if (ShutdownPort)
  {
    if (ShutdownPort -> mp_Node.ln_Name)
      FreeMem (ShutdownPort -> mp_Node.ln_Name, SpNameLen);
    RemPort (ShutdownPort);
    DeleteMsgPort (ShutdownPort);
  }
  if (ShutdownHandle)
  {
    while (msg = QGetMsg (ShutdownHandle))
      QFreeMsg (msg, sizeof (ShutdownMessage));
    if (QClose (ShutdownHandle))
    {
      printf ("Exiting, waiting for replies.\n");
      while (QClose (ShutdownHandle) > 0)
      {
        Delay (50);
        while (msg = QGetMsg (ShutdownHandle))
	  QFreeMsg (msg, sizeof (ShutdownMessage));
      }
    }
    FreeSignal (QueueSignal);
  }
  if (QueueBase)
    CloseLibrary (QueueBase);
  if (ShutdownBase)
    CloseLibrary (ShutdownBase);
  if (IntuitionBase)
    CloseLibrary ((struct Library *) IntuitionBase);  
  exit (error ? EXIT_FAILURE : EXIT_SUCCESS);
}

int
CancelShutdown (void)
{
  if (point_of_no_return)
    return 0;

  BroadcastShutdownMsg (SHUTDOWN_ABORT, 0);
  CleanUp (ShutdownCancelled);
}

QMessage *
GetShutdownMessage (void)
{
  QMessage *sm;

  for (;;)
  {
    if (sm = QGetMsg (ShutdownHandle))
      break;
    if (sm = QAllocMsg (sizeof (ShutdownMessage)))
      break;
    Delay (50);
  }
  return sm;
}

QMessage *
BroadcastShutdownMsg (ULONG status, ULONG time)
{
  ShutdownMessage *smsg;
  QMessage *qmsg;

  qmsg = GetShutdownMessage ();
  smsg = (ShutdownMessage *) qmsg -> qm_Data;
  smsg -> sm_Status   = status;
  smsg -> sm_TimeLeft = time;
  QAddMsg (ShutdownHandle, qmsg);
  return qmsg;
}

void
AbortableDelay (int seconds)
{
  int abort = 0, sec;
  Message *msg;
  ULONG mask;

  TimerReq -> tr_node.io_Command = TR_ADDREQUEST;
  TimerReq -> tr_time.tv_secs  = seconds;
  TimerReq -> tr_time.tv_micro = 0;
  SendIO ((struct IORequest *) TimerReq);

  while (1)
  {
    mask = Wait (WaitMask);
    if (mask & ShutdownMask)
    {
      if (msg = (Message *) GetMsg (ShutdownPort))
      {
        if (msg -> mn_Node.ln_Pri == 0) /* shutdown cancel request */
        {
          if (point_of_no_return || AbortableFlag == 0)
	    msg -> mn_Node.ln_Name = (APTR) -1;
          else
            abort = 1;
        }
        else /* shutdown delay request */
        {
          sec = msg -> mn_Node.ln_Pri;

	  if (MaxTime > 0)
          {
	    if ((MaxTime -= sec) < 0)
              sec += MaxTime;
            ShutdownTime += sec;
	    printf ("Shutdown delayed by %d seconds.\n", sec);
            msg -> mn_Node.ln_Pri = sec;
          }
          else
          {
	    msg -> mn_Node.ln_Name = (APTR) -1;
	    printf ("Shutdown delay refused.\n");
          }
        }
        ReplyMsg ((struct Message *) msg);
      }
    }
    if (mask & QueueMask)
    {
      /* Shutdown Queue reply: we aren't really interested
         because we take our messages fresh from the reply
	 list when we need them.
	 The timer is still running so we just wait some more.
      */
    }
    if (mask & SIGBREAKF_MASK || abort)
    {
      if (point_of_no_return)
      {
	printf (SorryTooLate);
	continue;
      }
      if (!(mask & TimerMask))
        AbortIO ((struct IORequest *) TimerReq);
      WaitIO ((struct IORequest *) TimerReq);
      SetSignal (0L, TimerMask);

      if (mask & SIGBREAKF_CTRL_C || abort)
      {
	if (mask & SIGBREAKF_CTRL_C)
	  printf ("CTRL-C\n");
	CancelShutdown ();
	printf ("Timer stopped, press CTRL-E or CTRL-F to reboot.\n");
      }
      if (mask & SIGBREAKF_CTRL_D) /* CTRL-D: speed up */
      {
	printf ("CTRL-D\n");
	return;
      }
      if (!FastBoot)
      {
	FastBoot = 1;
	if (mask & SIGBREAKF_CTRL_E) /* CTRL-E: fast reboot */
        {
	  printf ("CTRL-E\n");
	  GoDown (5);
        }
	if (mask & SIGBREAKF_CTRL_F) /* CTRL-F: very fast reboot */
	{
	  printf ("CTRL-F\n");
	  GoDown (0);
	}
      }
      else
	return;
    }
    if (mask & TimerMask) /* done waiting */
      return;
  }
}

void
GoingDownMessage (int seconds)
{
  if (seconds / 3600)
    printf ("The system is going down in %d:%.2d hours.\n",
            seconds / 3600, (seconds % 3600) / 60);
  else if (seconds / 60)
    printf ("The system is going down in %d:%.2d minutes.\n",
            seconds / 60, seconds % 60);
  else
    printf ("The system is going down in %d seconds.\n", seconds);
  fflush (stdout);
}

int
main (int argc, char *argv[])
{
  int shutdown_interval;
  int cancel_flag = 0;
  int opt, rest;

  SetTaskPri (FindTask (0L), 10);

  /*** Options ***/

  while ((opt = getopt (argc, argv, "cehiM:nNrs:t:SW")) != -1)
    switch (opt)
    {
      case 'M': MaxTime = atoi (optarg); break;
      case 'W': UnmountMode = UMNT_READONLY; break;
      case 'S': UseShutdownLibFlag = 1; break;
      case 'R': UnmountMode = UMNT_REMOUNT; break;
      case 'n': AbortableFlag = 0; break;
      case 'r': HaltFlag = HFLG_REBOOT; break;
      case 'h': HaltFlag = HFLG_HALT; break;
      case 'e': HaltFlag = HFLG_EXIT; break;      
      case 't': ShutdownTime = atoi (optarg); break;
      case 's': SyncTime = atoi (optarg); break;
      case 'c': cancel_flag = 1; break;
      case 'i': ShutdownTime = 0; MaxTime = 0; break;
      default:
        printf ("Usage: %s [-cehinNrSW] [-t <time>] [-M <max. time>] [-s <sync time>].\n", argv[0]);
	exit (EXIT_FAILURE);
    }

  /*** Libraries ***/

  if (HaltFlag)
    IntuitionBase = (struct IntuitionBase *) OpenLibrary ("intuition.library", 37);
  if (!(QueueBase = OpenLibrary ("queue.library", 3)))
    CleanUp ("Failed to open queue.library.\n");
  if (UseShutdownLibFlag)
  {
    if (!(ShutdownBase = OpenLibrary ("shutdown.library", 2)))
      CleanUp ("Failed to open shutdown.library.\n");
  }

  /*** Cancel shutdown ***/

  if (cancel_flag)
    CancelRunningShutdown ();

  /*** Remount ***/

  if (UnmountMode == UMNT_REMOUNT)
  {
    Unmount (NULL, UnmountMode);
    CleanUp ("Filesystems remounted.\n");
  }

  /*** Shutdown queue ***/

  if ((QueueSignal = AllocSignal (-1)) == -1)
    CleanUp (CantAllocatePort);
  QueueMask = 1 << QueueSignal;
  if (!(ShutdownHandle = QOpen ("shutdown", QMODE_SEND, QueueSignal)))
  {
    FreeSignal (QueueSignal);
    CleanUp ("Failed to open shutdown queue.\n");
  }

  /**** Timer device ***/

  if (!(TimerPort = CreateMsgPort ()))
    CleanUp (CantAllocatePort);
  if (!(TimerReq = (struct timerequest *)
      CreateIORequest (TimerPort, sizeof (struct timerequest))))
    CleanUp (OutOfMemory);
  if (TimerErr = OpenDevice (TIMERNAME, UNIT_VBLANK, (struct IORequest *) TimerReq, 0))
    CleanUp ("Can't open timer device.\n");
  TimerMask = 1 << TimerPort -> mp_SigBit;

  /*** Don't start shutdown twice ***/

  Forbid ();
  if (FindPort (ShutdownPortname))
  {
    Permit ();
    CleanUp ("Shutdown is already running.\n");
  }
  Permit ();

  /*** Named message port ***/

  if (!(ShutdownPort = CreateMsgPort ()))
    CleanUp (CantAllocatePort);
  if (!(ShutdownPort -> mp_Node.ln_Name =
	AllocMem (SpNameLen = strlen (ShutdownPortname) + 1, MEMF_PUBLIC)))
    CleanUp (OutOfMemory);
  bcopy (ShutdownPortname, ShutdownPort -> mp_Node.ln_Name, SpNameLen);
  ShutdownPort -> mp_Node.ln_Pri = 0;
  AddPort (ShutdownPort);
  ShutdownMask = 1 << ShutdownPort -> mp_SigBit;

  /*** Shutdown interval loop ***/

  WaitMask = QueueMask | ShutdownMask | TimerMask | SIGBREAKF_MASK;

  while (ShutdownTime >= 10)
  {
    GoingDownMessage (ShutdownTime);
    BroadcastShutdownMsg (SHUTDOWN_WARN, ShutdownTime);
    shutdown_interval = ShutdownTime / 3;
    ShutdownTime -= shutdown_interval;
    if (rest = ShutdownTime % 5)
    {
      shutdown_interval += rest;
      ShutdownTime -= rest;
    }
    AbortableDelay (shutdown_interval);
  }
  GoDown (ShutdownTime);
}

void
GoDown (int time)
{
  struct EasyStruct es = {
    sizeof (struct EasyStruct), 0, "AmigaDOS", "The system is halted", "Ok"
  };
  QMessage *smo, *smi;
  ULONG mask;

  if (UseShutdownLibFlag)
  {
    Shutdown (SHUTDOWN_EXTERN);
  }

  if (time)
  {
    /* last warning */
    GoingDownMessage (time);
    BroadcastShutdownMsg (SHUTDOWN_NOW, time);
    AbortableDelay (time);

    /* too late for writing */
    BroadcastShutdownMsg (SHUTDOWN_UMOUNT, 0);
  }
  point_of_no_return = 1;

  printf ("The system is going down, unmounting filesystems.\n");
  fflush (stdout);
  Unmount (NULL, UnmountMode);
  AbortableDelay (SyncTime);

  /*** all done, who turns off the light? ***/
  if (time && HaltFlag != HFLG_EXIT)
  {
    smo = BroadcastShutdownMsg (SHUTDOWN_HALT, 0);

    TimerReq -> tr_node.io_Command = TR_ADDREQUEST;
    TimerReq -> tr_time.tv_secs  = 5;
    TimerReq -> tr_time.tv_micro = 0;
    SendIO ((struct IORequest *) TimerReq);

    for (;;) /*** Wait for the last broadcast to be replied ***/
    {
      mask = Wait (TimerMask | QueueMask);
      if (mask & QueueMask)
        while (smi = QGetMsg (ShutdownHandle))
        {
	  if (smi == smo)
	    mask |= TimerMask;
          QFreeMsg (smi, sizeof (ShutdownMessage));
        }
      if (mask & TimerMask)
	break;
    }
  }
  switch (HaltFlag)
  {
    case HFLG_HALT:
      printf ("The system is halted.\n");
      if (IntuitionBase)
        BuildEasyRequest (NULL, &es, NULL);
      Delay (10); /* wait 0.5 seconds */
      Disable ();
      while (1);
    case HFLG_REBOOT:
      ColdReboot ();
    case HFLG_EXIT:
      CleanUp ("Done.\n");
  }
}
