Logo Search packages:      
Sourcecode: efax-gtk version File versions

efax_controller.cpp

/* Copyright (C) 2001 to 2005 Chris Vine

This program is distributed under the General Public Licence, version 2.
For particulars of this and relevant disclaimers see the file
COPYING distributed with the source files.

*/

#include <unistd.h>
#include <limits.h>
#include <signal.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <errno.h>

#include <fstream>
#include <iomanip>
#include <ctime>
#include <cstring>
#include <cstdlib>

// uncomment for debugging in EfaxController::timer_event()
//#include <iostream>

#include <glibmm/main.h>
#include <glibmm/convert.h>
#include <glibmm/timer.h>
#include <glibmm/thread.h>

#include "efax_controller.h"

#ifdef HAVE_PTHREAD_SIGMASK 
#include <pthread.h>
#endif

#ifdef HAVE_STRINGSTREAM
#include <sstream>
#else
#include <strstream>
#endif

#ifdef ENABLE_NLS
#include <libintl.h>
#endif

EfaxController::EfaxController(void): child_pid(0), state(inactive),
                              close_down(false) {
  // set up state_messages

  // it's OK to use std::string rather than Glib::ustring here, because
  // the contents are passed to StatusLine::write_status(const char*) and
  // std::string is just a transparent holder of the UTF-8 string
  // until it reaches that method

  state_messages.push_back(gettext("Inactive"));
  state_messages.push_back(gettext("Sending fax"));
  state_messages.push_back(gettext("Answering call"));
  state_messages.push_back(gettext("Answering call"));
  state_messages.push_back(gettext("Standing by to receive calls"));
  state_messages.push_back(gettext("Sending fax"));
  state_messages.push_back(gettext("Sending fax"));

  fax_made_notify.connect(sigc::mem_fun(*this, &EfaxController::sendfax_slot));
  no_fax_made_notify.connect(sigc::mem_fun(*this, &EfaxController::no_fax_made_slot));
}

void EfaxController::efax_closedown(void) {
  if (state == inactive) ready_to_quit_notify();
  else if (!close_down) {
    close_down = true;
    stop_slot();
  }
}

void EfaxController::init_sendfax_parms(void) {

  sendfax_parms_vec = prog_config.parms;

  // now add the first set of arguments to the copy of prog_config.parms
  // in sendfax_parms_vec
     
  struct std::tm* time_p;
  std::time_t time_count;

  std::time(&time_count);
  time_p = std::localtime(&time_count);

  char date_string[23];
  const char format[] = "%a %d-%b-%Y %H:%M";
  std::strftime(date_string, 23, format, time_p);

  std::string temp("-h");
  temp += date_string;
  temp += "    ";
  try {
    temp += Glib::locale_from_utf8(gettext("From"));
    temp += " ";
    temp += Glib::locale_from_utf8(prog_config.my_name);
  }
  catch (Glib::ConvertError&) {
    write_error("UTF-8 conversion error in EfaxController::init_sendfax_parms()\n");
  }
  temp += " (";
  temp += prog_config.my_number + ") --> ";
  temp += last_fax_item_sent.number + "    p.%d/%d";
  sendfax_parms_vec.push_back(temp);

  if (last_fax_item_sent.number.empty()) {
    sendfax_parms_vec.push_back("-jX3");
    sendfax_parms_vec.push_back("-t");
    sendfax_parms_vec.push_back("");
  }
  else {
    temp = "-t";
    if (prog_config.tone_dial) temp += 'T';
    else temp += 'P';
    temp += last_fax_item_sent.number;
    sendfax_parms_vec.push_back(temp);
  }
}

void EfaxController::sendfax(const Fax_item& fax_item) {

  if (state == receive_standby) {
    if (!is_receiving_fax()) {
      last_fax_item_sent = fax_item;
      state = start_send_on_standby;
      kill_child();
    }
    else write_error(gettext("Cannot send fax - a fax is being received\n"));
  }

  // note that this is in an else block - we do not want this
  // tested if state was set to start_send_on_standby above - we 
  // want that test to occur on the call to this method in
  // EfaxController::timer_event()
  else if (state == inactive || state == start_send_on_standby) {
    stdout_pipe.open(Pipe_fifo::non_block);
    // efax is sensitive to timing, so set pipe write to non-block also
    stdout_pipe.make_write_non_block();
    
    if (state == inactive) {
      state = sending;
      last_fax_item_sent = fax_item;
    }
    // else state == start_send_on_standby - we don't need to assign to
    // last_fax_item_sent in this case as it has already been done in the
    // 'if' block opening this function on the previous call to this function
    else state = send_on_standby;

    display_state();

    // get the first set of arguments for the exec() call in sendfax_slot (because
    // this is a multi-threaded program, we must do this before fork()ing because
    // we use functions to get the arguments which are not async-signal-safe)
    // the arguments are loaded into the EfaxController object member sendfax_parms_vec
    init_sendfax_parms();

    // now launch a worker thread to make up the fax pages in tiffg3 format
    // for sending by efax - the thread will emit dispatcher fax_made_notify
    // if the fax is correctly made up, which will call sendfax_slot() in this
    // initial (GUI) thread, which in turn invokes efax and sends the fax.  If
    // the fax is not correctly made up, the worker thread will emit dispatcher
    // no_fax_made_notify, which will call no_fax_made_slot() in this initial
    // (GUI) thread so as to clean up correctly

    // first block off the signals for which we have set handlers so that the worker
    // thread does not receive the signals, otherwise we will have memory synchronisation
    // issues in multi-processor systems - we will unblock in the initial (GUI) thread
    // as soon as the worker thread has been launched
    sigset_t sig_mask;
    sigemptyset(&sig_mask);
    sigaddset(&sig_mask, SIGCHLD);
    sigaddset(&sig_mask, SIGQUIT);
    sigaddset(&sig_mask, SIGTERM);
    sigaddset(&sig_mask, SIGINT);
    sigaddset(&sig_mask, SIGHUP);
    sigaddset(&sig_mask, SIGUSR2);
#ifdef HAVE_PTHREAD_SIGMASK 
    pthread_sigmask(SIG_BLOCK, &sig_mask, 0);
#else
    while (sigprocmask(SIG_BLOCK, &sig_mask, 0) == -1 && errno == EINTR);
#endif
    try {
      Glib::Thread::create(sigc::mem_fun(*this, &EfaxController::make_fax_thread), false);
    }
    catch (Glib::ThreadError&) {
      write_error("Cannot start thread to make fax for sending\n");
      stdout_pipe.close();
      state = inactive;
      display_state();
    }
    // now unblock signals so that the initial (GUI) thread can receive them
#ifdef HAVE_PTHREAD_SIGMASK 
    pthread_sigmask(SIG_UNBLOCK, &sig_mask, 0);
#else
    while (sigprocmask(SIG_UNBLOCK, &sig_mask, 0) == -1 && errno == EINTR);
#endif
  }
  else beep();
}

void EfaxController::no_fax_made_slot(void) {

  // first deal with the synchronising semaphore we posted to in
  // EfaxController::make_fax_thread() - this wait cannot actually block
  // because we have already posted to fax_made_sem in that method
  fax_made_sem.wait();

  cleanup_fax_send_fail();
  stdout_pipe.close();
  State state_val = state;
  state = inactive;
  if (state_val == send_on_standby) receive(receive_standby);
  // we don't need to call display_state() if we have called receive(), as
  // receive() will call display_state() itself
  else display_state();
}

std::pair<const char*, char* const*> EfaxController::get_sendfax_parms(void) {

  // sendfax_parms_vec has already been filled by init_sendfax_parms() and by
  // make_fax_thread() - so make up the C style arrays for the execvp() call
  // in sendfax_slot()
  char** exec_parms = new char*[sendfax_parms_vec.size() + 1];

  std::vector<std::string>::const_iterator iter;
  char**  temp_pp = exec_parms;
  for (iter = sendfax_parms_vec.begin(); iter != sendfax_parms_vec.end(); ++iter, ++temp_pp) {
    *temp_pp = new char[iter->size() + 1];
    std::strcpy(*temp_pp, iter->c_str());
  }

  *temp_pp = 0;

  char* prog_name = new char[std::strlen("efax-0.9a") + 1];
  std::strcpy(prog_name, "efax-0.9a");

  return std::pair<const char*, char* const*>(prog_name, exec_parms);
}

void EfaxController::sendfax_slot(void) {

  // this method runs in the initial (GUI) thread -  we get this process
  // to run in the initial (GUI) thread by invoking it from a Glib::Dispatcher
  // object in EfaxController::make_fax_thread(), which will execute the
  // slot attached to that object (this method) in the thread in which the
  // Glib::Dispatcher object was created.  The Glib::Dispatcher object was
  // created as a member of the EfaxController object, and thus was created
  // in the initial (GUI) thread.  We need to execute this method in the GUI
  // thread because the program calls waitpid() in that thread to examine
  // exit status of the child process this method creates with fork(), and
  // with Linuxthreads this will not otherwise work correctly

  // first deal with the synchronising semaphore we posted to in
  // EfaxController::make_fax_thread() - this wait cannot actually block
  // because we have already posted to fax_made_sem in that method
  fax_made_sem.wait();

  // get the arguments for the exec() call below (because this is a
  // multi-threaded program, we must do this before fork()ing because
  // we use functions to get the arguments which are not async-signal-safe)
  std::pair<const char*, char* const*> sendfax_parms(get_sendfax_parms());

  // set up a synchronising pipe  in case the child process finishes before
  // fork() in parent space has returned (yes, with an exec() error that can
  // happen with Linux kernel 2.6) - this is important because we test child_pid
  // in efax_controller_childexit_handler()
  Sync_pipe sync_pipe;

  child_pid = fork();

  if (child_pid == -1) {
    write_error("Fork error - exiting\n");
    std::exit(FORK_ERROR);
  }
  if (!child_pid) {  // child process - as soon as everything is set up we are going to do an exec()

    // now we have forked, we can connect stdout_pipe to stdout
    // and connect MainWindow::error_pipe to stderr
    stdout_pipe.connect_to_stdout();
    connect_to_stderr();

    // wait before we call execvp() until the parent process has set itself up
    // and releases this process
    sync_pipe.wait();

    execvp(sendfax_parms.first, sendfax_parms.second);

    // if we reached this point, then the execvp() call must have failed
    // report error and exit - uses _exit() and not exit()
    write_error("Can't find the efax-0.9a program - please check your installation\n"
            "and the PATH environmental variable\n");
    _exit(EXEC_ERROR); 
  } // end of child process
    
  // this is the parent process
  stdout_pipe.make_readonly();   // since the pipe is unidirectional, we can close the write fd
  join_child();

  // now we have set up, release the child process
  sync_pipe.release();
    
  // release the memory allocated on the heap for
  // the redundant sendfax_parms_pair
  // we are in the main parent process here - no worries about
  // only being able to use async-signal-safe functions
  delete_parms(sendfax_parms);
}

std::pair<const char*, char* const*> EfaxController::get_gs_parms(const std::string& basename) {

  // lock the Prog_config object to stop it being modified in the intial (GUI) thread
  // while we are accessing it here
  Glib::Mutex::Lock lock(*prog_config.mutex_p);

  std::vector<std::string> parms;
  std::string temp;
  parms.push_back("gs");
  parms.push_back("-q");
  parms.push_back("-sDEVICE=tiffg3");
  temp = "-r";
  temp += prog_config.resolution;
  parms.push_back(temp);
  parms.push_back("-dNOPAUSE");
  parms.push_back("-dSAFER");
  temp = "-sOutputFile=";
  temp += basename + ".%03d";
  parms.push_back(temp);
  temp = "-sPAPERSIZE=";
  temp += prog_config.page_size;
  parms.push_back(temp);
  parms.push_back(basename);
      
  char** exec_parms = new char*[parms.size() + 1];

  std::vector<std::string>::const_iterator iter;
  char**  temp_pp = exec_parms;
  for (iter = parms.begin(); iter != parms.end(); ++iter, ++temp_pp) {
    *temp_pp = new char[iter->size() + 1];
    std::strcpy(*temp_pp, iter->c_str());
  }

  *temp_pp = 0;
  
  char* prog_name = new char[std::strlen("gs") + 1];
  std::strcpy(prog_name, "gs");

  return std::pair<const char*, char* const*>(prog_name, exec_parms);
}

void EfaxController::make_fax_thread(void) {
  // convert the postscript file(s) into tiffg3 fax files, beginning at [filename].001
  // we will use ghostscript.  we will also load the results into sendfax_parms_vec

  std::vector<std::string>::const_iterator filename_iter;

  // we do not need a mutex to protect last_fax_item_sent - until EfaxController::timer_event()
  // resets state to inactive or receive_standby, we cannot invoke sendfax() again
  for (filename_iter = last_fax_item_sent.file_list.begin();
       filename_iter != last_fax_item_sent.file_list.end(); ++filename_iter) {

    std::string::size_type pos = filename_iter->find_last_of('/');

    if (pos == std::string::npos || pos + 2 > filename_iter->size()) {
      try {
      write_error(Glib::locale_from_utf8(gettext("Not valid file name\n")).c_str());
      }
      catch (Glib::ConvertError&) {
      write_error("UTF-8 conversion error in EfaxController::make_fax_thread()\n");
      }

      // synchronise memory on multi-processor systems before we emit the
      // Glib::Dispatcher signal - we call fax_made_sem.wait() in
      // EfaxController::no_fax_made_slot()
      fax_made_sem.post();

      // clean up and then end this worker thread
      no_fax_made_notify();
      return;
    }
    
    else if (access(filename_iter->c_str(), F_OK)) {
      try {
      write_error(Glib::locale_from_utf8(gettext("File does not exist\n")).c_str());
      }
      catch (Glib::ConvertError&) {
      write_error("UTF-8 conversion error in EfaxController::make_fax_thread()\n");
      }

      // synchronise memory on multi-processor systems before we emit the
      // Glib::Dispatcher signal - we call fax_made_sem.wait() in
      // EfaxController::no_fax_made_slot()
      fax_made_sem.post();

      // clean up and then end this worker thread
      no_fax_made_notify();
      return;
    }

    else if (access(filename_iter->c_str(), R_OK)) {
      try {
      write_error(Glib::locale_from_utf8(gettext("User does not have read permission on the file\n")).c_str());
      }
      catch (Glib::ConvertError&) {
      write_error("UTF-8 conversion error in EfaxController::make_fax_thread()\n");
      }

      // synchronise memory on multi-processor systems before we emit the
      // Glib::Dispatcher signal - we call fax_made_sem.wait() in
      // EfaxController::no_fax_made_slot()
      fax_made_sem.post();

      // clean up and then end this worker thread
      no_fax_made_notify();
      return;
    }

    // unfortunately ghostscript does not handle long file names
    // so we need to separate the file name from the full path (we will chdir() to the directory later)
    // pos is already set to the position of the last '/' character
    std::string dirname(filename_iter->substr(0, pos));
    pos++;
    std::string basename(filename_iter->substr(pos));

    // get the arguments for the exec() call below (because this is a
    // multi-threaded program, we must do this before fork()ing because
    // we use functions to get the arguments which are not async-signal-safe)
    std::pair<const char*, char* const*> gs_parms(get_gs_parms(basename));

    // create a synchronising pipe - we need to wait() on gs having completed executing
    // and then notify the parent process.  To wait() successfully we need to fork() once,
    // reset the child signal handler, and then fork() again
    Sync_pipe sync_pipe;

    pid_t pid = fork();
    
    if (pid == -1) {
      write_error("Fork error\n");
      std::exit(FORK_ERROR);
    }
    if (!pid) { // child process

      // unblock signals as these are blocked for all worker threads
      // (the child process inherits the signal mask of the thread
      // creating it with the fork() call)
      sigset_t sig_mask;
      sigemptyset(&sig_mask);
      sigaddset(&sig_mask, SIGCHLD);
      sigaddset(&sig_mask, SIGQUIT);
      sigaddset(&sig_mask, SIGTERM);
      sigaddset(&sig_mask, SIGINT);
      sigaddset(&sig_mask, SIGHUP);
      sigaddset(&sig_mask, SIGUSR2);
      // this child process is single threaded, so we can use sigprocmask()
      // rather than pthread_sigmask() (and should do so as sigprocmask()
      // is guaranteed to be async-signal-safe)
      // this process will not be receiving interrupts so we do not need
      // to test for EINTR on the call to sigprocmask()
      sigprocmask(SIG_UNBLOCK, &sig_mask, 0);

      connect_to_stderr();

      // now fork() again
      pid_t pid = fork();

      if (pid == -1) {
      write_error("Fork error\n");
      _exit(FORK_ERROR); // we have already forked, so use _exit() not exit()
      }
      if (!pid) {  // child process - when everything is set up, we are going to do an exec()

      // we don't need sync_pipe in this process - ignore it by calling release()
      sync_pipe.release();

      // now start up ghostscript
      // first we need to connect stdin to /dev/null to make ghostscript terminate
      // this process will not be receiving interrupts so we do not need
      // to test for EINTR on the calls to open(), dup2() and close() as this
      // process will not be receiving any signals
      int fd = open("/dev/null", O_RDWR);
      if (fd == -1) {
        write_error("Cannot open /dev/null in EfaxController::make_fax_thread()\n");
        // in case of error end child process here
        _exit(FILEOPEN_ERROR);
      }
      dup2(fd, 0);
      // now close stdout
      dup2(fd, 1);
      close(fd); // now stdin and stdout read/write to /dev/null, we can close the /dev/null file descriptor

      // unfortunately ghostscript does not handle long file names
      // so we need to chdir()
      chdir(dirname.c_str());
      execvp(gs_parms.first, gs_parms.second);

      // if we reached this point, then the execvp() call must have failed
      write_error("Can't find the ghostscript program - please check your installation\n"
                "and the PATH environmental variable\n");
      // this child process must end here - use _exit() not exit()
      _exit(EXEC_ERROR); 
      } // end of child process

      // this is the parent process

      // wait until ghostscript has produced the fax tiffg3 fax file.
      // Note that we have already fork()ed so this process will not be
      // receiving signals so we do not need to test for EINTR on the
      // call to wait()
      wait(0);

      // release the waiting parent process
      sync_pipe.release();

      // now end the process - use _exit() not exit()
      _exit(0);
    }
    // wait on sync_pipe until we know gs has made all the files in tiffg3 format
    sync_pipe.wait();

    // release the memory allocated on the heap for
    // the redundant gs_parms
    // we are in the main parent process here - no worries about
    // only being able to use async-signal-safe functions
    delete_parms(gs_parms);

    // now enter the names of the created files in sendfax_parms_vec
    bool valid_file = false;
    int partnumber = 1;
#ifdef HAVE_STRINGSTREAM
    std::ostringstream strm;
    strm << *filename_iter << '.' << std::setfill('0')
       << std::setw(3) << partnumber;
    int result = access(strm.str().c_str(), R_OK);
    
    while (!result) {  // file OK
      // valid_file only needs to be set true once, but it is more
      // convenient to do it in this loop
      valid_file = true;

      // we do not need a mutex to protect sendfax_parms_vec - until
      // EfaxController::timer_event() resets state to inactive or
      // receive_standby, we cannot invoke sendfax() again
      sendfax_parms_vec.push_back(strm.str());

      partnumber++;
      strm.str("");
      strm << *filename_iter << '.' << std::setfill('0')
         << std::setw(3) << partnumber;
      result = access(strm.str().c_str(), R_OK);
    }
#else
    std::ostrstream strm;
    strm << filename_iter->c_str() << '.' << std::setfill('0')
       << std::setw(3) << partnumber << std::ends;
    const char* test_name = strm.str();
    int result = access(test_name, R_OK);
  
    while (!result) {  // file OK
      // valid_file only needs to be set true once, but it is more
      // convenient to do it in this loop
      valid_file = true;

      sendfax_parms_vec.push_back(test_name);
      delete[] test_name;

      partnumber++;
      std::ostrstream strm;
      strm << filename_iter->c_str() << '.' << std::setfill('0')
         << std::setw(3) << partnumber << std::ends;
      test_name = strm.str();
      result = access(test_name, R_OK);
    }
    delete[] test_name;
#endif  
    
    if (!valid_file) {
      try {
      write_error(Glib::locale_from_utf8(gettext("Not valid postscript file\n")).c_str());
      }
      catch (Glib::ConvertError&) {
      write_error("UTF-8 conversion error in EfaxController::make_fax_thread()\n");
      }

      // synchronise memory on multi-processor systems before we emit the
      // Glib::Dispatcher signal - we call fax_made_sem.wait() in
      // EfaxController::no_fax_made_slot()
      fax_made_sem.post();

      // clean up and then end this worker thread
      no_fax_made_notify();
      return;
    }
  }
  // we have now made the fax pages in tiffg3 format and entered them into sendfax_parms_vec.
  // At the end of this method this worker thread will end, and emitting fax_made_notify
  // will cause sendfax_slot() to be executed in the initial (GUI) thread

  // synchronise memory on multi-processor systems before we emit the
  // Glib::Dispatcher signal - we call fax_made_sem.wait() in
  // EfaxController::sendfax_slot()
  fax_made_sem.post();

  fax_made_notify();
}

std::pair<const char*, char* const*> EfaxController::get_receive_parms(int mode) {

  std::vector<std::string> efax_parms(prog_config.parms);

  std::string temp("-r");
  temp += prog_config.receive_dirname;
  efax_parms.push_back(temp);

  if (mode == receive_takeover) efax_parms.push_back("-w");
      
  else if (mode == receive_standby) {
    temp = "-jS0=";
    temp += prog_config.rings;
    efax_parms.push_back(temp);
    efax_parms.push_back("-s");
    efax_parms.push_back("-w");
  }
      
  char** exec_parms = new char*[efax_parms.size() + 1];

  std::vector<std::string>::const_iterator iter;
  char**  temp_pp = exec_parms;
  for (iter = efax_parms.begin(); iter != efax_parms.end(); ++iter, ++temp_pp) {
    *temp_pp = new char[iter->size() + 1];
    std::strcpy(*temp_pp, iter->c_str());
  }
  
  *temp_pp = 0;

  char* prog_name = new char[std::strlen("efax-0.9a") + 1];
  std::strcpy(prog_name, "efax-0.9a");
  
  return std::pair<const char*, char* const*>(prog_name, exec_parms);
}

void EfaxController::receive(State mode) {

  if (state != inactive) beep();

  else {
    // get a time value to create the directory name into which the fax is to be saved
    // and to insert in prog_config.receive_dirname for parent and child
    struct std::tm* time_p;
    std::time_t time_count;
    
    std::time(&time_count);
    time_p = std::localtime(&time_count);

    std::string fax_pathname;
    if (!prog_config.working_dir.empty()) {
      fax_pathname = prog_config.working_dir;
      fax_pathname += "/faxin/";
    }
    else {
      write_error("You don't have the $HOME environmental variable set.\n"
              "You may need to search for the directory in which\n"
              "the received fax is saved, and it probably won't\n"
              "appear in the fax list!\n");
    }

    const char format[] = "%y%m%d%H%M%S";
    {
      // lock the Prog_config object while we modify it
      Glib::Mutex::Lock lock(*prog_config.mutex_p);
      std::strftime(prog_config.receive_dirname, MAX_TEMP_NAME + 1, format, time_p);
    }
    std::string temp(fax_pathname);
    temp += prog_config.receive_dirname;

    // check whether directory already exists or can't be created
    int count;
    for (count = 0; count < 4 && mkdir(temp.c_str(), S_IRUSR | S_IWUSR | S_IXUSR); count++) {
      // it is not worth checking for a short sleep because of a signal as we will
      // repeat the attempt four times anyway
      sleep(1); // wait a second to get a different time
      std::time(&time_count);
      time_p = std::localtime(&time_count);
      {
      // lock the Prog_config object while we modify it
      Glib::Mutex::Lock lock(*prog_config.mutex_p);
      std::strftime(prog_config.receive_dirname, MAX_TEMP_NAME + 1, format, time_p);
      }
      temp = fax_pathname + prog_config.receive_dirname;
    }
    if (count == 4) {
      write_error("Can't create directory to save fax\n");
      {
      // lock the Prog_config object while we modify it
      Glib::Mutex::Lock lock(*prog_config.mutex_p);
      *prog_config.receive_dirname = 0;
      }
      return;
    }

    fax_pathname += prog_config.receive_dirname;

    stdout_pipe.open(Pipe_fifo::non_block);
    // efax is sensitive to timing, so set pipe write to non-block also
    stdout_pipe.make_write_non_block();
    
    state = mode;
    display_state();

    // get the arguments for the exec() call below (because this is a
    // multi-threaded program, we must do this before fork()ing because
    // we use functions to get the arguments which are not async-signal-safe)
    std::pair<const char*, char* const*> receive_parms(get_receive_parms(mode));

    // set up a synchronising pipe  in case the child process finishes before
    // fork() in parent space has returned (yes, with an exec() error that can
    // happen with Linux kernel 2.6) - this is important because we test child_pid
    // in efax_controller_childexit_handler()
    Sync_pipe sync_pipe;

    child_pid = fork();
    if (child_pid == -1) {
      write_error("Fork error - exiting\n");
      std::exit(FORK_ERROR);
    }
    if (!child_pid) {  // child process - as soon as everything is set up we are going to do an exec()

      // now we have forked, we can connect stdout_pipe to stdout
      // and connect MainWindow::error_pipe to stderr
      stdout_pipe.connect_to_stdout();
      connect_to_stderr();

      chdir(fax_pathname.c_str());

      // wait before we call execvp() until the parent process has set itself up
      // and releases this process
      sync_pipe.wait();

      // now start up efax in receive mode
      execvp(receive_parms.first, receive_parms.second);

      // if we reached this point, then the execvp() call must have failed
      // report the error and end this process - use _exit() and not exit()
      write_error("Can't find the efax-0.9a program - please check your installation\n"
              "and the PATH environmental variable\n");
      _exit(EXEC_ERROR); 
    } // end of child process

    // this is the parent process
    stdout_pipe.make_readonly();   // since the pipe is unidirectional, we can close the write fd
    join_child();

    // now we have set up, release the child process
    sync_pipe.release();

    // release the memory allocated on the heap for
    // the redundant receive_parms
    // we are in the main parent process here - no worries about
    // only being able to use async-signal-safe functions
    delete_parms(receive_parms);
  }
}

void EfaxController::delete_parms(std::pair<const char*, char* const*> parms_pair) {

  delete[] parms_pair.first;
  char* const* temp_pp = parms_pair.second;
  for(; *temp_pp != 0; ++temp_pp) {
    delete[] *temp_pp;
  }
  delete[] parms_pair.second;
}

bool EfaxController::read_pipe_slot(Glib::IOCondition) {

  char pipe_buffer[PIPE_BUF + 1];
  ssize_t result;

  while ((result = stdout_pipe.read(pipe_buffer, PIPE_BUF)) > 0) {
    pipe_buffer[result] = 0;
    stdout_message(pipe_buffer);
  }
  if (result == -1                             // as this is a non-blocking pipe, result should
      && (errno == EAGAIN || errno == EINTR)) {// end up being -1 with errno == EAGAIN
    return true;                               // retain SigC connection
  }
  return false; // something has gone wrong (probably the pipe has been destroyed), so disconnect
}

void EfaxController::stop_slot(void) {
 if (state != inactive) {
   stdout_message(gettext("\n*** Stopping send/receive session ***\n\n"));
   kill_child();
 }
  else beep();
}

void EfaxController::timer_event(void) {

  int stat_val;
  pid_t result;

  // reap the status of any exited child processes
  while ((result = waitpid(-1, &stat_val, WNOHANG)) > 0) {

    if (result && result == child_pid) {

      child_pid = 0;

      int exit_code = -1;
      if (WIFEXITED(stat_val)) exit_code = WEXITSTATUS(stat_val);

      // this won't work if efax is suid root and we are not root
      if (!prog_config.lock_file.empty()) unlink(prog_config.lock_file.c_str());
    
      bool restart_standby = false;
      bool end_receive = false;

      switch(state) {

      case EfaxController::sending:
      case EfaxController::send_on_standby:
      if (!exit_code) {
        save_sent_fax();
        if (last_fax_item_sent.is_socket_file) {
          // if we are sending a print job via the socket server, notify the
          // Socket_server object which created the temporary file (the server
          // object will clean up by deleting it and also cause the socket
          // file list to be updated - we do not need to do that here)
          remove_from_socket_server_filelist(last_fax_item_sent.file_list[0]);
        }
      }
      else cleanup_fax_send_fail();
      unjoin_child();
      {
        State state_val = state;
        state = inactive;
        if (state_val == send_on_standby
            && !close_down) {
          receive(receive_standby);
        }
        // we do not need to call display_state() if we have called receive(), as
        // receive() will call display_state() itself
        else display_state();
      }
      break;
      case EfaxController::start_send_on_standby:
      clear_receive_dirname();
      unjoin_child();
      if (!close_down) sendfax(last_fax_item_sent);
      else {
        state = inactive;
        display_state();
      }
      break;
      case EfaxController::receive_standby:
      if ((!exit_code || exit_code == 3)
          && !close_down) {
        restart_standby = true;
      }
      else end_receive = true;
      break;
      default:
      if (!inactive) end_receive = true;
      break;
      }

      if (end_receive || restart_standby) {
      clear_receive_dirname();
      unjoin_child();
      state = inactive; // this is needed even if we are going to call receive()
      // now restart if in standby mode
      if (restart_standby) receive(receive_standby);
      // we do not need to call display_state() if we have called receive(), as
      // receive() will call display_state() itself
      else display_state();
      restart_standby = false;
      end_receive = false;
      }

      if (close_down && state == inactive) ready_to_quit_notify();
    }
    /* uncomment for debugging
    else {
      std::cout << "Not efax_controller child" << std::endl;
      std::cout << "Child pid is " << result << std::endl;
      std::cout << "Child exit code is " << exit_code << std::endl;
    }
    */
  }
}

void EfaxController::clear_receive_dirname(void) {
  if (*prog_config.receive_dirname) {
    // now delete receive_dirname if it is empty
    std::string full_dirname;
    if (!prog_config.working_dir.empty()) {
      full_dirname = prog_config.working_dir;
      full_dirname += "/faxin/";
    }
    full_dirname += prog_config.receive_dirname;
    int result = rmdir(full_dirname.c_str()); // only deletes the directory if it is empty

    if (result == -1 && errno == ENOTEMPTY) {
      // before assuming a fax has been successfully received, check to see
      // if [fax_name].001 is of 0 size (if it is delete it)
      struct stat statinfo;
      std::string file_name(full_dirname);
      file_name += '/';
      file_name += prog_config.receive_dirname;
      file_name += ".001";
      if (!stat(file_name.c_str(), &statinfo)
        && !statinfo.st_size) {
      unlink(file_name.c_str());
      rmdir(full_dirname.c_str());
      }
      else fax_received_notify();
    }
    // lock the Prog_config object while we modify it
    Glib::Mutex::Lock lock(*prog_config.mutex_p);
    *prog_config.receive_dirname = 0;
  }
}

void EfaxController::join_child(void) {
  // we do not need a mutex for this - join_child() and unjoin_child() are
  // only called in the initial (GUI) thread
  stdout_connection = Glib::signal_io().connect(sigc::mem_fun(*this, &EfaxController::read_pipe_slot),
                                    stdout_pipe.get_read_fd(), Glib::IO_IN);
}

void EfaxController::unjoin_child(void) {
  // we do not need a mutex for this - join_child() and unjoin_child() are
  // only called in the initial (GUI) thread, and stdout_pipe is only manipulated
  // in that thread or after a new single-threaded process has been launched with fork()
  stdout_pipe.close();
  stdout_connection.disconnect();
}

void EfaxController::kill_child(void) {
  if (child_pid) {
    kill(child_pid, SIGTERM);
    // one second delay
    Glib::usleep(1000000);
    // now really make sure
    // (we don't need to check child_pid again in the implementation as at
    // version 2.2.12 because the child exit handling is in this thread, via
    // MainWindow::timer_event_handler() and EfaxController::timer_event()
    // but we would need to check it again (and protect it with a mutex) if
    // the program is modified to put child exit handling in a signal handling
    // thread waiting on sigwait() which resets the value of child_pid)
    kill(child_pid, SIGKILL);
  }
}

void EfaxController::save_sent_fax(void) {
  
  // get a time value to create the directory name into which the fax is to be saved
  struct std::tm* time_p;
  std::time_t time_count;
    
  std::time(&time_count);
  time_p = std::localtime(&time_count);

  // now create the directory into which the fax files to be saved
  std::string fileout_name;
  if (!prog_config.working_dir.empty()) {
    fileout_name = prog_config.working_dir;
    fileout_name += "/faxsent/";
  }
  else {
    write_error("You don't have the $HOME environmental variable set.\n"
            "You may need to search for the directory in which\n"
            "the fax is saved, and it probably won't appear in\n"
            "the fax list!\n");
  }

  const char dirname_format[] = "%y%m%d%H%M%S";
  char top_dirname[MAX_TEMP_NAME + 1];
  std::strftime(top_dirname, MAX_TEMP_NAME + 1, dirname_format, time_p);
  std::string temp(fileout_name);
  temp += top_dirname;

  // check whether directory already exists or can't be created
  int count;
  for (count = 0; count < 4 && mkdir(temp.c_str(), S_IRUSR | S_IWUSR | S_IXUSR); count++) {
    // it is not worth checking for a short sleep because of a signal as we will
    // repeat the attempt four times anyway
    sleep(1); // wait a second to get a different time
    std::time(&time_count);
    time_p = std::localtime(&time_count);
    std::strftime(top_dirname, MAX_TEMP_NAME + 1, dirname_format, time_p);
    temp = fileout_name + top_dirname;
  }
  if (count == 4) {
    write_error("Can't create directory to save fax\n");
    return;
  }

  // now make fileout_name the same as the directory name for the saved faxes
  // that we have just created
  fileout_name += top_dirname;
  const std::string fileout_dirname(fileout_name); // keep for later to enter a description

  // and now complete the unsuffixed file name of the destination files
  fileout_name += '/';
  fileout_name += top_dirname;

  // now create a string containing the date for the fax description
  // glibc has const struct tm* as last param of strftime()
  // but the const does not appear to be guaranteed by POSIX
  // so do localtime() again just in case
  time_p = std::localtime(&time_count);
  const char date_description_format[] = "(%H%M %Z %d %b %Y)";
  const int max_description_datesize = 126;
  char date_description[max_description_datesize];
  std::strftime(date_description, max_description_datesize, date_description_format, time_p);

  // now copy files into the relevant directory for the fax pages to be saved

  std::vector<std::string> file_basename_vec; // this is used in making the fax description
  int out_partnumber = 1; // in order to generate the suffix for each fax page as saved

  std::vector<std::string>::const_iterator sentname_iter;
  for (sentname_iter = last_fax_item_sent.file_list.begin();
       sentname_iter != last_fax_item_sent.file_list.end(); ++sentname_iter) {

    std::string::size_type pos = sentname_iter->find_last_of('/');
    if (pos == std::string::npos || pos + 2 > sentname_iter->size()) {
      write_error("Not valid file name to save -- can't save sent fax\n");
    }

    else {
      // save the file base name for later use in making the fax description
      pos++;
      file_basename_vec.push_back(sentname_iter->substr(pos));

      // make the suffixes
      int in_partnumber = 1;
#ifdef HAVE_STRINGSTREAM
      std::ostringstream in_suffix_strm;
      in_suffix_strm << '.' << std::setfill('0')
                 << std::setw(3) << in_partnumber;

      std::ostringstream out_suffix_strm;
      out_suffix_strm << '.' << std::setfill('0')
                  << std::setw(3) << out_partnumber;

      // make the suffixed source and destination files
      std::string suffixed_inname = *sentname_iter + in_suffix_strm.str();
      std::string suffixed_outname = fileout_name + out_suffix_strm.str();

#else
      std::ostrstream in_suffix_strm;
      in_suffix_strm << '.' << std::setfill('0')
                 << std::setw(3) << in_partnumber << std::ends;
      const char* in_suffix = in_suffix_strm.str();

      std::ostrstream out_suffix_strm;
      out_suffix_strm << '.' << std::setfill('0')
                  << std::setw(3) << out_partnumber << std::ends;
      const char* out_suffix = out_suffix_strm.str();

      // make the suffixed source and destination files
      std::string suffixed_inname = *sentname_iter + in_suffix;
      std::string suffixed_outname = fileout_name + out_suffix;

      delete[] in_suffix;
      delete[] out_suffix;
#endif

      std::ifstream filein;
      std::ofstream fileout;
      const int BLOCKSIZE = 1024;
      char block[BLOCKSIZE];

      while (!access(suffixed_inname.c_str(), R_OK)
           && (filein.open(suffixed_inname.c_str(), std::ios::in), filein) // use comma operator to check filein
           && (fileout.open(suffixed_outname.c_str(), std::ios::out), fileout)) { // ditto for fileout
      while (filein) {
        filein.read(block, BLOCKSIZE);
        fileout.write(block, filein.gcount());
      }
      filein.close();
      filein.clear();
      fileout.close();
      fileout.clear();
      unlink(suffixed_inname.c_str());
     
      in_partnumber++;
      out_partnumber++;
#ifdef HAVE_STRINGSTREAM
      in_suffix_strm.str("");
      in_suffix_strm << '.' << std::setfill('0')
                   << std::setw(3) << in_partnumber;

      out_suffix_strm.str("");
      out_suffix_strm << '.' << std::setfill('0')
                  << std::setw(3) << out_partnumber;

      // make the suffixed source and destination files
      suffixed_inname = *sentname_iter + in_suffix_strm.str();
      suffixed_outname = fileout_name + out_suffix_strm.str();

#else
      std::ostrstream in_suffix_strm;
      in_suffix_strm << '.' << std::setfill('0')
                   << std::setw(3) << in_partnumber << std::ends;
      in_suffix = in_suffix_strm.str();

      std::ostrstream out_suffix_strm;
      out_suffix_strm << '.' << std::setfill('0')
                  << std::setw(3) << out_partnumber << std::ends;
      out_suffix = out_suffix_strm.str();

      // make the suffixed source and destination files
      suffixed_inname = *sentname_iter + in_suffix;
      suffixed_outname = fileout_name + out_suffix;

      delete[] in_suffix;
      delete[] out_suffix;
#endif
      }
      fileout.clear();
      if (in_partnumber < 2) write_error("There was a problem saving all or part of the sent fax\n");
    }
  }

  // now save the sent fax description
  if (out_partnumber > 1) {
    const std::string description_filename(fileout_dirname + "/Description");
    std::ofstream fileout(description_filename.c_str(), std::ios::out);
    if (fileout) {
      if (last_fax_item_sent.is_socket_file) fileout << gettext("PRINT JOB");
      else {
      std::vector<std::string>::const_iterator filename_iter;
      for (filename_iter = file_basename_vec.begin();
           filename_iter != file_basename_vec.end(); ++filename_iter) {
      
        if (filename_iter != file_basename_vec.begin()) fileout << '+';
        fileout << *filename_iter;
      }
      }
      if (!last_fax_item_sent.number.empty()) {
      fileout << " --> " << last_fax_item_sent.number;
      }
      fileout << ' ' << date_description;
    }
    else write_error("Cannot save sent fax description\n");
  }
}

void EfaxController::cleanup_fax_send_fail(void) {

  std::vector<std::string>::const_iterator filename_iter;
  for (filename_iter = last_fax_item_sent.file_list.begin();
       filename_iter != last_fax_item_sent.file_list.end(); ++filename_iter) {

    // make the suffix
    int partnumber = 1;
#ifdef HAVE_STRINGSTREAM
    std::ostringstream strm;
    strm << '.' << std::setfill('0') << std::setw(3) << partnumber;

    // make the suffixed file name
    std::string filename = *filename_iter + strm.str();

    while (!access(filename.c_str(), R_OK)) {
      unlink(filename.c_str());
      partnumber++;
      strm.str("");
      strm << '.' << std::setfill('0') << std::setw(3) << partnumber;
      filename = *filename_iter + strm.str();
    }
#else
    std::ostrstream strm;
    strm << '.' << std::setfill('0') << std::setw(3) << partnumber << std::ends;
    const char* suffix = strm.str();

    // make the suffixed file name
    std::string filename = *filename_iter + suffix;
    delete[] suffix;

    while (!access(filename.c_str(), R_OK)) {
      unlink(filename.c_str());
      partnumber++;
      std::ostrstream strm;
      strm << '.' << std::setfill('0') << std::setw(3) << partnumber << std::ends;
      suffix = strm.str();
      filename = *filename_iter + suffix;
      delete[] suffix;
    }
#endif
  }
}

bool EfaxController::is_receiving_fax(void) const {

  bool return_val = false;
  if (*prog_config.receive_dirname) {

    std::string file_name(prog_config.working_dir);
    file_name += "/faxin/";
    file_name += prog_config.receive_dirname;
    file_name += '/';
    file_name += prog_config.receive_dirname;
    file_name += ".001";

    struct stat statinfo;
    if (!stat(file_name.c_str(), &statinfo)) return_val = true;
  }
  return return_val;
}

Generated by  Doxygen 1.6.0   Back to index