/*
   shutdown.c --- shutdown frontend.

   (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.
*/

#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>

#if defined (__GNUC__)
#include "halt_inline.h"
#else
#include "halt_pragmas.h"
#endif
#include "halt.h"

int GoDown (int now);

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

struct IntuitionBase *IntuitionBase = NULL;
struct Library *HaltBase = NULL;
ShutdownMessage *Smsg = NULL;
ShutdownContext SContext;
struct timerequest *TimerReq = NULL;
struct MsgPort *TimerPort = NULL;
struct MsgPort *ReplyPort = NULL;
struct MsgPort *ShutdownPort = NULL;
ULONG TimerMask;
ULONG TimerErr = 1;
ULONG ShutdownMask;
ULONG ReplyMask;
ULONG WaitMask;
int point_of_no_return = 0;
int ExpectTimeout = 0;
char *ShutdownPortname = "Shutdown";
int SpNameLen;
ULONG ShutdownFlags;
ULONG BroadcastTimeout = 5;
char *CantAllocatePort = "Can't allocate 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 = 1, SyncTime = 5;
int FastBoot = 0;
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;
}

ShutdownMessage *
AllocShutdownMessage ()
{
  ShutdownNode *node;

  if (!(node = (ShutdownNode *)
    AllocMem (sizeof (ShutdownNode), MEMF_PUBLIC | MEMF_CLEAR)))
  {
    return NULL;
  }
  node -> sn_Msg.sm_Msg.mn_ReplyPort = ReplyPort;
  node -> sn_Msg.sm_Msg.mn_Length = sizeof (ShutdownMessage);
  node -> sn_Msg.sm_Context = &SContext;
  AddTail (&SContext.sc_List, (struct Node *) node);
  return &node -> sn_Msg;
}

ULONG
PutAndGet (ShutdownMessage *smsg, char *portname)
{
  struct MsgPort *prt;

  Forbid ();
  if (prt = FindPort (portname))
  {
    PutMsg (prt, (struct Message *) smsg);
    Permit ();
    do
      WaitPort (ReplyPort);
    while (!GetMsg (ReplyPort));
  }
  else
    Permit ();
  return ((ULONG) prt);
}

void
CleanUp (char *error)
{
  ShutdownNode *node, *next;

  if (error)
    printf (error);
  if (!TimerErr)
    CloseDevice ((struct IORequest *) TimerReq);
  if (TimerReq)
    DeleteIORequest (TimerReq);
  if (TimerPort)
    DeleteMsgPort (TimerPort);
  if (ReplyPort)
    DeleteMsgPort (ReplyPort);
  if (ShutdownPort)
  {
    if (ShutdownPort -> mp_Node.ln_Name)
      FreeMem (ShutdownPort -> mp_Node.ln_Name, SpNameLen);
    RemPort (ShutdownPort);
    DeleteMsgPort (ShutdownPort);
  }
  node = (ShutdownNode *) SContext.sc_List.lh_Head;
  while (next = (ShutdownNode *) node -> sn_MinNode.mln_Succ)
  {
    FreeMem (node, sizeof (ShutdownNode));
    node = next;
  }
  if (HaltBase)
    CloseLibrary (HaltBase);
  if (IntuitionBase)
    CloseLibrary ((struct Library *) IntuitionBase);  
  exit (error ? EXIT_FAILURE : EXIT_SUCCESS);
}

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

  if (SContext.sc_TimeOuts)
  {
    printf ("Shutdown cannot exit, there are still %d unreplied messages.\n",
	    SContext.sc_TimeOuts);
    return 0;
  }
  if (ExpectTimeout == 1)
  {
    printf ("Press CTRL-C to abort the following "
	    "cancel message before it times out.\n");
    ExpectTimeout = 2;
  }
  if (Smsg)
  {
    BroadcastTimeout = 0;
    Smsg -> sm_Status = SHUTDOWN_ABORT;
    BroadcastShutdownMsg ();
  }
  CleanUp (ShutdownCancelled);
}

int
BroadcastShutdownMsg ()
{
  if (BroadcastTimeout)
  {
    TimerReq -> tr_node.io_Command = TR_ADDREQUEST;
    TimerReq -> tr_time.tv_secs  = BroadcastTimeout;
    TimerReq -> tr_time.tv_micro = 0;
    SendIO ((struct IORequest *) TimerReq);
  }
  Smsg -> sm_Flags = ShutdownFlags;
  ShutdownStatus (Smsg);
  if (Smsg -> sm_Flags & SDMF_SIG_MASK)
  {
    printf ("Warning: shutdown message timed out.\n");
    if (!ExpectTimeout)
      ExpectTimeout = 1;
  }
  if (Smsg -> sm_Flags & SDMF_CTRL_C)
  {
    /* We didn't receive a timeout yet but a CTRL-C during a
       broadcast is unlikely if there was no timeout ahead.
    */
    if (!ExpectTimeout)
      ExpectTimeout = 1;
  }
  if (BroadcastTimeout)
  {
    if (Smsg -> sm_Flags & SDMF_SIG_MASK)
    {
      RemShutdownPort (Smsg -> sm_Context -> sc_Port);

      while (!(Smsg = AllocShutdownMessage ()))
	Delay (50); /* Better ideas welcome ... */
    }
    else
      AbortIO ((struct IORequest *) TimerReq);
    WaitIO ((struct IORequest *) TimerReq);
    SetSignal (0L, TimerMask);
  }
  if (!point_of_no_return)
  {
    if (Smsg -> sm_Flags & SDMF_CANCEL)
      CancelShutdown ();
  }
}

void
AbortableDelay (int seconds)
{
  ShutdownMessage *sm;
  int abort = 0;
  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)
    {
      do
        WaitPort (ShutdownPort);
      while (!(sm = (ShutdownMessage *) GetMsg (ShutdownPort)));
      if (sm -> sm_Status == SHUTDOWN_ABORT)
	if (point_of_no_return)
	  sm -> sm_Status = SHUTDOWN_UMOUNT;
	else
          abort = 1;
      ReplyMsg ((struct Message *) sm);
    }
    if (mask & ReplyMask) /* late reply */
    {
      if (GetMsg (ReplyPort))
	SContext.sc_TimeOuts --;
    }
    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 && !ExpectTimeout)
	  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 (0);
        }
	if (mask & SIGBREAKF_CTRL_F) /* CTRL-F: very fast reboot */
	{
	  printf ("CTRL-F\n");
	  GoDown (1);
	}
      }
      else
	return;
    }
    if (mask & TimerMask) /* done waiting */
      return;
  }
}

int
main (int argc, char *argv[])
{
  int cancel_flag = 0, abortable_flag = 1;
  int shutdown_time = 10, shutdown_interval;
  int opt;

  bzero (&SContext, sizeof (ShutdownContext));
  NewList (&SContext.sc_List);
  SetTaskPri (FindTask (0L), 10);

  while ((opt = getopt (argc, argv, "chnrs:t:T:")) != -1)
    switch (opt)
    {
      case 'n': abortable_flag = 0; break;
      case 'r': HaltFlag = 0; break;
      case 'h': HaltFlag = 1; break;
      case 't': shutdown_time = atoi (optarg); break;
      case 's': SyncTime = atoi (optarg); break;
      case 'c': cancel_flag = 1; break;
      case 'T': BroadcastTimeout = atoi (optarg); break;
      default:
        printf ("Usage: %s [-chnr] [-t <time>] [-s <sync time>] [-T <timeout>].\n", argv[0]);
	exit (EXIT_FAILURE);
    }

  if (HaltFlag)
    IntuitionBase = (struct IntuitionBase *) OpenLibrary ("intuition.library", 37);
  if (!(HaltBase = OpenLibrary ("halt.library", 0)))
    CleanUp ("Failed to open halt.library.\n");

  if (!(ReplyPort = CreateMsgPort ()))
    CleanUp (CantAllocatePort);
  ReplyMask = 1 << ReplyPort -> mp_SigBit;
  if (!(Smsg = AllocShutdownMessage ()))
    CleanUp (OutOfMemory);
  if (abortable_flag)
    ShutdownFlags = SDMF_ABORTABLE;

  /* open 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");
  Smsg -> sm_Context -> sc_Mask = TimerMask = 1 << TimerPort -> mp_SigBit;

  if (cancel_flag)
  {
    Smsg -> sm_Status = SHUTDOWN_ABORT;
    if (!PutAndGet (Smsg, ShutdownPortname))
      CleanUp ("Shutdown is not running.\n");
    if (Smsg -> sm_Status == SHUTDOWN_UMOUNT)
      CleanUp (SorryTooLate);
    CleanUp (ShutdownCancelled);
  }

  /* don't start shutdown twice */
  Forbid ();
  if (FindPort (ShutdownPortname))
  {
    Permit ();
    CleanUp ("Shutdown is already running.\n");
  }
  Permit ();
  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;
  WaitMask = ReplyMask | ShutdownMask | TimerMask | SIGBREAKF_MASK;

  while (shutdown_time > 5)
  {
    shutdown_interval = shutdown_time / 2;
    printf ("The system is going down in %d seconds.\n", shutdown_time);
    Smsg -> sm_Status   = SHUTDOWN_WARN;
    Smsg -> sm_TimeLeft = shutdown_time;
    BroadcastShutdownMsg ();
    AbortableDelay (shutdown_interval);
    shutdown_time -= shutdown_interval;
  }
  GoDown (0);
}

int
GoDown (int now)
{
  struct EasyStruct es = {
    sizeof (struct EasyStruct), 0, "AmigaDOS", "The system is halted", "Ok"
  };

  if (!now)
  {
    /* last warning */
    Smsg -> sm_Status   = SHUTDOWN_NOW;
    Smsg -> sm_TimeLeft = 5;
    BroadcastShutdownMsg ();
    printf ("The system is going down in 5 seconds.\n");
    AbortableDelay (5);

    /* too late for writing */
    Smsg -> sm_Status   = SHUTDOWN_UMOUNT;
    Smsg -> sm_TimeLeft = 0;
    BroadcastShutdownMsg ();
  }
  point_of_no_return = 1;
  ShutdownFlags &= ~SDMF_ABORTABLE;

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

  if (!now) /* all done, who turns off the light? */
  {
    Smsg -> sm_Status   = SHUTDOWN_HALT;
    Smsg -> sm_TimeLeft = 0;
    BroadcastShutdownMsg ();
  }
  printf ("The system is halted.\n");

  if (IntuitionBase)
    BuildEasyRequest (NULL, &es, NULL);
  Delay (10); /* wait 0.5 seconds */
  Reboot (HaltFlag); /* 0 = reboot, 1 = halt */
}
