static const char* version_id =
  "$Id: autoreply.cc,v 1.11 1999/11/01 21:53:40 v2kea318 Exp $";

// $Log: autoreply.cc,v $
// Revision 1.11  1999/11/01  21:53:40  v2kea318
// Simplify the construction and disection of the FIFO file name.
//
// Revision 1.10  1999/11/01  19:20:16  v2kea318
// Use the Thread::cancel method to kill the monitor thread.
//
// Revision 1.9  1999/10/28  17:43:07  v2kea318
// Before checking that the FIFO was opened, ensure that loop is still
// true.  If we kill the thread and remove the FIFO, the open statement
// will return with autoreply in an error state, which means we will
// throw an exception.  This is not good if we are shutting down.  So
// we tolerate the error by breaking out of the loop and ending peacefully.
//
// Revision 1.8  1999/10/26  21:48:57  v2kea318
// Fix the signal handling in the thread routine.  Setting the thread
// to use the default handler for SIGTERM defeated the design of the signal
// handlers in the main msgd thread, which is to handle this signal and
// clean up.  If the autoreply_monitor thread is on the processor at the
// time the signal is raised, the default handler is used, not the other
// thread's special handler.  Now we kill with SIGUSR1, handling this
// signal by setting a flag indicating we are not to continue.  This actually
// doesn't kill the thread; we're still blocking until the open of the
// FIFO is fulfilled.  I really should figure out how to truly kill the
// thread.  But, right now, the thread is only killed at termination, so
// it's not a big deal.  The daemon now correctly cleans up after itself.
//
// Revision 1.7  1999/10/22  20:50:46  v2kea318
// Use the OO Thread interface for thread things.
//
// Revision 1.6  1999/10/21  14:17:50  v2kea318
// Get the userid (user name) via the passwd structure returned by
// getpwuid rather than from an environment variable.
//
// Revision 1.5  1999/10/21  14:03:49  v2kea318
// Use the ANSI standard remove function rather than the POSIX standard
// unlink to remove the FIFO file in cleanup().
//
// Revision 1.4  1999/10/19  20:08:48  v2kea318
// Read the autoreply message string using the read_all function from
// misc-io.h
//
// Revision 1.3  1999/10/14  16:41:47  v2kea318
// Use the pseudo machine independant thread.h header to define all
// thread related things.
//
// Revision 1.2  1999/10/06  14:27:00  v2kea318
// Do the file creation and changing in the internal routine file.  This will
// apply whatever function is supplied, e.g. mkdir and mkfifo.  If the
// system call fails and errno is EEXIST, i.e. the file was not created because
// it already exists, the file routine will do a chmod on the file to ensure
// the file has only the desired permissions.  The cleanup method was also
// added.  The destructor now calls cleanup().
//
// Revision 1.1  1999/10/05  15:13:36  v2kea318
// Initial revision
//
//***************************************************************************

#include "autoreply.h"
#include <fstream>
#include <sys/stat.h>
#include <pwd.h>
#include <unistd.h>
#include <cerrno>
#include <cstdlib>
#include <stdexcept>
#include <csignal>
#include <cstdio>  // for the remove function.
#include "misc-io.h"

//***************************************************************************

MutexRep AutoReply::mutex;
Pointer<Thread> AutoReply::monitor_thread_obj(NULL);
string AutoReply::fifo_name;
bool AutoReply::reply = false;
string AutoReply::reply_string;

//***************************************************************************

static void internal_error(const string& desc)
{
  extern char* sys_errlist[];
  string errno_d = sys_errlist[errno];
  throw runtime_error(desc + string(": ") + errno_d);
}

//***************************************************************************

typedef int (*FileBuilder)(const char* path, mode_t mode);
static void file(const string& fname, mode_t prot, FileBuilder build,
		 const string& desc)
{
  int rc = build(fname.c_str(), prot);
  if (rc != 0) { // took an error.
    if (errno == EEXIST)  /* file already exists, so do a chmod to make
			     sure we have the write protections. */
      file(fname, prot, chmod, "Unable to chmod");
    else
      internal_error(desc);
  }
}

//***************************************************************************

AutoReply::AutoReply() 
{
  /* The auto reply mechanism will talk with the outside world via a
     FIFO.  The class constructor must build the FIFO. */
  fifo_name = string("/tmp/") + getpwuid(getuid())->pw_name 
    + "/.msg_autoreply";

  // make sure the directory is there.
  string dirname(fifo_name.begin(), fifo_name.rfind('/'));
  file(dirname, 0700, mkdir, "Couldn't create directory for FIFO");

  // now build the FIFO.
  file(fifo_name, 0600, mkfifo, "Couldn't create FIFO");
}

//***************************************************************************

AutoReply::~AutoReply()
{
  cleanup();
}

//***************************************************************************

void AutoReply::start_autoreply_monitor_thread()
{
  (monitor_thread_obj = Thread::create())->start(autoreply_monitor_thread, 
						 NULL);
}

//***************************************************************************

void* AutoReply::autoreply_monitor_thread(void*)
{
  // infinite loop
  while (true) {
    /* Open the FIFO.  The properties of a FIFO ensure that this open will
       block until there is something to read. */
    ifstream autoreply(fifo_name.c_str());
    if (!autoreply) internal_error("Unable to open FIFO");

    // read the command from the FIFO, a single word.
    string command;
    autoreply >> command;
    
    // handle it.
    if (command == "AUTOREPLY_SET") {
      // grab the mutex.
      MutexRep m(mutex);

      // setting the string, so read it.
      reply_string = "";
      autoreply.get();  // skip first character
      read_all(autoreply, reply_string);
      cout << "read string " << reply_string << endl;

      // set the flag.
      reply = true;
    } else if (command == "AUTOREPLY_UNSET") {
      MutexRep m(mutex);
      reply_string = "";
      reply = false;
    } else { // invalid command.
      cerr << "unrecognized AUTOREPLY command: " << command << endl;
    }
  }

  // return value.
  return NULL;
}

//***************************************************************************

void AutoReply::kill_monitor_thread()
{
  monitor_thread_obj->cancel();
  monitor_thread_obj = Pointer<Thread>(NULL);
}

//***************************************************************************

string AutoReply::message() const 
{
  /* We must return a copy of the string because we don't want the monitor
     thread to change the string as we are returning it.  So, grab the
     mutex, copy the string, and return the copy. */
  MutexRep m(mutex);
  string cp_reply_string(reply_string);
  return cp_reply_string;
}

//***************************************************************************

AutoReply::operator bool() const 
{
  MutexRep m(mutex);
  bool r = reply;
  return r;
}

//***************************************************************************

void AutoReply::cleanup()
{
  // kill the monitor thread.
  if (monitor_thread_obj) kill_monitor_thread();

  // remove the FIFO.
  remove(fifo_name.c_str());
}

