Did some stuff
[matches/swarm.git] / src / options.c
1 #define _XOPEN_SOURCE 700
2
3 /**
4  * @file options.c
5  * @purpose Implementations of functions for populating Options struct
6  *      Also implements helpers and minor cleanup functions
7  * @author Sam Moore ([email protected])
8  */
9
10 // --- external includes --- //
11 #include <string.h>
12 #include <stdlib.h>
13 #include <time.h>
14 #include <unistd.h>
15 #include <errno.h>
16 #include <sys/types.h>
17 #include <sys/stat.h>
18 #include <fcntl.h>
19 #include <signal.h>
20 #include <sys/wait.h>
21
22
23 // --- local includes --- //
24 #include "options.h"
25 #include "log.h"
26 #include "daemon.h"
27
28 /**
29  * @funct cleanup_command
30  * @purpose Removes the COMMAND_FILE used for one off command parsing
31  *      Added to exit table only if needed
32  */
33 void cleanup_command() 
34 {
35         remove(COMMAND_FILE);
36 }
37
38 /**
39  * @funct cleanup_daemon
40  * @purpose Removes files and fifo's used by daemon
41  *      Added to exit table only if needed
42  */
43 void cleanup_daemon()
44 {
45         remove(DAEMON_PID_FILE);
46         remove(DAEMON_FIFO);
47         remove(DAEMON_BARRIER_FIFO);
48 }
49
50
51 /**
52  * @funct Initialise
53  * @purpose Setup default options, call parse_args, do basic setup and initialisation
54  * @param argc - From main
55  * @param argv - From main
56  * @param o - Options struct to setup
57  */
58 void options_setup(int argc, char ** argv, Options * o)
59 {
60         srand(time(NULL)); // rand is used in a few places, so seed the RNG
61
62         // Setup default options
63         o->program = argv[0];
64         o->master_addr = NULL;
65         o->shell = "bash"; // choosing other shells will not end well
66         o->logfile = NULL;
67         o->outfile = NULL;
68         o->verbosity = LOGNOTE; // default log level
69         o->port = 0;
70         o->append = NULL;
71         o->prepend = NULL;
72         o->nCPU = sysconf( _SC_NPROCESSORS_ONLN );
73         o->daemon = false;
74         o->interactive = true;
75         o->encrypt = true;
76
77         // Parse arguments      
78         options_parse_args(argc, argv, o);
79
80         // Initialisation behaviour that depends on option values
81         
82         if (!o->daemon) // if we are not a daemon...
83         {
84                 FILE * f = fopen(DAEMON_PID_FILE, "r"); 
85                 if (f != NULL)
86                 {
87                         //... but the DAEMON_PID_FILE exists ...
88                         int daemon_pid;
89                         fscanf(f, "%d", &daemon_pid); // get its PID
90                         fclose(f);
91                         if (kill(daemon_pid, 0) != 0) // Check it's running
92                         {
93                                 error("Initialise", "There was a daemon [%d] running here, but it's gone for some reason.", daemon_pid);
94                         }
95                         // We are a daemon wrapper
96                         o->daemon_wrapper = true;
97
98                 }
99         }
100         else
101         {
102                 // We are not a daemon wrapper
103                 o->daemon_wrapper = false;
104         }
105
106         if (o->logfile != NULL)
107         {
108                 // A logfile was given; freopen on stderr
109                 if (o->logfile[0] == '+') // option to append
110                         freopen(o->logfile+1, "a", stderr);
111                 else
112                         freopen(o->logfile, "w", stderr);
113
114                 setbuf(stderr, NULL);
115         }
116         if (o->outfile != NULL)
117         {
118                 // An outfile was given; freopen on stdout
119                 if (o->outfile[0] == '+') // option to append
120                         freopen(o->outfile+1, "a", stdout);
121                 else
122                         freopen(o->outfile, "w", stdout);
123         }
124
125         // If the verbosity is high enough, display a welcome message
126         if (o->verbosity >= LOGINFO)
127         {
128                 char buffer[BUFSIZ]; getcwd(buffer, BUFSIZ);
129                 char * type = (o->daemon) ? "daemon" : (o->daemon_wrapper) ? "wrapper" : (o->master_addr != NULL) ? "slave" : "interactive";
130                 log_print(LOGINFO, "options_setup", "Directory %s; type of instance: %s", buffer, type);
131         }
132         
133         // If we are a daemon wrapper, we can't be interactive
134         // This is purely to avoid confusing people; it is trivial to be both a daemon wrapper and interactive
135         //      but they'll never see any outputs
136         if (o->daemon_wrapper && o->interactive)
137         {
138                 log_print(LOGWARN, "options_setup", "There is a daemonised swarm [%d] running in this directory.");
139                 log_print(LOGWARN, "options_setup", "You can only pass commands to the daemon by invoking %s -c [command]", o->program);
140                 log_print(LOGWARN, "options_setup", "Running `swarm -c \"#EXIT#\"' will quit the daemon, unless it is waiting on a BARRIER");
141                 error("options_setup", "Can't run an interactive wrapper to a daemon");
142         }
143
144 }
145
146 /**
147  * @funct options_parse_args
148  * @purpose Parse arguments and setup options accordingly
149  * @param argc - From main via Initialise
150  * @param argv - From main via Initialise
151  * @param o - Options struct to setup
152  */
153 void options_parse_args(int argc, char ** argv, Options * o)
154 {
155
156         // Go through all arguments
157         for (int i = 1; i < argc; ++i)
158         {
159                 // Most switches always start with '-' and have a single character
160                 if (argv[i][0] == '-' && argv[i][2] == '\0')
161                 {
162                         // Set the number of local shells
163                         if (argv[i][1] == 'n')
164                         {
165                                 if (i >= argc-1)
166                                         error("ParseArguments", "No argument following %s switch", argv[i]);
167
168                                 o->nCPU = atoi(argv[++i]);
169                                 fprintf(stderr, "nCPU is %s (%d)\n", argv[i], o->nCPU);
170                         }
171                         else if (argv[i][1] == 'r') // specify that this instance is remote
172                         {
173                                 if (i >= argc-1)
174                                         error("ParseArguments", "No argument following %s switch", argv[i]);
175                                 o->master_addr = argv[++i];
176                                 char * p = strstr(o->master_addr, ":");
177                                 if (p != NULL)
178                                 {
179                                         *(p-1) = '\0';
180                                         o->port = atoi(p);
181                                 }
182                         }
183                         else if (argv[i][1] == 'c') // Give a one off command
184                         {
185
186                                 if (i >= argc-1)
187                                         error("ParseArguments", "No argument following %s switch", argv[i]);
188                                 
189                                 if (!o->interactive)
190                                         error("ParseArguments", "Can't use %s switch in combination with a script", argv[i]);
191
192                                 // insert terrible hack here
193                                 o->interactive = false;
194                                 FILE * f = fopen(COMMAND_FILE, "a");
195                                 fprintf(f, "%s\n", argv[++i]);
196                                 fclose(f);
197                                 dup2(open(COMMAND_FILE, O_RDONLY), fileno(stdin));
198                                 atexit(cleanup_command);
199                         }
200                         else if (argv[i][1] == 's') // Specify the shell... TODO: Remove?
201                         {
202                                 if (i >= argc-1)
203                                         error("ParseArguments", "No argument following %s switch", argv[i]);
204
205                                 o->shell = argv[++i]; // obviously this breaks things
206                         }
207                         else if (argv[i][1] == 'l')  // Specify logfile and/or level
208                         {
209                                 if (i >= argc-1)
210                                         error("ParseArguments", "No argument following %s switch", argv[i]);
211
212                                 char * l = argv[++i];
213                                 while (*l != '\0') if (*(l++) == ':') break;
214                                 if (*l != '\0')
215                                 {
216                                         o->verbosity = atoi(l);
217                                         *(l-1) = 0;
218                                 }
219                                 if (argv[i][0] != '\0')
220                                         o->logfile = argv[i];
221         
222                         }
223                         else if (argv[i][1] == 'o') // Specify output file
224                         {
225                                 if (i >= argc-1)
226                                         error("ParseArguments", "No argument following %s switch", argv[i]);
227
228                                 o->outfile = argv[++i];
229                         }
230                         else
231                         {
232                                 fprintf(stderr, "%s : Unrecognised switch \"%s\"\n", argv[0], argv[i]);
233                                 exit(EXIT_FAILURE);
234                         }
235                 }
236                 else if (strcmp(argv[i], "--daemon") == 0)
237                 {
238                         // The --daemon switch is special because we want people to know exactly what it does
239                         // Except it isn't really a "true" daemon, but whatever
240
241                         // First check if there is already a daemon
242                         FILE * f = fopen(DAEMON_PID_FILE, "r"); 
243                         if (f != NULL)
244                         {
245                                 int daemon_pid; fscanf(f, "%d\n", &daemon_pid);
246                                 fclose(f);
247                                 // There is already a daemon...
248                                 if (kill(daemon_pid, 0) != 0) // is it running?
249                                 {
250                                         if (errno == ESRCH)
251                                         {
252                                                 log_print(LOGERR, "ParseArguments", "It looks like a daemon [%d] failed to exit cleanly. Starting a new daemon.", daemon_pid);
253                                                 cleanup_daemon(); //TODO: Exit and leave it up to the user to remove files instead?
254                                         }
255                                         else
256                                         {
257                                                 error("ParseArguments", "Couldn't determine whether a daemon [%d] was already running : %s", daemon_pid, strerror(errno));
258                                         }
259                                 }
260                                 else
261                                 {
262                                         // Don't rain on the existing daemon's parade
263                                         error("ParseArguments", "A daemon is already running!");
264                                 }
265                         }
266                         
267                         int pid = fork();
268                         if (pid != 0)
269                         {
270                                 exit(EXIT_SUCCESS); // fork off into daemon
271                         }
272                         atexit(cleanup_daemon);
273
274                         f = fopen(DAEMON_PID_FILE, "w");
275                         if (f == NULL)
276                                 error("ParseArguments", "Couldn't open %s : %s", DAEMON_PID_FILE, strerror(errno));
277                         fprintf(f, "%d", getpid()); fclose(f); // print daemon's PID to file
278         
279                         fprintf(stdout, "%d\n", getpid()); // ... and to stdout, because this is helpful for bash scripting
280                                                            // (in case you didn't realise, this program is meant to be used in bash scripts)
281                         freopen("/dev/null", "w", stdout); // freopen to a dark void
282                         freopen("/dev/null", "w", stderr);
283                         // NOTE: If the user manually specified output and log files, these streams get freopen'd again later
284                         // Slightly inefficient to do twice, but I don't care
285
286                         // Create a fifo to use for stdin
287                         mkfifo(DAEMON_FIFO, 0600);
288                         mkfifo(DAEMON_BARRIER_FIFO, 0600);
289                         freopen(DAEMON_FIFO, "r", stdin); // NOTE: Daemon will actually block here 
290                                                         // until a daemon wrapper is run, and opens the fifo for writing
291                         o->daemon = true;
292                         
293                         
294                 }
295                 else if (o->interactive) // a script was given and we can run it
296                 {
297                         o->interactive = false;
298                         dup2(open(argv[i], O_RDONLY), fileno(stdin)); // replace stdin
299                 }
300                 else // Too many arguments were given
301                 {
302                         fprintf(stderr, "%s : Usage %s [options] [script]\n", argv[0], argv[0]);
303                         fprintf(stderr, "%s : (extra argv[%d] %s)\n", argv[0], i, argv[i]);
304                         exit(EXIT_FAILURE);
305                 }
306         }
307 }
308
309 /**
310  * @funct strdup
311  * @purpose Implement the stdlib function strdup
312  *      Because I couldn't get things to compile without this
313  * @param str - The string to copy
314  * @returns char* to allocated memory containing a copy of str
315  *      NOTE: The memory needs to be free(3)'d
316  */
317 // TODO: Remove?
318 char * strdup(const char * str)
319 {
320         int n = strlen(str) + 1;
321         char * dup = (char*)(calloc(n, sizeof(char)));
322         if (dup != NULL)
323         {
324                 strcpy(dup, str);
325         }
326         return dup;
327 }
328
329 /**
330  * @funct sigchld_respond
331  * @purpose Print messages in response to SIGCHLD... also may SIGKILL the program
332  */
333 void sigchld_respond(int s, char * name, int id)
334 {
335         fprintf(stderr, "Unexpected exit of slave %s:%d", name, id);
336         if (WIFSIGNALED(s))
337         {
338                 int sig = WTERMSIG(s);
339                 fprintf(stderr, " due to signal %s", strsignal(sig));
340                 if (sig == SIGKILL)
341                 {
342                         fprintf(stderr," - committing suicide\n");
343                         kill(getpid(), sig);
344                 }
345         }
346         else
347         {               
348                 fprintf(stderr, " return code %d.",s);
349         }
350         fprintf(stderr, " Starting replacement.\n");
351 }
352

UCC git Repository :: git.ucc.asn.au