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