GNUmakefile: replace leading spaces with TABs
[iwhd.git] / auto.c
blobb428bce6503fa1d2b2d934ad1301d8f3977c1272
1 #include <config.h>
3 #include <errno.h>
4 #include <error.h>
5 #include <fcntl.h>
6 #include <netdb.h>
7 #include <signal.h>
8 #include <stdlib.h>
9 #include <string.h>
10 #include <time.h>
11 #include <unistd.h>
12 #include <sys/socket.h>
13 #include <sys/stat.h>
14 #include <sys/types.h>
15 #include <sys/wait.h>
17 #include <stdarg.h> /* for microhttpd.h (bug in there) */
18 #include <stdint.h> /* for microhttpd.h (bug in there) */
19 #include <jansson.h>
21 #include "iwh.h"
22 #include "state_defs.h"
24 static int auto_db_port;
25 static char auto_arg_port[10];
27 static char *auto_arg_mongod[] = {
28 "mongod",
29 "--port", auto_arg_port,
30 "--dbpath", AUTO_DIR_DB,
31 /* "--fork", */ /* chdirs god knows where, we cannot use this. */
32 /* "--logpath", AUTO_MONGOD_LOG, */ /* required by --fork */
33 /* "--logappend", */
34 "--pidfilepath", "mongo.pid",
35 NULL
38 /* The --quiet option in mongod is useless, so redirect instead. */
39 static char *auto_arg_mongod_quiet[] = {
40 "mongod",
41 "--port", auto_arg_port,
42 "--dbpath", AUTO_DIR_DB,
43 "--logpath", AUTO_MONGOD_LOG,
44 "--pidfilepath", "mongo.pid",
45 NULL
48 static int auto_pid_mongod;
50 static int
51 auto_mkdir (const char *name)
53 struct stat statb;
55 if (mkdir(name, 0777) < 0) {
56 if (errno == EEXIST) {
57 if (stat(name, &statb) < 0) {
58 error (0, errno, "stat %s failed", name);
59 return -1;
61 if (!S_ISDIR(statb.st_mode)) {
62 error (0, 0, "path %s is not a directory",name);
63 return -1;
65 return 0;
67 error(0, errno, "Cannot create %s", name);
68 return -1;
70 return 0;
73 static int
74 auto_prepare_area (void)
77 if (auto_mkdir(AUTO_DIR_FS) < 0) {
78 return -1;
80 if (auto_mkdir(AUTO_DIR_DB) < 0) {
81 return -1;
83 return 0;
86 static void
87 auto_kill_mongod (int sig)
89 if (auto_pid_mongod) {
90 kill(auto_pid_mongod, sig);
94 static int
95 auto_spawn (const char *prog, char *argv[])
97 struct stat statb;
98 pid_t pid;
101 * The stat check is purely so that common errors, such as ENOENT
102 * if the program is not available, were printed before the fork.
103 * This serves no security purpose but only makes stderr more tidy.
105 if (stat(prog, &statb) < 0) {
106 error (0, errno, "stat %s failed", prog);
107 return -1;
109 if (!S_ISREG(statb.st_mode)) {
110 error (0, 0, "path %s is not a regular file", prog);
111 return -1;
114 pid = fork();
115 if (pid < 0) {
116 error (0, errno, "fork failed");
117 return -1;
120 if (pid == 0) {
121 execvp(prog, argv);
122 error (EXIT_FAILURE, errno, "failed to run command %s", prog);
126 * This is where you'd normally run waitpid for your daemon, so that
127 * argument check failures were caught at least. In case of mongod,
128 * daemonizing it is a whole can of worms, so we do not. On the
129 * upside, it stays on our session (and process group) and dies
130 * cleanly on keyboard interrupt.
133 return pid;
136 static int
137 auto_test_mongod(void)
139 union {
140 struct sockaddr_in a4;
141 struct sockaddr a;
142 } addr;
143 int sfd;
144 int rc;
147 * We hardcode IPv4 because Mongo often listens on IPv4 only.
149 memset(&addr, 0, sizeof(addr));
150 addr.a4.sin_family = AF_INET;
151 addr.a4.sin_port = htons(auto_db_port);
152 addr.a4.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
154 DPRINTF("trying to connect to mongod (host 127.0.0.1 port %u) ...\n",
155 auto_db_port);
157 sfd = socket(addr.a.sa_family, SOCK_STREAM, 0);
158 if (sfd < 0) {
159 error(0, errno, "socket");
160 return -1;
163 rc = connect(sfd, &addr.a, sizeof(addr.a4));
164 if (rc != 0) {
165 DPRINTF("connect: %s\n", strerror(errno));
166 close(sfd);
167 return 1;
170 close(sfd);
171 return 0;
174 static int
175 auto_wait_mongod(void)
177 struct timespec ts;
178 time_t start_time;
179 int rc;
181 start_time = time(NULL);
182 for (;;) {
183 rc = auto_test_mongod();
184 if (rc == 0)
185 break;
186 if (time(NULL) >= start_time + 20) {
187 error(0, 0, "failed to verify mongod using port %s",
188 auto_arg_port);
189 return -1;
192 ts.tv_sec = 1;
193 ts.tv_nsec = 0;
194 nanosleep(&ts, NULL);
196 DPRINTF("mongod went up after %ld s\n", (long)time(NULL) - start_time);
198 return 0;
201 static void
202 auto_action (int sig, siginfo_t *info, void *uctx)
204 (void) info;
205 (void) uctx;
207 if (sig == SIGSEGV || sig == SIGILL || sig == SIGFPE || sig == SIGBUS) {
208 auto_kill_mongod(SIGTERM);
210 else {
211 auto_kill_mongod(info->si_signo);
215 static int
216 auto_set_sig (void)
218 struct sigaction actb;
220 memset(&actb, 0, sizeof(struct sigaction));
221 actb.sa_flags |= SA_SIGINFO;
222 actb.sa_sigaction = auto_action;
224 /* Not trapping SIGINT or SIGHUP since mongo is in our session. */
225 if (sigaction(SIGTERM, &actb, NULL) ||
226 sigaction(SIGSEGV, &actb, NULL) ||
227 sigaction(SIGILL, &actb, NULL) ||
228 sigaction(SIGFPE, &actb, NULL) ||
229 sigaction(SIGBUS, &actb, NULL) ||
230 sigaction(SIGABRT, &actb, NULL)) {
231 error(0, errno, "sigaction");
232 return -1;
234 return 0;
237 static void
238 auto_stop (void)
240 auto_kill_mongod(SIGTERM);
244 auto_start (int dbport)
246 int rc;
247 char **earg;
248 int pid;
250 auto_db_port = dbport;
251 snprintf(auto_arg_port, sizeof(auto_arg_port), "%u", dbport);
253 if (auto_prepare_area() < 0)
254 return -1;
256 rc = auto_test_mongod();
257 if (rc < 0)
258 return -1;
261 * This is a trick. The auto_test_mongod() merely connects to a TCP
262 * port, and does not execute a NO-OP in Mongo. Therefore, it succeeds
263 * if a foreign application is listening on our private port.
264 * We abort because we do not want anyone listening there.
266 if (rc == 0) {
267 error (0, 0, "something is listening on port %s,"
268 " not auto-starting Mongo", auto_arg_port);
269 return -1;
272 DPRINTF("auto-starting mongod\n");
273 earg = verbose ? auto_arg_mongod : auto_arg_mongod_quiet;
274 pid = auto_spawn(AUTO_BIN_MONGOD, earg);
275 if (pid < 0)
276 return -1;
277 auto_pid_mongod = pid;
278 if (auto_wait_mongod() < 0) {
279 auto_kill_mongod(SIGTERM);
280 return -1;
283 if (auto_set_sig() < 0) {
284 auto_kill_mongod(SIGTERM);
285 return -1;
287 if (atexit(auto_stop) != 0) {
288 error (0, 0, "atexit failed for auto_stop");
289 auto_kill_mongod(SIGTERM);
290 return -1;
293 DPRINTF("mongod listens on port %u\n", dbport);
294 return 0;