1 #if defined (NM_OS_LINUX)
2 # define _GNU_SOURCE
3 #endif
4 #include <nm_core.h>
5 #include <nm_dbus.h>
6 #include <nm_utils.h>
7 #include <nm_cfg_file.h>
8 #include <nm_mon_daemon.h>
9 #include <nm_remote_api.h>
10 #include <nm_qmp_control.h>
11
12 #include <sys/wait.h> /* waitpid(2) */
13 #include <time.h> /* nanosleep(2) */
14 #include <pthread.h>
15 #include <mqueue.h>
16
17 #include <json.h>
18
19 static volatile sig_atomic_t nm_mon_rebuild = 0;
20
21 static void nm_mon_check_vms(const nm_vect_t *mon_list);
22 static void nm_mon_build_list(nm_vect_t *list, nm_vect_t *vms);
23 static void nm_mon_signals_handler(int signal);
24 static int nm_mon_store_pid(void);
25
26 typedef struct nm_qmp_w_data {
27 nm_str_t *cmd;
28 pthread_barrier_t *barrier;
29 } nm_qmp_w_data_t;
30
31 typedef struct nm_clean_data {
32 nm_mon_vms_t vms;
33 #if defined (NM_OS_LINUX)
34 nm_vect_t *vm_list;
35 pthread_t *qmp_worker;
36 pthread_t *api_server;
37 #endif
38 nm_thr_ctrl_t qmp_ctrl;
39 nm_thr_ctrl_t api_ctrl;
40 } nm_clean_data_t;
41
42 #define NM_QMP_W_INIT (nm_qmp_w_data_t) { NULL, NULL }
43 #define NM_CLEAN_INIT (nm_clean_data_t) \
44 { NM_MON_VMS_INIT, NULL, NULL, NULL, NM_THR_CTRL_INIT, NM_THR_CTRL_INIT }
45
46 #if defined (NM_OS_LINUX)
nm_mon_cleanup(int rc,void * arg)47 static void nm_mon_cleanup(int rc, void *arg)
48 {
49 nm_clean_data_t *data = arg;
50 const nm_cfg_t *cfg = nm_cfg_get();
51
52 nm_debug("mon daemon exited: %d\n", rc);
53
54 nm_vect_free(data->vms.list, NULL);
55 nm_vect_free(data->vm_list, nm_str_vect_free_cb);
56
57 #if defined (NM_WITH_DBUS)
58 nm_dbus_disconnect();
59 #endif
60 data->qmp_ctrl.stop = true;
61 pthread_join(*data->qmp_worker, NULL);
62 #if defined (NM_WITH_REMOTE)
63 if (cfg->api_server) {
64 data->api_ctrl.stop = true;
65 pthread_join(*data->api_server, NULL);
66 }
67 #endif
68
69 if (unlink(cfg->daemon_pid.data) != 0) {
70 nm_debug("error delete mon daemon pidfile: %s\n", strerror(rc));
71 }
72 nm_exit_core();
73 }
74 #endif /* NM_OS_LINUX */
75
nm_qmp_worker(void * data)76 void *nm_qmp_worker(void *data)
77 {
78 struct json_object *parsed, *args, *jobid;
79 nm_str_t jobid_copy = NM_INIT_STR;
80 nm_str_t vmname = NM_INIT_STR;
81 nm_qmp_w_data_t *arg = data;
82 nm_str_t cmd = NM_INIT_STR;
83 const char *jobid_str;
84 char *name_start;
85
86 nm_str_copy(&cmd, arg->cmd);
87 pthread_barrier_wait(arg->barrier);
88
89 pthread_detach(pthread_self());
90
91 parsed = json_tokener_parse(cmd.data);
92
93 if (!parsed) {
94 nm_debug("%s: cannot parse json\n", __func__);
95 pthread_exit(NULL);
96 }
97 json_object_object_get_ex(parsed, "arguments", &args);
98 json_object_object_get_ex(args, "job-id", &jobid);
99
100 if (!args || !jobid) {
101 nm_debug("%s: malformed json\n", __func__);
102 pthread_exit(NULL);
103 }
104 jobid_str = json_object_get_string(jobid);
105
106 /*
107 * Get VM name from job-id
108 * input string example: vmdel-vmname-2021-05-27-15-14-12-tVusSMWY
109 */
110 nm_str_format(&jobid_copy, "%s", jobid_str);
111
112 /*
113 * Cut job-id, we have 7 dashes in UID.
114 * input: vmdel-vmname-2021-05-27-15-14-12-tVusSMWY
115 * <----<--<--<--<--<--<--------
116 * 7 6 5 4 3 2 1
117 * result: vmdel-vmname
118 */
119 for (size_t sep = 0; sep < 7; sep++) {
120 char *dash = strrchr(jobid_copy.data, '-');
121 if (dash) {
122 *dash = '\0';
123 } else {
124 break;
125 }
126 }
127
128 /*
129 * Cut VM name:
130 * input: vmdel-vmname
131 * ----->
132 * result: -vmname
133 */
134 name_start = strchr(jobid_copy.data, '-');
135 if (!name_start) {
136 nm_debug("%s: error get VM name from job-id\n", __func__);
137 pthread_exit(NULL);
138 }
139
140 name_start++; /* skip dash */
141 nm_str_format(&vmname, "%s", name_start);
142
143 nm_qmp_vm_exec_async(&vmname, cmd.data, jobid_str);
144
145 json_object_put(parsed);
146
147 nm_str_free(&cmd);
148 nm_str_free(&vmname);
149 nm_str_free(&jobid_copy);
150
151 pthread_exit(NULL);
152 }
153
nm_qmp_dispatcher(void * ctx)154 void *nm_qmp_dispatcher(void *ctx)
155 {
156 const nm_cfg_t *cfg = nm_cfg_get();
157 nm_thr_ctrl_t *args = ctx;
158 struct mq_attr mq_attr;
159 char *msg = NULL;
160 ssize_t rcv_len;
161 FILE *log;
162 mqd_t mq;
163
164 if ((log = fopen(cfg->log_path.data, "w")) == NULL) {
165 pthread_exit(NULL);
166 }
167
168 if ((mq = mq_open(NM_MQ_PATH, O_RDWR | O_CREAT,
169 0600, NULL)) == (mqd_t) -1) {
170 fprintf(log, "%s:cannot open mq: %s\n", __func__, strerror(errno));
171 fflush(log);
172 pthread_exit(NULL);
173 }
174
175 memset(&mq_attr, 0, sizeof(mq_attr));
176 if (mq_getattr(mq, &mq_attr) == -1) {
177 fprintf(log, "%s:cannot get mq attrs: %s\n", __func__, strerror(errno));
178 goto out;
179 }
180
181 msg = nm_calloc(1, mq_attr.mq_msgsize + 1);
182
183 while (!args->stop) {
184 struct timespec ts;
185
186 memset(msg, 0, mq_attr.mq_msgsize + 1);
187 memset(&ts, 0, sizeof(ts));
188 clock_gettime(CLOCK_REALTIME, &ts);
189 ts.tv_sec += 1;
190
191 rcv_len = mq_timedreceive(mq, msg, mq_attr.mq_msgsize, NULL, &ts);
192 if (rcv_len > 0) {
193 nm_qmp_w_data_t data = NM_QMP_W_INIT;
194 nm_str_t cmd = NM_INIT_STR;
195 pthread_barrier_t barr;
196 pthread_t thr;
197
198 if (pthread_barrier_init(&barr, NULL, 2) != 0) {
199 fprintf(log, "%s:cannot init barrier\n", __func__);
200 fflush(log);
201 }
202
203 data.barrier = &barr;
204 data.cmd = &cmd;
205
206 nm_str_format(&cmd, "%s", msg);
207
208 if (pthread_create(&thr, NULL, nm_qmp_worker, &data) != 0) {
209 nm_exit(EXIT_FAILURE);
210 }
211 #if defined (NM_OS_LINUX)
212 pthread_setname_np(thr, "nemu-qmp-worker");
213 #endif
214 pthread_barrier_wait(&barr);
215 pthread_barrier_destroy(&barr);
216 nm_str_free(&cmd);
217 }
218 }
219
220 out:
221 free(msg);
222 fclose(log);
223 mq_close(mq);
224 pthread_exit(NULL);
225 }
226
nm_mon_check_version(pid_t * opid)227 static bool nm_mon_check_version(pid_t *opid)
228 {
229 const char *path = nm_cfg_get()->daemon_pid.data;
230 struct stat info;
231 bool res = false;
232 char *buf, *nl;
233 int fd;
234
235 memset(&info, 0x0, sizeof(info));
236
237 if ((stat(path, &info) == -1) || (!info.st_size))
238 return false;
239
240 buf = nm_calloc(1, info.st_size + 1);
241 fd = open(path, O_RDONLY);
242 if (fd == -1) {
243 nm_debug("%s: error open pid file: %s: %s",
244 __func__, path, strerror(errno));
245 free(buf);
246 return false;
247 }
248
249 if (read(fd, buf, info.st_size) < 0) {
250 nm_debug("%s: error read pid file: %s: %s",
251 __func__, path, strerror(errno));
252 goto out;
253 }
254
255 if ((nl = strchr(buf, '\n')) != NULL) {
256 /* nEMU >= 3.0.0, check and cut version number */
257 nm_debug("%s: daemon version: %s [actual: %s]\n",
258 __func__, nl + 1, NM_VERSION);
259 if (nm_str_cmp_tt(nl + 1, NM_VERSION) != NM_OK) {
260 res = true;
261 *nl = '\0';
262 nm_debug("%s: daemon version different, need restart\n", __func__);
263 }
264 } else {
265 res = true;
266 nm_debug("%s: nEMU < 3.0.0, cannot check version,"
267 " restart anyway\n", __func__);
268 }
269
270 if (res) {
271 *opid = nm_str_ttoul(buf, 10);
272 }
273
274 out:
275 close(fd);
276 free(buf);
277 return res;
278 }
279
nm_mon_start(void)280 void nm_mon_start(void)
281 {
282 const nm_cfg_t *cfg = nm_cfg_get();
283 bool need_restart = false;
284 pid_t pid, wpid, opid;
285 int wstatus = 0;
286
287 if (!cfg->start_daemon)
288 return;
289
290 if (access(cfg->daemon_pid.data, R_OK) != -1) {
291 if ((need_restart = nm_mon_check_version(&opid)) == false) {
292 return;
293 }
294 }
295
296 if (need_restart) {
297 struct timespec ts;
298 bool restart_ok = false;
299
300 memset(&ts, 0, sizeof(ts));
301 ts.tv_nsec = 5e+7; /* 0.05sec */
302
303 if (kill(opid, SIGINT) < 0) {
304 nm_bug("%s: error send signal to pid %d: %s",
305 __func__, opid, strerror(errno));
306 }
307
308 /* wait for daemon shutdown */
309 for (int try = 0; try < 300; try++) {
310 if (kill(opid, 0) < 0) {
311 restart_ok = true;
312 break;
313 }
314 nanosleep(&ts, NULL);
315 }
316
317 if (!restart_ok) {
318 nm_bug("%s: after 15 seconds the daemon has not exited\n", __func__);
319 }
320 }
321
322 pid = fork();
323
324 switch (pid) {
325 case 0: /* child */
326 if (execlp(nm_nemu_path(), nm_nemu_path(), "--daemon", NULL) == -1) {
327 fprintf(stderr, "%s: execlp error: %s\n", __func__, strerror(errno));
328 nm_exit(EXIT_FAILURE);
329 }
330 break;
331 case -1: /* error */
332 fprintf(stderr, "%s: fork error: %s\n", __func__, strerror(errno));
333 nm_exit(EXIT_FAILURE);
334 default: /* parent */
335 wpid = waitpid(pid, &wstatus, 0);
336 if ((wpid == pid) && (WEXITSTATUS(wstatus) != 0)) {
337 fprintf(stderr, "%s: failed to start daemon\n", __func__);
338 nm_exit(EXIT_FAILURE);
339 }
340 break;
341 }
342 }
343
nm_mon_ping(void)344 void nm_mon_ping(void)
345 {
346 pid_t pid;
347 int fd;
348 struct stat info;
349 const char *path = nm_cfg_get()->daemon_pid.data;
350 char *buf, *nl;
351
352 memset(&info, 0x0, sizeof(info));
353
354 if ((stat(path, &info) == -1) || (!info.st_size))
355 return;
356
357 buf = nm_calloc(1, info.st_size + 1);
358 fd = open(path, O_RDONLY);
359 if (fd == -1) {
360 nm_debug("%s: error open pid file: %s: %s",
361 __func__, path, strerror(errno));
362 free(buf);
363 return;
364 }
365
366 if (read(fd, buf, info.st_size) < 0) {
367 nm_debug("%s: error read pid file: %s: %s",
368 __func__, path, strerror(errno));
369 goto out;
370 }
371
372 if ((nl = strchr(buf, '\n')) != NULL) {
373 /* nEMU >= 3.0.0, cut version number */
374 *nl = '\0';
375 }
376
377 pid = nm_str_ttoul(buf, 10);
378
379 if (kill(pid, SIGUSR1) < 0) {
380 nm_debug("%s: error send signal to pid %d: %s",
381 __func__, pid, strerror(errno));
382 }
383 out:
384 close(fd);
385 free(buf);
386 }
387
nm_mon_loop(void)388 void nm_mon_loop(void)
389 {
390 #if defined (NM_WITH_REMOTE)
391 nm_api_ctx_t api_ctx = NM_API_CTX_INIT;
392 #endif
393 nm_clean_data_t clean = NM_CLEAN_INIT;
394 nm_vect_t mon_list = NM_INIT_VECT;
395 nm_vect_t vm_list = NM_INIT_VECT;
396 pthread_t qmp_thr, api_srv;
397 const nm_cfg_t *cfg;
398 struct sigaction sa;
399 struct timespec ts;
400 pid_t pid;
401
402 nm_cfg_init();
403 cfg = nm_cfg_get();
404 if (access(cfg->daemon_pid.data, R_OK) != -1) {
405 return;
406 }
407
408 pid = fork();
409
410 switch(pid) {
411 case 0: /* child */
412 break;
413 case -1: /* error */
414 fprintf(stderr, "%s: fork error: %s\n", __func__, strerror(errno));
415 nm_exit(EXIT_FAILURE);
416 default: /* parent */
417 nm_exit_core();
418 }
419
420 if (setsid() < 0) {
421 fprintf(stderr, "%s: setsid error: %s\n", __func__, strerror(errno));
422 nm_exit(EXIT_FAILURE);
423 }
424
425 if (chdir("/") < 0) {
426 fprintf(stderr, "%s: chdir error: %s\n", __func__, strerror(errno));
427 nm_exit(EXIT_FAILURE);
428 }
429
430 #if defined (NM_OS_LINUX)
431 clean.vms.list = &mon_list;
432 clean.vm_list = &vm_list;
433 clean.qmp_worker = &qmp_thr;
434 clean.api_server = &api_srv;
435
436 if (on_exit(nm_mon_cleanup, &clean) != 0) {
437 fprintf(stderr, "%s: on_exit(3) failed\n", __func__);
438 nm_exit(EXIT_FAILURE);
439 }
440 #endif
441
442 close(STDIN_FILENO);
443 close(STDOUT_FILENO);
444 close(STDERR_FILENO);
445
446 sigemptyset(&sa.sa_mask);
447 sa.sa_flags = 0;
448 sa.sa_handler = nm_mon_signals_handler;
449 sigaction(SIGUSR1, &sa, NULL);
450 sigaction(SIGINT, &sa, NULL);
451 sigaction(SIGTERM, &sa, NULL);
452
453 ts.tv_sec = cfg->daemon_sleep / 1000;
454 ts.tv_nsec = (cfg->daemon_sleep % 1000) * 1e+6;
455
456 if (nm_mon_store_pid() != NM_OK) {
457 nm_exit(EXIT_FAILURE);
458 }
459
460 nm_db_init();
461 nm_mon_build_list(&mon_list, &vm_list);
462 #if defined (NM_WITH_DBUS)
463 if (nm_dbus_connect() != NM_OK) {
464 nm_exit(EXIT_FAILURE);
465 }
466 #endif
467 if (pthread_create(&qmp_thr, NULL, nm_qmp_dispatcher, &clean.qmp_ctrl) != 0) {
468 nm_exit(EXIT_FAILURE);
469 }
470 #if defined (NM_OS_LINUX)
471 pthread_setname_np(qmp_thr, "nemu-qmp-dsp");
472 #endif
473
474 #if defined (NM_WITH_REMOTE)
475 if (cfg->api_server) {
476 api_ctx.vms = &clean.vms;
477 api_ctx.ctrl = &clean.api_ctrl;
478 if (pthread_create(&api_srv, NULL, nm_api_server, &api_ctx) != 0) {
479 nm_exit(EXIT_FAILURE);
480 }
481 #if defined (NM_OS_LINUX)
482 pthread_setname_np(api_srv, "nemu-api");
483 #endif
484 }
485 #endif /* NM_WITH_REMOTE */
486
487 for (;;) {
488 if (nm_mon_rebuild) {
489 pthread_mutex_lock(&clean.vms.mtx);
490 nm_mon_build_list(&mon_list, &vm_list);
491 pthread_mutex_unlock(&clean.vms.mtx);
492 nm_mon_rebuild = 0;
493 }
494 nm_mon_check_vms(&mon_list);
495 nanosleep(&ts, NULL);
496 }
497 }
498
nm_mon_check_vms(const nm_vect_t * mon_list)499 static void nm_mon_check_vms(const nm_vect_t *mon_list)
500 {
501 nm_str_t body = NM_INIT_STR;
502
503 for (size_t n = 0; n < mon_list->n_memb; n++) {
504 char *name = nm_mon_item_get_name_cstr(mon_list, n);
505 int8_t status = nm_mon_item_get_status(mon_list, n);
506
507 if (nm_qmp_test_socket(nm_mon_item_get_name(mon_list, n)) == NM_OK) {
508 if (!status) {
509 nm_str_format(&body, "%s started", name);
510 #if defined (NM_WITH_DBUS)
511 nm_dbus_send_notify("VM status changed:", body.data);
512 #endif
513 }
514 nm_mon_item_set_status(mon_list, n, NM_TRUE);
515 } else {
516 if (status == 1) {
517 nm_str_format(&body, "%s stopped", name);
518 #if defined (NM_WITH_DBUS)
519 nm_dbus_send_notify("VM status changed:", body.data);
520 #endif
521 }
522 nm_mon_item_set_status(mon_list, n, NM_FALSE);
523 }
524 nm_str_free(&body);
525 }
526 }
527
nm_mon_build_list(nm_vect_t * list,nm_vect_t * vms)528 static void nm_mon_build_list(nm_vect_t *list, nm_vect_t *vms)
529 {
530 nm_vect_free(list, NULL);
531 nm_vect_free(vms, nm_str_vect_free_cb);
532
533 nm_db_select(NM_GET_VMS_SQL, vms);
534
535 for (size_t n = 0; n < vms->n_memb; n++) {
536 nm_mon_item_t item = NM_ITEM_INIT;
537
538 item.name = nm_vect_str(vms, n);
539 nm_vect_insert(list, &item, sizeof(nm_mon_item_t), NULL);
540 }
541 }
542
nm_mon_signals_handler(int signal)543 static void nm_mon_signals_handler(int signal)
544 {
545 switch (signal) {
546 case SIGUSR1:
547 nm_mon_rebuild = 1;
548 break;
549 case SIGINT:
550 nm_exit(EXIT_SUCCESS);
551 case SIGTERM:
552 nm_exit(EXIT_FAILURE);
553 }
554 }
555
nm_mon_store_pid(void)556 static int nm_mon_store_pid(void)
557 {
558 int fd, rc = NM_OK;
559 pid_t pid;
560 char *path = nm_cfg_get()->daemon_pid.data;
561 nm_str_t res = NM_INIT_STR;
562
563 fd = open(path, O_WRONLY | O_CREAT, 0644);
564 if (fd == -1) {
565 nm_debug("%s: error create pid file: %s: %s",
566 __func__, path, strerror(errno));
567 return NM_ERR;
568 }
569
570 pid = getpid();
571 nm_str_format(&res, "%d\n%s", pid, NM_VERSION);
572
573 if (write(fd, res.data, res.len) < 0) {
574 nm_debug("%s: error save pid number\n", __func__);
575 rc = NM_ERR;
576 }
577
578 close(fd);
579 nm_str_free(&res);
580
581 return rc;
582 }
583 /* vim:set ts=4 sw=4: */
584