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

   ToDo:
     - broadcast message to telnetd (AmiTCP)
*/

#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 "shutdown_inline.h"
#else
#include "shutdown_pragmas.h"
#endif
#include "shutdown.h"

struct IntuitionBase *IntuitionBase = NULL;
struct Library *ShutdownBase = NULL;
ShutdownMessage *Smsg = NULL;
struct timerequest *TimerReq = NULL;
struct MsgPort *TimerPort = NULL;
struct MsgPort *ReplyPort = NULL;
struct MsgPort *ShutdownPort = NULL;
ULONG TimerMask;
ULONG TimerErr = 1;
ULONG ShutdownMask;
ULONG WaitMask;
int point_of_no_return = 0;
char *ShutdownPortname = "Shutdown";
int SpNameLen;
char *CantAllocatePort = "Can't allocate message port.\n";
char *ShutdownCancelled = "shutdown cancelled.\n";
char *OutOfMemory = "Out of memory";
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 (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)
{
  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);
  }
  if (Smsg)
    FreeMem (Smsg, sizeof (ShutdownMessage));
  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;
  if (Smsg)
  {
    Smsg -> sm_Status = SHUTDOWN_ABORT;
    ShutdownStatus (Smsg);
  }
  CleanUp (ShutdownCancelled);
}

void
AbortableDelay (int seconds)
{
  ShutdownMessage *sm;
  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)
        mask |= SIGBREAKF_CTRL_C;
      ReplyMsg ((struct Message *) sm);
    }
    if (mask & SIGBREAKF_CTRL_C)
    {
      if (!point_of_no_return)
      {
        if (!(mask & TimerMask))
        {
          AbortIO ((struct IORequest *) TimerReq);
        }
        WaitIO ((struct IORequest *) TimerReq);
        CancelShutdown ();
      }
    }
    if (mask & TimerMask)
    {
      WaitIO ((struct IORequest *) TimerReq);
      return;
    }
  }
}

int
main (int argc, char *argv[])
{
  struct EasyStruct es = {
    sizeof (struct EasyStruct), 0, "AmigaDOS", "The system is halted", "Ok"
  };
  int halt_flag = 1, cancel_flag = 0;
  int shutdown_time = 10, shutdown_interval;
  int opt, sync_time = 5;

#if !defined (__GNUC__)
  onbreak (CancelShutdown);
#endif
  SetTaskPri (FindTask (0L), 10);

  while ((opt = getopt (argc, argv, "chrs:t:")) != -1)
    switch (opt)
    {
      case 'r': halt_flag = 0; break;
      case 'h': halt_flag = 1; break;
      case 't': shutdown_time = atoi (optarg); break;
      case 's': sync_time = atoi (optarg); break;
      case 'c': cancel_flag = 1; break;
      default:
        printf ("Usage: %s [-chr] [-t <time>] [-s <sync time>].\n", argv[0]);
	exit (EXIT_FAILURE);
    }

  if (halt_flag)
    IntuitionBase = (struct IntuitionBase *) OpenLibrary ("intuition.library", 37);
  if (!(ShutdownBase = OpenLibrary ("shutdown.library", 0)))
    CleanUp ("Failed to open shutdown.library.\n");
  if (!(Smsg = (ShutdownMessage *)
	AllocMem (sizeof (ShutdownMessage), MEMF_PUBLIC)))
    CleanUp (OutOfMemory);
  if (!(ReplyPort = CreateMsgPort ()))
    CleanUp (CantAllocatePort);
  bzero (Smsg, sizeof (ShutdownMessage));
  Smsg -> sm_Msg.mn_ReplyPort = ReplyPort;
  Smsg -> sm_Msg.mn_Length = sizeof (ShutdownMessage);

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

  if (cancel_flag)
  {
    Smsg -> sm_Status = SHUTDOWN_ABORT;
    if (!PutAndGet (Smsg, ShutdownPortname))
      CleanUp ("Shutdown is not running.\n");
    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 = ShutdownMask | TimerMask | SIGBREAKF_CTRL_C;

  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;
    ShutdownStatus (Smsg);
    AbortableDelay (shutdown_interval);
    shutdown_time -= shutdown_interval;
  }

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

  /* unmount filesystems and allow them to flush */
  Smsg -> sm_Status   = SHUTDOWN_UMOUNT;
  Smsg -> sm_TimeLeft = 0;
  ShutdownStatus (Smsg);
  point_of_no_return = 1;
  Unmount (NULL);
  printf ("The system is going down, unmounting filesystems.\n");
  Delay (50 * sync_time);

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

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