#include <sys/wait.h>
#include <stdarg.h>
#include <syslog.h>
+#include <sys/file.h>
+#include <utime.h>
+#include <time.h>
+#include <assert.h>
#define CONNECT_RETRY_MSEC 100
-
-static long opt_start_timeout=60;
-static int opt_port;
-static int opt_syslog;
-static int opt_stderr;
+#define DEFAULT_START_COMMAND_TIMEOUT 60
+#define DEFAULT_IDLE_SERVER_TIMEOUT (90*60)
+#define FLOCK_TIMEOUT_OVER_START_TIMEOUT 2
+#define SESSION_BUFFER_SIZE 0x1000
/* /usr/include/glib-2.0/glib/gmacros.h */
static const char *program_name;
+static int opt_start;
+static int opt_stop;
+static long opt_start_command_timeout=DEFAULT_START_COMMAND_TIMEOUT;
+static long opt_idle_server_timeout=DEFAULT_IDLE_SERVER_TIMEOUT;
+static int opt_port;
+static int opt_syslog;
+static int opt_stderr;
+static const char *opt_lock;
+static const char *opt_command;
+
static void fatal(const char *fmt,...) G_GNUC_PRINTF(1,2) G_GNUC_NORETURN;
static void fatal(const char *fmt,...)
{
static void usage(void)
{
fprintf(stderr,"\
-Syntax: %s [{-T|--start-timeout} <start-timeout] [-S|--syslog] [-e|--stderr]\n\
- {-p|--port} <server-port> <start-server-command>\n\
+Syntax: %s {-1|--start} [{-T|--start-command-timeout} <start-command-timeout>]\n\
+ \t[{-l|--lock} <filename>] [-S|--syslog] [-e|--stderr]\n\
+ \t{-p|--port} <server-port> <start-server-command>\n\
+ or %s {-0|--stop} [{-i|--idle-server-timeout} <idle-server-timeout>]\n\
+ \t[{-l|--lock} <filename>] [-S|--syslog] [-e|--stderr]\n\
+ \t<stop-server-command>\n\
\n\
-Error messages out to stderr by default, -S|--syslog omits stderr output,\n\
-both -S|--syslog and -e|--stderr output the errors by both methods.\n\
-\n",program_name);
+Error messages are printed to stderr by default,\n\
+-S|--syslog omits stderr output, both -S|--syslog and -e|--stderr output\n\
+the errors by both methods.\n\
+\n",program_name,program_name);
exit(EXIT_FAILURE);
}
static const struct option longopts[]={
- {"start-timeout",1,0,'T'},
- {"syslog" ,0,0,'S'},
- {"stderr" ,0,0,'e'},
- {"port" ,1,0,'p'},
- {NULL ,0,0,0 },
+ {"start" ,0,0,'1'},
+ {"stop" ,0,0,'0'},
+ {"start-command-timeout",1,0,'T'},
+ {"idle-server-timeout" ,1,0,'i'},
+ {"syslog" ,0,0,'S'},
+ {"stderr" ,0,0,'e'},
+ {"lock" ,1,0,'l'},
+ {"port" ,1,0,'p'},
+ {NULL ,0,0,0 },
};
+static int lock_fd=-1;
+
+static int sighandler_flock_timeout_hit;
+static void sighandler_flock_timeout(int signo)
+{
+ sighandler_flock_timeout_hit=1;
+}
+
+static int lock_create(int lock_mode)
+{
+int retries=3;
+sighandler_t sighandler_alrm_orig;
+int flock_rc;
+
+ if (!opt_lock)
+ return 1;
+ /* Never drop the lock if the lock is already being held. */
+ if (lock_fd!=-1)
+ retries=-1;
+retry:
+ if (lock_fd==-1) {
+ if (-1==(lock_fd=open(opt_lock,O_CREAT|O_RDWR,0600)))
+ fatal("Error creating lock file \"%s\": %m",opt_lock);
+ }
+ sighandler_alrm_orig=signal(SIGALRM,sighandler_flock_timeout);
+ alarm(opt_start_command_timeout+FLOCK_TIMEOUT_OVER_START_TIMEOUT);
+ flock_rc=flock(lock_fd,lock_mode);
+ alarm(0);
+ signal(SIGALRM,sighandler_alrm_orig);
+ if (sighandler_flock_timeout_hit)
+ fatal("Timeout locking lock file \"%s\": %m",opt_lock);
+ if (flock_rc) {
+ if (lock_mode&LOCK_NB && errno==EWOULDBLOCK)
+ return 0;
+ fatal("Error locking lock file \"%s\": %m",opt_lock);
+ }
+ if (!access(opt_lock,R_OK|W_OK)) {
+ if (retries--<=0)
+ fatal("Racing for the lock file \"%s\", giving up",opt_lock);
+ if (close(lock_fd))
+ fatal("Error closing lock file \"%s\": %m",opt_lock);
+ lock_fd=-1;
+ goto retry;
+ }
+ return 1;
+}
+
+static void lock_touch(void)
+{
+ if (!opt_lock || lock_fd==-1)
+ return;
+ if (utime(opt_lock,NULL))
+ fatal("Error updating lock file \"%s\" timestamp: %m",opt_lock);
+}
+
+static void lock_close(void)
+{
+ if (lock_fd==-1)
+ return;
+ if (close(lock_fd))
+ fatal("Error closing lock file \"%s\": %m",opt_lock);
+ lock_fd=-1;
+}
+
static int connect_try(void)
{
int fdtcp;
for (fdi=0;fdi<G_N_ELEMENTS(pollfdi);fdi++)
if (pollfdi[fdi].events)
break;
- if (fdi>=G_N_ELEMENTS(pollfdi))
+ if (fdi>=G_N_ELEMENTS(pollfdi)) {
+ lock_close();
exit(EXIT_SUCCESS);
+ }
if (0>=poll(pollfdi,G_N_ELEMENTS(pollfdi),-1))
fatal("poll(%s socket,%s socket): %m",pollfdi_name[0],pollfdi_name[1]);
for (fdi=0;fdi<G_N_ELEMENTS(pollfdi);fdi++)
for (fdi=0;fdi<G_N_ELEMENTS(pollfdi);fdi++) {
for (;;) {
ssize_t got;
- char buf[0x1000];
+ char buf[SESSION_BUFFER_SIZE];
if (fcntl(pollfdi[fdi].fd,F_SETFL,O_NONBLOCK))
fatal("fcntl(%s socket,F_SETFL,O_NONBLOCK): %m",pollfdi_name[fdi]);
if (WIFEXITED(rc) && !WEXITSTATUS(rc))
return;
if (WIFEXITED(rc))
- fatal("Error spawning command \"%s\": return code %d!",
+ fatal("Error spawning command \"%s\": return code %d",
command,WEXITSTATUS(rc));
#ifdef WCOREDUMP
if (WIFSIGNALED(rc) && WCOREDUMP(rc))
- fatal("Error spawning command \"%s\": dumped core (terminating signal %d)!",
+ fatal("Error spawning command \"%s\": dumped core (terminating signal %d)",
command,WTERMSIG(rc));
#endif /* WCOREDUMP */
if (WIFSIGNALED(rc))
- fatal("Error spawning command \"%s\": terminating signal %d!",
+ fatal("Error spawning command \"%s\": terminating signal %d",
command,WTERMSIG(rc));
if (WIFSTOPPED(rc))
- fatal("Error spawning command \"%s\": stopping signal %d!",
+ fatal("Error spawning command \"%s\": stopping signal %d",
command,WSTOPSIG(rc));
#ifdef WIFCONTINUED
if (WIFCONTINUED(rc))
- fatal("Error spawning command \"%s\": resumed by SIGCONT!",
+ fatal("Error spawning command \"%s\": resumed by SIGCONT",
command);
#endif /* WIFCONTINUED */
- fatal("Error spawning command \"%s\": unknown reason!",
+ fatal("Error spawning command \"%s\": unknown reason",
command);
}
-int main(int argc,char **argv)
+static void start(void) G_GNUC_NORETURN;
+static void start(void)
{
int retry;
-const char *command;
+
+ if (!opt_port)
+ fatal("-p|--port is a required argument for -1|--start");
+ if (opt_idle_server_timeout!=DEFAULT_IDLE_SERVER_TIMEOUT)
+ fatal("-i|--idle-server-timeout is a forbidden argument for -1|--start");
+
+ lock_create(LOCK_SH);
+ lock_touch();
+ session_try();
+ lock_close();
+
+ lock_create(LOCK_EX);
+ system_checked(opt_command);
+ lock_create(LOCK_SH);
+
+ for (retry=0;retry*CONNECT_RETRY_MSEC/1000<opt_start_command_timeout;retry++) {
+ lock_touch();
+ session_try();
+ if (poll(NULL,0,CONNECT_RETRY_MSEC))
+ fatal("poll(timeout %dmsec): %m",CONNECT_RETRY_MSEC);
+ }
+ fatal("Timed out after %ld seconds connecting to port %d after spawned: %s",
+ opt_start_command_timeout,opt_port,opt_command);
+}
+
+/* Returns: Is fresh? */
+static int lock_create_and_time_check(int lock_mode)
+{
+struct stat statbuf;
+
+ if (!opt_lock)
+ return 1;
+
+ if (!lock_create(lock_mode|LOCK_NB))
+ exit(EXIT_SUCCESS);
+ if (lock_fd==-1 || !fstat(lock_fd,&statbuf))
+ fatal("Error fstat(2)ting lock file \"%s\": %m",opt_lock);
+ return statbuf.st_mtime>=time(NULL)-opt_idle_server_timeout;
+}
+
+static void lock_delete_and_close(void)
+{
+ /* Should not happen. */
+ if (!opt_lock || lock_fd==-1)
+ return;
+ if (unlink(opt_lock))
+ fatal("Error deleting no longer used lock file \"%s\": %m",opt_lock);
+ lock_close();
+}
+
+static void stop(void) G_GNUC_NORETURN;
+static void stop(void)
+{
+int is_fresh;
+
+ /* Lock still being held! */
+ if (opt_port)
+ fatal("-p|--port is a forbidden argument for -0|--stop");
+ if (opt_start_command_timeout!=DEFAULT_START_COMMAND_TIMEOUT)
+ fatal("-T|--start-command-timeout is a forbidden argument for -0|--stop");
+ if (opt_idle_server_timeout!=DEFAULT_IDLE_SERVER_TIMEOUT && !opt_lock)
+ fatal("-l|--lock is a required argument for -i|--idle-server-timeout of -1|--start");
+
+ is_fresh=lock_create_and_time_check(LOCK_SH);
+ lock_close();
+ if (is_fresh)
+ exit(EXIT_SUCCESS);
+
+ lock_create_and_time_check(LOCK_EX);
+ system_checked(opt_command);
+ lock_delete_and_close();
+
+ exit(EXIT_SUCCESS);
+}
+
+int main(int argc,char **argv) G_GNUC_NORETURN;
+int main(int argc,char **argv)
+{
char optc;
if ((program_name=strrchr(argv[0],'/')))
long l;
char *endptr;
- case 'T': /* -T|--start-timeout */
+ case '1': /* -1|--start */
+ opt_start=1;
+ break;
+
+ case '0': /* -0|--stop */
+ opt_stop=1;
+ break;
+
+ case 'T': /* -T|--start-command-timeout */
+ l=strtol(optarg,&endptr,0);
+ if (l<=0 || l>=LONG_MAX-FLOCK_TIMEOUT_OVER_START_TIMEOUT || (endptr && *endptr))
+ fatal("Invalid -T|--start-command-timeout value: %s",optarg);
+ opt_start_command_timeout=l;
+ break;
+
+ case 'i': /* -i|--idle-server-timeout */
l=strtol(optarg,&endptr,0);
if (l<=0 || l>=LONG_MAX || (endptr && *endptr))
- fatal("Invalid -T|--start-timeout values: %s",optarg);
- opt_start_timeout=l;
+ fatal("Invalid -i|--idle-server-timeout value: %s",optarg);
+ opt_idle_server_timeout=l;
+ break;
+
+ case 'S': /* -S|--syslog */
+ opt_syslog=1;
+ break;
+
+ case 'e': /* -e|--stderr */
+ opt_stderr=1;
break;
+ case 'l': /* -l|--lock */
+ opt_lock=optarg;
+
case 'p': /* -p|--port */
l=strtol(optarg,&endptr,0);
if (l<=0 || l>=0x10000 || (endptr && *endptr))
default:
if (optc!='h')
- fatal("Error parsing commandline!");
+ fatal("Error parsing commandline");
usage();
break;
}
- if (!opt_port)
- fatal("-p|--port is a required argument!");
+ if (!opt_start && !opt_stop)
+ fatal("At least one of -1|--opt-start or -0|--opt-stop is required");
+ if ( opt_start && opt_stop)
+ fatal("Both modes -1|--opt-start and -0|--opt-stop can never be specified simultaneously");
+
if (optind>=argc)
- fatal("<start-server-command> is a required argument!");
+ fatal("<start-server-command/stop-server-command> is a required argument");
if (optind+1<argc)
- fatal("Too many arguments, <start-server-command> needs quoting?");
- command=argv[optind];
-
- session_try();
- system_checked(command);
-
- for (retry=0;retry*CONNECT_RETRY_MSEC/1000<opt_start_timeout;retry++) {
- session_try();
- if (poll(NULL,0,CONNECT_RETRY_MSEC))
- fatal("poll(timeout %dmsec): %m",CONNECT_RETRY_MSEC);
- }
- fatal("Timed out after %ld seconds connecting to port %d after spawned: %s",
- opt_start_timeout,opt_port,command);
+ fatal("Too many arguments, <start-server-command/stop-server-command> may need quoting");
+ opt_command=argv[optind];
+
+ if (opt_start)
+ start();
+ if (opt_stop)
+ stop();
+ assert(0);
}