-#include "options.h"
+#define _XOPEN_SOURCE 700
+
+/**
+ * @file options.c
+ * @purpose Implementations of functions for populating Options struct
+ * Also implements helpers and minor cleanup functions
+ */
+
+// --- external includes --- //
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
-#include "log.h"
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
-#include "daemon.h"
#include <signal.h>
+#include <sys/wait.h>
+
+// --- local includes --- //
+#include "options.h"
+#include "log.h"
+#include "daemon.h"
-void remove_command()
+/**
+ * @funct cleanup_command
+ * @purpose Removes the COMMAND_FILE used for one off command parsing
+ * Added to exit table only if needed
+ */
+void cleanup_command()
{
remove(COMMAND_FILE);
-
}
-void remove_daemon()
+/**
+ * @funct cleanup_daemon
+ * @purpose Removes files and fifo's used by daemon
+ * Added to exit table only if needed
+ */
+void cleanup_daemon()
{
remove(DAEMON_PID_FILE);
remove(DAEMON_FIFO);
remove(DAEMON_BARRIER_FIFO);
}
-void close_err()
-{
- fclose(stderr);
-}
-void close_out()
+/**
+ * @funct Initialise
+ * @purpose Setup default options, call parse_args, do basic setup and initialisation
+ * @param argc - From main
+ * @param argv - From main
+ * @param o - Options struct to setup
+ */
+void options_setup(int argc, char ** argv, Options * o)
{
- fclose(stdout);
-}
-
-char name[BUFSIZ];
+ srand(time(NULL)); // rand is used in a few places, so seed the RNG
-void Initialise(int argc, char ** argv, Options * o)
-{
- srand(time(NULL));
+ // Setup default options
o->program = argv[0];
o->master_addr = NULL;
- o->shell = "bash"; // choosing other shells seems to not work... for some reason
+ o->shell = "bash"; // choosing other shells will not end well
o->logfile = NULL;
o->outfile = NULL;
- o->verbosity = 2;
+ o->verbosity = LOGNOTE; // default log level
o->port = 0;
o->append = NULL;
o->prepend = NULL;
- o->end = "\a\a\a";
o->nCPU = sysconf( _SC_NPROCESSORS_ONLN );
o->daemon = false;
- o->encrypt = true;
o->interactive = true;
+ o->encrypt = true;
- gethostname(name, sizeof(name));
- o->name = strdup(name);
+ // Parse arguments
+ options_parse_args(argc, argv, o);
- o->master_pid = getpid();
+ // Initialisation behaviour that depends on option values
- ParseArguments(argc, argv, o);
-
- o->endlen = (o->end != NULL) ? strlen(o->end) : 0;
-
- if (!o->daemon)
+ if (!o->daemon) // if we are not a daemon...
{
FILE * f = fopen(DAEMON_PID_FILE, "r");
if (f != NULL)
{
+ //... but the DAEMON_PID_FILE exists ...
int daemon_pid;
- fscanf(f, "%d", &daemon_pid);
+ fscanf(f, "%d", &daemon_pid); // get its PID
fclose(f);
- if (kill(daemon_pid, 0) != 0)
+ if (kill(daemon_pid, 0) != 0) // Check it's running
{
error("Initialise", "There was a daemon [%d] running here, but it's gone for some reason.", daemon_pid);
}
+ // We are a daemon wrapper
o->daemon_wrapper = true;
}
}
else
+ {
+ // We are not a daemon wrapper
o->daemon_wrapper = false;
+ }
if (o->logfile != NULL)
{
- if (o->logfile[0] == '+')
+ // A logfile was given; freopen on stderr
+ if (o->logfile[0] == '+') // option to append
freopen(o->logfile+1, "a", stderr);
else
freopen(o->logfile, "w", stderr);
setbuf(stderr, NULL);
- atexit(close_err);
}
if (o->outfile != NULL)
{
- if (o->outfile[0] == '+')
+ // An outfile was given; freopen on stdout
+ if (o->outfile[0] == '+') // option to append
freopen(o->outfile+1, "a", stdout);
else
freopen(o->outfile, "w", stdout);
- atexit(close_out);
}
- if (o->verbosity >= 3)
+ // If the verbosity is high enough, display a welcome message
+ if (o->verbosity >= LOGINFO)
{
char buffer[BUFSIZ]; getcwd(buffer, BUFSIZ);
char * type = (o->daemon) ? "daemon" : (o->daemon_wrapper) ? "wrapper" : (o->master_addr != NULL) ? "slave" : "interactive";
- log_print(3, "Initialise", "Directory %s; type of instance: %s", buffer, type);
+ log_print(LOGINFO, "options_setup", "Directory %s; type of instance: %s", buffer, type);
}
+
+ // If we are a daemon wrapper, we can't be interactive
+ // This is purely to avoid confusing people; it is trivial to be both a daemon wrapper and interactive
+ // but they'll never see any outputs
if (o->daemon_wrapper && o->interactive)
{
- log_print(1, "Initialise", "There is a daemonised swarm [%d] running in this directory.");
- log_print(1, "Initialise", "You can only pass commands to the daemon by invoking %s -c [command]", options.program);
- log_print(1, "Initialise", "Running `swarm -c \"#EXIT#\"' will quit the daemon, unless it is waiting on a BARRIER");
- error("Initialise", "Can't run an interactive wrapper to a daemon");
+ log_print(LOGWARN, "options_setup", "There is a daemonised swarm [%d] running in this directory.");
+ log_print(LOGWARN, "options_setup", "You can only pass commands to the daemon by invoking %s -c [command]", o->program);
+ log_print(LOGWARN, "options_setup", "Running `swarm -c \"#EXIT#\"' will quit the daemon, unless it is waiting on a BARRIER");
+ error("options_setup", "Can't run an interactive wrapper to a daemon");
}
}
-void ParseArguments(int argc, char ** argv, Options * o)
+/**
+ * @funct options_parse_args
+ * @purpose Parse arguments and setup options accordingly
+ * @param argc - From main via Initialise
+ * @param argv - From main via Initialise
+ * @param o - Options struct to setup
+ */
+void options_parse_args(int argc, char ** argv, Options * o)
{
+ // Go through all arguments
for (int i = 1; i < argc; ++i)
{
+ // Most switches always start with '-' and have a single character
if (argv[i][0] == '-' && argv[i][2] == '\0')
{
-
-
-
-
-
- if (argv[i][1] == 'p')
- {
- if (i >= argc-1)
- error("ParseArguments", "No argument following %s switch", argv[i]);
- o->port = atoi(argv[++i]);
- }
- else if (argv[i][1] == 'n')
+ // Set the number of local shells
+ if (argv[i][1] == 'n')
{
if (i >= argc-1)
error("ParseArguments", "No argument following %s switch", argv[i]);
+
o->nCPU = atoi(argv[++i]);
+ fprintf(stderr, "nCPU is %s (%d)\n", argv[i], o->nCPU);
}
- else if (argv[i][1] == 'r')
+ else if (argv[i][1] == 'r') // specify that this instance is remote
{
if (i >= argc-1)
error("ParseArguments", "No argument following %s switch", argv[i]);
o->port = atoi(p);
}
}
- else if (argv[i][1] == 'c')
+ else if (argv[i][1] == 'c') // Give a one off command
{
if (i >= argc-1)
fprintf(f, "%s\n", argv[++i]);
fclose(f);
dup2(open(COMMAND_FILE, O_RDONLY), fileno(stdin));
- atexit(remove_command);
+ atexit(cleanup_command);
}
- else if (argv[i][1] == 's')
+ else if (argv[i][1] == 's') // Specify the shell... TODO: Remove?
{
if (i >= argc-1)
error("ParseArguments", "No argument following %s switch", argv[i]);
o->shell = argv[++i]; // obviously this breaks things
}
- else if (argv[i][1] == 'l')
+ else if (argv[i][1] == 'l') // Specify logfile and/or level
{
if (i >= argc-1)
error("ParseArguments", "No argument following %s switch", argv[i]);
o->logfile = argv[i];
}
- else if (argv[i][1] == 'o')
+ else if (argv[i][1] == 'o') // Specify output file
{
if (i >= argc-1)
error("ParseArguments", "No argument following %s switch", argv[i]);
o->outfile = argv[++i];
}
- else if (argv[i][1] == 'e')
- o->encrypt = true;
- else if (argv[i][1] == 'u')
- o->encrypt = false;
else
{
fprintf(stderr, "%s : Unrecognised switch \"%s\"\n", argv[0], argv[i]);
}
else if (strcmp(argv[i], "--daemon") == 0)
{
+ // The --daemon switch is special because we want people to know exactly what it does
+ // Except it isn't really a "true" daemon, but whatever
+
+ // First check if there is already a daemon
FILE * f = fopen(DAEMON_PID_FILE, "r");
if (f != NULL)
{
int daemon_pid; fscanf(f, "%d\n", &daemon_pid);
fclose(f);
-
- if (kill(daemon_pid, 0) != 0)
+ // There is already a daemon...
+ if (kill(daemon_pid, 0) != 0) // is it running?
{
if (errno == ESRCH)
{
- log_print(0, "ParseArguments", "It looks like a daemon [%d] failed to exit cleanly. Starting a new daemon.", daemon_pid);
- remove_daemon();
+ log_print(LOGERR, "ParseArguments", "It looks like a daemon [%d] failed to exit cleanly. Starting a new daemon.", daemon_pid);
+ cleanup_daemon(); //TODO: Exit and leave it up to the user to remove files instead?
}
else
{
}
}
else
+ {
+ // Don't rain on the existing daemon's parade
error("ParseArguments", "A daemon is already running!");
+ }
}
int pid = fork();
if (pid != 0)
{
- //setbuf(stdout, NULL);
- //fprintf(stdout, "%d\n", pid);
exit(EXIT_SUCCESS); // fork off into daemon
}
- atexit(remove_daemon);
+ atexit(cleanup_daemon);
f = fopen(DAEMON_PID_FILE, "w");
if (f == NULL)
error("ParseArguments", "Couldn't open %s : %s", DAEMON_PID_FILE, strerror(errno));
- fprintf(f, "%d", getpid()); fclose(f);
+ fprintf(f, "%d", getpid()); fclose(f); // print daemon's PID to file
- fprintf(stdout, "%d\n", getpid());
- freopen("/dev/null", "w", stdout);
+ fprintf(stdout, "%d\n", getpid()); // ... and to stdout, because this is helpful for bash scripting
+ // (in case you didn't realise, this program is meant to be used in bash scripts)
+ freopen("/dev/null", "w", stdout); // freopen to a dark void
freopen("/dev/null", "w", stderr);
+ // NOTE: If the user manually specified output and log files, these streams get freopen'd again later
+ // Slightly inefficient to do twice, but I don't care
+ // Create a fifo to use for stdin
mkfifo(DAEMON_FIFO, 0600);
mkfifo(DAEMON_BARRIER_FIFO, 0600);
- freopen(DAEMON_FIFO, "r", stdin);
-
+ freopen(DAEMON_FIFO, "r", stdin); // NOTE: Daemon will actually block here
+ // until a daemon wrapper is run, and opens the fifo for writing
o->daemon = true;
}
- else if (o->interactive)
+ else if (o->interactive) // a script was given and we can run it
{
o->interactive = false;
dup2(open(argv[i], O_RDONLY), fileno(stdin)); // replace stdin
}
- else
+ else // Too many arguments were given
{
fprintf(stderr, "%s : Usage %s [options] [script]\n", argv[0], argv[0]);
fprintf(stderr, "%s : (extra argv[%d] %s)\n", argv[0], i, argv[i]);
}
}
-
+/**
+ * @funct strdup
+ * @purpose Implement the stdlib function strdup
+ * Because I couldn't get things to compile without this
+ * @param str - The string to copy
+ * @returns char* to allocated memory containing a copy of str
+ * NOTE: The memory needs to be free(3)'d
+ */
+// TODO: Remove?
char * strdup(const char * str)
{
int n = strlen(str) + 1;
return dup;
}
+/**
+ * @funct sigchld_respond
+ * @purpose Print messages in response to SIGCHLD... also may SIGKILL the program
+ */
+void sigchld_respond(int s, char * name, int id)
+{
+ fprintf(stderr, "Unexpected exit of slave %s:%d", name, id);
+ if (WIFSIGNALED(s))
+ {
+ int sig = WTERMSIG(s);
+ fprintf(stderr, " due to signal %s", strsignal(sig));
+ if (sig == SIGKILL)
+ {
+ fprintf(stderr," - committing suicide\n");
+ kill(getpid(), sig);
+ }
+ }
+ else
+ {
+ fprintf(stderr, " return code %d.",s);
+ }
+ fprintf(stderr, " Starting replacement.\n");
+}