Security: Set supplemental groups correctly when dropping privileges.
[vhostmd:vhostmd.git] / vhostmd / vhostmd.c
1 /*
2  * Copyright (C) 2008 Novell, Inc.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
17  *
18  * Author: Jim Fehlig <jfehlig@novell.com>
19  */
20
21 #include <config.h>
22
23 #include <unistd.h>
24 #include <fcntl.h>
25 #include <paths.h>
26 #include <limits.h>
27 #include <stdlib.h>
28 #include <stdio.h>
29 #include <stdarg.h>
30 #include <string.h>
31 #include <stdint.h>
32 #include <stddef.h>
33 #include <errno.h>
34 #include <getopt.h>
35 #include <signal.h>
36 #include <pwd.h>
37 #include <grp.h>
38 #include <arpa/inet.h>
39 #include <sys/types.h>
40 #include <sys/wait.h>
41 #include <sys/stat.h>
42 #include <libxml/parser.h>
43 #include <libxml/xpath.h>
44
45 #include "util.h"
46 #include "metric.h"
47
48
49 /*
50  * vhostmd will periodically write metrics to a disk.  The metrics
51  * to write, how often, and where to write them are all adjustable
52  * via the vhostmd.xml configuration file.
53  *
54  * Currently, the disk format is quite simple: a raw, memory-backed
55  * disk containing
56  * - 4 byte signature, big endian
57  * - 4 byte busy flag, big endian
58  * - 4 byte content checksum, big endian
59  * - 4 byte content length, big endian
60  * - content
61  */
62
63 #define MDISK_SIZE_MIN      1024
64 #define MDISK_SIZE_MAX      (256 * 1024 * 1024)
65 #define MDISK_SIGNATURE     0x6d766264  /* 'mvbd' */
66
67 typedef struct _mdisk_header
68 {
69    uint32_t sig;
70    uint32_t busy;
71    uint32_t sum;
72    uint32_t length;
73 } mdisk_header;
74
75 #define MDISK_HEADER_SIZE   (sizeof(mdisk_header))
76
77 /* 
78  * Macro for determining usable size of metrics disk
79  */
80 #define MDISK_SIZE          (mdisk_size - MDISK_HEADER_SIZE)
81
82 /*
83  * Transports
84  */
85 #define VBD      (1 << 0)
86 #define XENSTORE (1 << 1)
87
88 /* Global variables */
89 static int down = 0;
90 static int mdisk_size = MDISK_SIZE_MIN;
91 static int update_period = 5;
92 static char *def_mdisk_path = "/dev/shm/vhostmd0";
93 static char *mdisk_path = NULL;
94 static char *pid_file = "/var/run/vhostmd.pid";
95 static metric *metrics = NULL;
96 static mdisk_header md_header =
97          {
98             .sig = 0,
99             .busy = 0,
100             .sum = 0,
101             .length = 0,
102          };
103 static char *search_path = NULL;
104 static int transports = 0;
105
106
107 /**********************************************************************
108  * Basic daemon support functions
109  *********************************************************************/
110
111 static void sig_handler(int sig, siginfo_t *siginfo ATTRIBUTE_UNUSED,
112                         void *context ATTRIBUTE_UNUSED)
113 {
114    switch (sig) {
115       case SIGINT:
116       case SIGTERM:
117       case SIGQUIT:
118          down = 1;
119          break;
120       default:
121          break;
122    }
123 }
124
125 static int write_pid_file(const char *pfile)
126 {
127    int fd;
128    FILE *fh;
129
130    if (pfile[0] == '\0')
131       return 0;
132
133    if ((fd = open(pfile, O_WRONLY|O_CREAT|O_EXCL, 0644)) < 0) {
134       vu_log(VHOSTMD_ERR, "Failed to open pid file '%s' : %s",
135                   pfile, strerror(errno));
136       return -1;
137    }
138
139    if (!(fh = fdopen(fd, "w"))) {
140       vu_log(VHOSTMD_ERR, "Failed to fdopen pid file '%s' : %s",
141                   pfile, strerror(errno));
142       close(fd);
143       return -1;
144    }
145
146    if (fprintf(fh, "%lu\n", (unsigned long)getpid()) < 0) {
147       vu_log(VHOSTMD_ERR, "Failed to write to pid file '%s' : %s",
148                   pfile, strerror(errno));
149       close(fd);
150       return -1;
151    }
152
153    if (fclose(fh) == EOF) {
154       vu_log(VHOSTMD_ERR, "Failed to close pid file '%s' : %s",
155                   pfile, strerror(errno));
156       return -1;
157    }
158
159    return 0;
160 }
161
162 static int daemonize(void)
163 {
164    int pid = fork();
165    switch (pid) {
166       case 0:
167       {
168          int stdinfd = -1;
169          int stdoutfd = -1;
170          int nextpid;
171          
172          if ((stdinfd = open(_PATH_DEVNULL, O_RDONLY)) < 0)
173             goto cleanup;
174          if ((stdoutfd = open(_PATH_DEVNULL, O_WRONLY)) < 0)
175             goto cleanup;
176          if (dup2(stdinfd, STDIN_FILENO) != STDIN_FILENO)
177             goto cleanup;
178          if (dup2(stdoutfd, STDOUT_FILENO) != STDOUT_FILENO)
179             goto cleanup;
180          if (dup2(stdoutfd, STDERR_FILENO) != STDERR_FILENO)
181             goto cleanup;
182          if (close(stdinfd) < 0)
183             goto cleanup;
184          stdinfd = -1;
185          if (close(stdoutfd) < 0)
186             goto cleanup;
187          stdoutfd = -1;
188
189          if (chdir ("/") == -1)
190             goto cleanup;
191          
192          if (setsid() < 0)
193             goto cleanup;
194          
195          nextpid = fork();
196          switch (nextpid) {
197             case 0:
198                return 0;
199             case -1:
200                return -1;
201             default:
202                _exit(0);
203          }
204          
205       cleanup:
206          if (stdoutfd != -1)
207             close(stdoutfd);
208          if (stdinfd != -1)
209             close(stdinfd);
210          return -1;
211          
212       }
213       
214       case -1:
215          return -1;
216          
217       default:
218       {
219          int got, status = 0;
220          /* We wait to make sure the next child forked successfully */
221          if ((got = waitpid(pid, &status, 0)) < 0 ||
222              got != pid || status != 0) {
223             return -1;
224          }
225          _exit(0);
226       }
227    }
228 }
229
230 /**********************************************************************
231  * Config file parsing functions
232  *********************************************************************/
233
234 /* Parse a XML group metric node and return success indication */
235 static int parse_group_metric(xmlDocPtr xml ATTRIBUTE_UNUSED,
236                               xmlXPathContextPtr ctxt, xmlNodePtr node, metric *mdef)
237 {
238    xmlXPathObjectPtr obj = NULL;
239    xmlChar *path = NULL;
240    char *cp = NULL;
241    xmlChar *prop;
242    int ret = -1;
243    int i;
244
245    free(mdef->name);
246    free(mdef->type_str);
247    mdef->name = NULL;
248    mdef->type_str = NULL;
249    mdef->cnt = 0;
250
251    path = xmlGetNodePath(node);
252    if (path == NULL) {
253       vu_log(VHOSTMD_WARN, "parse_group_metric: node path not found");
254       return -1;
255    }
256    asprintf(&cp, "%s/variable", path);
257
258    obj = xmlXPathEval( BAD_CAST cp, ctxt);
259    if ((obj == NULL) || (obj->type != XPATH_NODESET)) {
260       vu_log(VHOSTMD_WARN, "parse_group_metric: variable set not found");
261       goto error;
262    }
263
264    mdef->cnt = xmlXPathNodeSetGetLength(obj->nodesetval);
265    vu_log(VHOSTMD_INFO, "parse_group_metric: number of variable nodes: %d", mdef->cnt);
266    for (i = 0; i < mdef->cnt; i++) {
267       xmlNode *n = obj->nodesetval->nodeTab[i];
268       if ((prop = xmlGetProp(n, BAD_CAST "name")) == NULL) {
269          vu_log(VHOSTMD_WARN, "parse_group_metric: metric name not specified");
270          goto error;
271       }
272       vu_append_string(&mdef->name, prop);
273       free(prop);
274
275       if ((prop = xmlGetProp(n, BAD_CAST "type")) == NULL) {
276          vu_log(VHOSTMD_WARN, "parse_group_metric: metric type not specified");
277          goto error;
278       }
279       vu_append_string(&mdef->type_str, prop);
280       free(prop);
281    }
282    ret = 0;
283 error:
284    free(path);
285    free(cp);
286    if (obj)
287       xmlXPathFreeObject(obj);
288    return ret;
289 }
290
291 /* Parse a XML metric node and return a metric definition */
292 static metric *parse_metric(xmlDocPtr xml, xmlXPathContextPtr ctxt, xmlNodePtr node)
293 {
294    metric *mdef = NULL;
295    xmlNodePtr cur;
296    xmlChar *mtype = NULL;
297    xmlChar *mcontext = NULL;
298    xmlChar *str;
299
300    mdef = calloc(1, sizeof(metric));
301    if (mdef == NULL) {
302       vu_log(VHOSTMD_WARN, "Unable to allocate memory for "
303                   "metrics definition");
304       return NULL;
305    }
306    
307    /* Get the metric type attribute */
308    if ((mtype = xmlGetProp(node, BAD_CAST "type")) == NULL) {
309       vu_log(VHOSTMD_WARN, "metric type not specified");
310       goto error;
311    }
312
313    if (metric_type_from_str(mtype, &(mdef->type))) {
314       vu_log(VHOSTMD_WARN, "Unsupported metric type %s", mtype);
315       goto error;
316    }
317    mdef->type_str = strdup((char *)mtype);
318
319    /* Get the metric context attribute */
320    if ((mcontext = xmlGetProp(node, BAD_CAST "context")) == NULL) {
321       vu_log(VHOSTMD_WARN, "metric context not specified");
322       goto error;
323    }
324    if (xmlStrEqual(mcontext, BAD_CAST "host"))
325       mdef->ctx = METRIC_CONTEXT_HOST;
326    else if (xmlStrEqual(mcontext, BAD_CAST "vm"))
327       mdef->ctx = METRIC_CONTEXT_VM;
328    else {
329       vu_log(VHOSTMD_WARN, "Unsupported metric context (%s) :"
330                   "supported contexts (host) and (vm)", mcontext);
331       goto error;
332    }
333       
334    /* Get the metric name and the action */
335    cur = node->xmlChildrenNode;
336
337    while(cur != NULL) {
338       str = xmlNodeListGetString(xml, cur->xmlChildrenNode, 1);
339       if (str && xmlStrEqual(cur->name, BAD_CAST "name")) {
340          mdef->name= strdup((char *)str);
341       }
342       if (str && xmlStrEqual(cur->name, BAD_CAST "action")) {
343          mdef->action = strdup((char *)str);
344       }
345       if (str)
346          free(str);
347       cur = cur->next;
348    }
349    
350    if (mdef->name == NULL) {
351          vu_log(VHOSTMD_WARN, "Metric name not specified");
352          goto error;
353    }
354    if (mdef->action == NULL) {
355          vu_log(VHOSTMD_WARN, "Metric action not specified");
356          goto error;
357    }
358
359    vu_log(VHOSTMD_INFO, "Adding %s metric '%s'",
360                mdef->ctx == METRIC_CONTEXT_HOST ? "host" : "vm",
361                mdef->name);
362    vu_log(VHOSTMD_INFO, "\t action: %s", mdef->action);
363
364    mdef->cnt = 1;
365    if (mdef->type == M_GROUP) {
366       if (parse_group_metric(xml, ctxt, node, mdef) == -1) {
367          goto error;
368       }
369    }
370
371    free(mtype);
372    free(mcontext);
373
374    return mdef;
375    
376  error:
377    if (mdef) {
378       free(mdef->name);
379       free(mdef->action);
380       free(mdef->type_str);
381       free(mdef);
382    }
383    free(mtype);
384    free(mcontext);
385    
386    return NULL;
387 }
388
389 /* Parse metrics nodes contained in XML doc */
390 static int parse_metrics(xmlDocPtr xml,
391                          xmlXPathContextPtr ctxt)
392 {
393    xmlXPathObjectPtr obj;
394    xmlNodePtr relnode;
395    metric *mdef;
396    int num = 0;
397    int i;
398    
399    if (ctxt == NULL) {
400       vu_log(VHOSTMD_ERR, "Invalid parameter to parse_metrics");
401       return -1;
402    }
403
404    relnode = ctxt->node;
405    
406    obj = xmlXPathEval( BAD_CAST "//vhostmd/metrics/metric", ctxt);
407    if ((obj == NULL) || (obj->type != XPATH_NODESET)) {
408       xmlXPathFreeObject(obj);
409       vu_log(VHOSTMD_WARN, "No metrics found or malformed definition");
410       return -1;
411    }
412
413    num = xmlXPathNodeSetGetLength(obj->nodesetval);
414    vu_log(VHOSTMD_INFO, "Number of metrics nodes: %d", num);
415    for (i = 0; i < num; i++) {
416       mdef = parse_metric(xml, ctxt, obj->nodesetval->nodeTab[i]);
417       if (mdef) {
418          mdef->next = metrics;
419          metrics = mdef;
420       }
421       else {
422          vu_log(VHOSTMD_WARN, "Unable to parse metric node, ignoring ...");
423          continue;
424       }
425    }
426
427    xmlXPathFreeObject(obj);
428    ctxt->node = relnode;
429    return 0;
430 }
431
432 static int parse_transports(xmlDocPtr xml,
433                          xmlXPathContextPtr ctxt)
434 {
435    xmlXPathObjectPtr obj;
436    xmlNodePtr relnode;
437    xmlNodePtr cur;
438    xmlChar *str;
439    int num = 0;
440    int i;
441    
442    if (ctxt == NULL) {
443       vu_log(VHOSTMD_ERR, "Invalid parameter to parse_transports");
444       return -1;
445    }
446
447    relnode = ctxt->node;
448    
449    obj = xmlXPathEval( BAD_CAST "//vhostmd/globals/transport", ctxt);
450    if ((obj == NULL) || (obj->type != XPATH_NODESET)) {
451       xmlXPathFreeObject(obj);
452       vu_log(VHOSTMD_WARN, "No transport found or malformed definition");
453       return -1;
454    }
455
456    num = xmlXPathNodeSetGetLength(obj->nodesetval);
457    for (i = 0; i < num; i++) {
458       cur = obj->nodesetval->nodeTab[i]->xmlChildrenNode;
459       str = xmlNodeListGetString(xml, cur, 1);
460       if (str) {
461          if (strncasecmp((char *)str, "vbd", strlen("vbd")) == 0)
462              transports |= VBD;
463          if (strncasecmp((char *)str, "xenstore", strlen("xenstore")) == 0) {
464 #ifdef WITH_XENSTORE
465              transports |= XENSTORE;
466 #else
467              vu_log (VHOSTMD_ERR, "No support for xenstore transport in this vhostmd");
468              return -1;
469 #endif
470          }
471          free(str);
472       }
473    }
474    xmlXPathFreeObject(obj);
475    ctxt->node = relnode;
476    /* Should not happen */
477    if (transports == 0)
478        transports = VBD;
479
480    return 0;
481 }
482
483 static int validate_config_file(const char *filename)
484 {
485     xmlDocPtr doc = NULL;
486     xmlParserCtxtPtr pctxt = NULL;
487     xmlNode *root_element = NULL;
488     int ret = -1;
489
490     pctxt = xmlNewParserCtxt();
491     if (!pctxt || !pctxt->sax) {
492         vu_log(VHOSTMD_ERR, "%s(): failed to allocate parser context \n", __FUNCTION__);
493         goto error;
494     }
495
496     doc = xmlCtxtReadFile(pctxt, filename, NULL, XML_PARSE_DTDVALID);
497     if (!doc) {
498         vu_log(VHOSTMD_ERR, "%s(): could not read file:%s \n", __FUNCTION__, filename);
499         goto error;
500     }
501     if (pctxt->valid == 0) {
502         vu_log(VHOSTMD_ERR, "%s(): Failed to validate :%s \n", __FUNCTION__, filename);
503         goto error;
504     }
505
506     root_element = xmlDocGetRootElement(doc);
507     if (!root_element) {
508         vu_log(VHOSTMD_ERR, "%s(): could not locate root element\n", __FUNCTION__);
509         goto error;
510     }
511
512     if (xmlStrncmp((const xmlChar*)"vhostmd", root_element->name, strlen("vhostmd")) != 0) {
513         vu_log(VHOSTMD_ERR, "%s(): Incorrect root element name:%s\n", __FUNCTION__,
514                     root_element->name);
515         goto error;
516     }
517     ret = 0;
518
519 error:
520     //if (root_element)
521        //xmlFreeNode(root_element);
522     if (doc)
523        xmlFreeDoc(doc);
524     if (pctxt)
525        xmlFreeParserCtxt(pctxt);
526     return(ret);
527
528 }
529
530 /* Parse vhostmd configuration file */
531 static int parse_config_file(const char *filename)
532 {
533    xmlParserCtxtPtr pctxt = NULL;
534    xmlDocPtr xml = NULL;
535    xmlXPathContextPtr ctxt = NULL;
536    xmlNodePtr root;
537    //config_file_element *element = NULL;
538    char *unit = NULL;
539    long l;
540    int ret = -1;
541
542    /* Set up a parser context so we can catch the details of XML errors. */
543    pctxt = xmlNewParserCtxt();
544    if (!pctxt || !pctxt->sax)
545       goto out;
546
547    xml = xmlCtxtReadFile(pctxt, filename, NULL,
548                          XML_PARSE_NOENT | XML_PARSE_NONET |
549                          XML_PARSE_NOWARNING);
550    if (!xml) {
551       vu_log(VHOSTMD_ERR, "libxml failed to parse config file %s",
552                   filename);
553       goto out;
554    }
555
556    if ((root = xmlDocGetRootElement(xml)) == NULL) {
557       vu_log(VHOSTMD_ERR, "Config file %s missing root element",
558                   filename);
559       goto out;
560    }
561
562    if (!xmlStrEqual(root->name, BAD_CAST "vhostmd")) {
563       vu_log(VHOSTMD_ERR, "Config file contains incorrect root element");
564       goto out;
565    }
566
567    ctxt = xmlXPathNewContext(xml);
568    if (ctxt == NULL) {
569       vu_log(VHOSTMD_ERR, "Unable to allocate memory");
570       goto out;
571    }
572
573    ctxt->node = root;
574
575    /* Get global settings */
576    mdisk_path = vu_xpath_string("string(./globals/disk/path[1])", ctxt);
577    if (mdisk_path == NULL)
578       mdisk_path = strdup(def_mdisk_path);
579
580    unit = vu_xpath_string("string(./globals/disk/size[1]/@unit)", ctxt);
581    if (vu_xpath_long("string(./globals/disk/size[1])", ctxt, &l) == 0) {
582       mdisk_size = vu_val_by_unit(unit, (int)l);
583    }
584    else {
585       vu_log(VHOSTMD_ERR, "Unable to parse metrics disk size");
586       goto out;
587    }
588
589    if (vu_xpath_long("string(./globals/update_period[1])", ctxt, &l) == 0) {
590       update_period = (int)l;
591    }
592    else {
593       vu_log(VHOSTMD_ERR, "Unable to parse update period");
594       goto out;
595    }
596
597    if ((search_path = vu_xpath_string("string(./globals/path[1])", ctxt)) != NULL) {
598       setenv("PATH", search_path, 1);
599    }
600
601    if (parse_transports(xml, ctxt) == -1) {
602       vu_log(VHOSTMD_ERR, "Unable to parse transports");
603       goto out;
604    }
605     
606    /* Parse requested metrics definitions */
607    if (parse_metrics(xml, ctxt)) {
608       vu_log(VHOSTMD_ERR, "Unable to parse metrics definition "
609                   "in vhostmd config file");
610       goto out;
611    }
612       
613    ret = 0;
614
615  out:
616    free(unit);
617    xmlXPathFreeContext(ctxt);
618    xmlFreeDoc(xml);
619    xmlFreeParserCtxt(pctxt);
620    return ret;
621 }
622
623 /**********************************************************************
624  * daemon-specific functions
625  *********************************************************************/
626
627 /* Ensure valid config settings, returning non-zero of failure */
628 static int check_config(void)
629 {
630    /* check valid disk path */
631    if (!mdisk_path) {
632       vu_log(VHOSTMD_ERR, "Metrics disk path not specified");
633       return -1;
634    }
635
636    /* check valid disk size */
637    if (mdisk_size < MDISK_SIZE_MIN || mdisk_size > MDISK_SIZE_MAX) {
638       vu_log(VHOSTMD_ERR, "Specified metrics disk size "
639                   "(%d) not within supported range: (%d - %d)",
640                   mdisk_size, MDISK_SIZE_MIN, MDISK_SIZE_MAX);
641       return -1;
642    }
643
644    /* check valid update period */
645    if (update_period < 1) {
646       vu_log(VHOSTMD_ERR, "Specified update period (%d) less "
647                   "than minimum supported (1)",
648                   update_period);
649       return -1;
650    }
651
652    vu_log(VHOSTMD_INFO, "Using metrics disk path %s", mdisk_path);
653    vu_log(VHOSTMD_INFO, "Using metrics disk size %d", mdisk_size);
654    vu_log(VHOSTMD_INFO, "Using update period of %d seconds",
655                update_period);
656
657    return 0;
658 }
659
660 static int metrics_disk_busy(int fd, int busy)
661 {
662    md_header.busy = (uint32_t)(htonl(busy));
663    
664    lseek(fd, offsetof(mdisk_header, busy), SEEK_SET);
665    write(fd, &(md_header.busy), sizeof(uint32_t));
666    return 0;
667 }
668
669 static int metrics_disk_header_update(int fd, vu_buffer *buf)
670 {
671    uint32_t sum;
672
673    md_header.sig = htonl(MDISK_SIGNATURE);
674    md_header.length = 0;
675    sum = md_header.sum = 0;
676    
677    if (buf) {
678       md_header.length = htonl(buf->use);
679       md_header.sum = sum = htonl(vu_buffer_checksum(buf));
680    }
681
682    if (lseek(fd, offsetof(mdisk_header, sig), SEEK_SET) == -1)
683        goto error;
684    if (write(fd, &(md_header.sig), sizeof(uint32_t)) != sizeof(uint32_t)) {
685        vu_log(VHOSTMD_ERR, "Error writing metrics disk header sig: %s",
686                strerror(errno));
687        goto error;
688    }
689
690    if (lseek(fd, offsetof(mdisk_header, sum), SEEK_SET) == -1)
691        goto error;
692    if (write(fd, &sum, sizeof(uint32_t)) != sizeof(uint32_t)) {
693        vu_log(VHOSTMD_ERR, "Error writing metrics disk header sum: %s",
694                strerror(errno));
695        goto error;
696    }
697
698    if (lseek(fd, offsetof(mdisk_header, length), SEEK_SET) == -1)
699        goto error;
700    if (write(fd, &(md_header.length), sizeof(uint32_t)) != sizeof(uint32_t)) {
701        vu_log(VHOSTMD_ERR, "Error writing metrics disk header length: %s",
702                strerror(errno));
703        goto error;
704    }
705
706    return 0;
707 error:
708    return -1;
709 }
710
711 static int metrics_disk_update(int fd, vu_buffer *buf)
712 {
713    if (buf->use > MDISK_SIZE) {
714       vu_log(VHOSTMD_ERR, "Metrics data is larger than metrics disk");
715       return -1;
716    }
717       
718    metrics_disk_busy(fd, 1);
719    metrics_disk_header_update(fd, buf);
720    lseek(fd, MDISK_HEADER_SIZE, SEEK_SET);
721    write(fd, buf->content, buf->use);
722    metrics_disk_busy(fd, 0);
723
724    return 0;
725 }
726
727 static int metrics_free()
728 {
729    metric *m = metrics;
730    metric *m_old;
731
732    while (m) {
733       if (m->name)
734          free(m->name);
735       if (m->action)
736          free(m->action);
737       if (m->value)
738          free(m->value);
739       if (m->type_str)
740          free(m->type_str);
741       m_old = m;
742       m = m->next;
743       free(m_old);
744    }
745    return 0;
746 }
747
748 static void metrics_disk_close(int fd)
749 {
750    if (fd != -1)
751       close(fd);
752    if (mdisk_path) {
753       free(mdisk_path);
754    }
755    if (search_path)
756       free(search_path);
757    metrics_free();
758 }
759
760 static int metrics_disk_create(void)
761 {
762    char *dir = NULL;
763    char *tmp;
764    char *buf = NULL;
765    int fd = -1;
766    int i;
767    int size = mdisk_size - MDISK_HEADER_SIZE;
768    
769    /* create directory */
770    if ((tmp = strrchr(mdisk_path, '/'))) {
771       dir = strndup(mdisk_path, tmp - mdisk_path);
772       if (dir == NULL) {
773          vu_log(VHOSTMD_ERR, "Unable to allocate memory");
774          return -1;
775       }
776
777       if ((mkdir(dir, 0700) < 0) && (errno != EEXIST)) {
778          vu_log(VHOSTMD_ERR, "Unable to create directory '%s' "
779                      "for metrics disk: %s", dir, strerror(errno));
780          goto error;
781       }
782    }
783    
784    /* buffer for zero filling disk to requested size */
785    buf = calloc(1, MDISK_SIZE_MIN);
786    if (buf == NULL) {
787       vu_log(VHOSTMD_ERR, "Unable to allocate memory");
788       goto error;
789    }
790
791    /* create disk */
792    fd = open(mdisk_path, O_RDWR | O_CREAT | O_TRUNC,
793              (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH));
794    if (fd < 0) {
795       vu_log(VHOSTMD_ERR, "Failed to open metrics disk: %s",
796                   strerror(errno));
797       goto error;
798    }
799
800    /* write header, mark disk as busy */
801    metrics_disk_busy(fd, 1);
802    if (metrics_disk_header_update(fd, NULL)) {
803       vu_log(VHOSTMD_ERR, "Error writing metrics disk header");
804       goto error;
805    }
806
807    /* truncate to a possible new size */
808    ftruncate(fd, mdisk_size);
809
810    /* zero fill metrics data */
811    lseek(fd, MDISK_HEADER_SIZE, SEEK_SET);
812    for (i = 0; i < size / MDISK_SIZE_MIN; i++)
813       if (write(fd, buf, MDISK_SIZE_MIN) != MDISK_SIZE_MIN) {
814          vu_log(VHOSTMD_ERR, "Error creating disk of requested "
815                      "size: %s", strerror(errno));
816          goto error;
817       }
818    if (write(fd, buf, size % MDISK_SIZE_MIN) <
819        size % MDISK_SIZE_MIN ) {
820          vu_log(VHOSTMD_ERR, "Error creating disk of requested "
821                      "size: %s", strerror(errno));
822          goto error;
823    }
824
825    free(dir);
826    free(buf);
827    return fd;
828
829  error:
830    free(dir);
831    free(buf);
832    close(fd);
833    return -1;
834 }
835
836 static int metrics_host_get(vu_buffer *buf)
837 {
838    metric *m = metrics;
839    
840    while (m) {
841       if (m->ctx != METRIC_CONTEXT_HOST) {
842          m = m->next;
843          continue;
844       }
845       
846       if (metric_xml(m, buf))
847          vu_log(VHOSTMD_ERR, "Error retrieving metric %s", m->name);
848          
849       m = m->next;
850    }
851    return 0;
852 }
853
854 static int metrics_vm_get(vu_vm *vm, vu_buffer *buf)
855 {
856    metric *m = metrics;
857
858    while (m) {
859       if (m->ctx != METRIC_CONTEXT_VM) {
860          m = m->next;
861          continue;
862       }
863       m->vm = vm;
864       if (metric_xml(m, buf))
865          vu_log(VHOSTMD_ERR, "Error retrieving metric %s", m->name);
866          
867       m = m->next;
868    }
869    return 0;
870 }
871
872 static int metrics_vms_get(vu_buffer *buf, int **ids)
873 {
874    int num_vms;
875    int i;
876
877    *ids = NULL;
878    
879    num_vms = vu_num_vms();
880    if (num_vms == -1)
881       return -1;
882    if (num_vms == 0)
883       return 0;
884    
885    *ids = calloc(num_vms, sizeof(int));
886    if (*ids == NULL) {
887       vu_log (VHOSTMD_ERR, "calloc: %m");
888       return -1;
889    }
890
891    num_vms = vu_get_vms(*ids, num_vms);
892    for (i = 0; i < num_vms; i++) {
893       vu_vm *vm;
894       
895       vm = vu_get_vm((*ids)[i]);
896       if (vm == NULL)
897          continue;
898       
899       metrics_vm_get(vm, buf);
900       vu_vm_free(vm);
901    }
902    
903    return num_vms;
904 }
905
906
907 /* Main run loop for vhostmd */
908 static int vhostmd_run(int diskfd)
909 {
910    int *ids = NULL;
911    int num_vms = 0;
912    vu_buffer *buf = NULL;
913    
914    if (vu_buffer_create(&buf, MDISK_SIZE_MIN - MDISK_HEADER_SIZE)) {
915       vu_log(VHOSTMD_ERR, "Unable to allocate memory");
916       return -1;
917    }
918    
919    while (!down) {
920       vu_buffer_add(buf, "<metrics>\n", -1);
921       if (metrics_host_get(buf))
922          vu_log(VHOSTMD_ERR, "Failed to collect host metrics "
923                      "during update");
924
925       if ((num_vms = metrics_vms_get(buf, &ids)) == -1)
926          vu_log(VHOSTMD_ERR, "Failed to collect vm metrics "
927                      "during update");
928
929       vu_buffer_add(buf, "</metrics>\n", -1);
930       if (transports & VBD)
931          metrics_disk_update(diskfd, buf);
932 #ifdef WITH_XENSTORE
933       if (transports & XENSTORE)
934          metrics_xenstore_update(buf->content, ids, num_vms);
935 #endif
936       if (ids)
937           free(ids);
938       sleep(update_period);
939       vu_buffer_erase(buf);
940    }
941    vu_buffer_delete(buf);
942    return 0;
943 }
944
945 static void usage(const char *argv0)
946 {
947    fprintf (stderr,
948             "\n\
949    Usage:\n\
950    %s [options]\n\
951    \n\
952    Options:\n\
953    -v | --verbose         Verbose messages.\n\
954    -c | --connect <uri>   Set the libvirt URI.\n\
955    -d | --no-daemonize    Process will not daemonize - useful for debugging.\n\
956    -f | --config <file>   Configuration file.\n\
957    -p | --pid-file <file> PID file.\n\
958    -u | --user <user>     Drop root privs and run as <user>.\n\
959    \n\
960    Host metrics gathering daemon:\n\
961    \n",
962             argv0);
963 }
964
965 int main(int argc, char *argv[])
966 {
967    struct sigaction sig_action;
968    const char *pfile = NULL;
969    const char *cfile = SYSCONF_DIR "/vhostmd/vhostmd.conf";
970    int verbose = 0;
971    int no_daemonize = 0;
972    int ret = 1;
973    int mdisk_fd = -1;
974    const char *user = NULL;
975
976    struct option opts[] = {
977       { "verbose", no_argument, &verbose, 1},
978       { "no-daemonize", no_argument, &no_daemonize, 1},
979       { "config", required_argument, NULL, 'f'},
980       { "pid-file", required_argument, NULL, 'p'},
981       { "user", required_argument, NULL, 'u'},
982 #ifndef XENCTRL
983       { "connect", required_argument, NULL, 'c'},
984 #endif
985       { "help", no_argument, NULL, '?' },
986       {0, 0, 0, 0}
987    };
988
989    while (1) {
990       int optidx = 0;
991       int c;
992
993       c = getopt_long(argc, argv, "c:df:p:u:v", opts, &optidx);
994
995       if (c == -1)
996          break;
997
998       switch (c) {
999          case 0:
1000             /* Got one of the flags */
1001             break;
1002          case 'v':
1003             verbose = 1;
1004             break;
1005          case 'd':
1006             no_daemonize = 1;
1007             break;
1008          case 'f':
1009             cfile = optarg;
1010             break;
1011          case 'p':
1012             pfile = optarg;
1013             break;
1014          case 'u':
1015             user = optarg;
1016             break;
1017 #ifndef XENCTRL
1018          case 'c':
1019             libvirt_uri = optarg;
1020             break;
1021 #endif
1022          case '?':
1023             usage(argv[0]);
1024             return 2;
1025          default:
1026             fprintf(stderr, "hostmetricsd: unknown option: %c\n", c);
1027             exit(1);
1028       }
1029    }
1030
1031    vu_log_init(no_daemonize, verbose);
1032    
1033    if (!no_daemonize) {
1034       if (daemonize() < 0) {
1035          vu_log(VHOSTMD_ERR, "Failed to fork as daemon: %s",
1036                      strerror(errno));
1037          goto out;
1038       }
1039    }
1040
1041    /* If running as root and no PID file is set, use the default */
1042    if (pfile == NULL &&
1043        getuid() == 0 &&
1044        pid_file[0] != '\0')
1045       pfile = pid_file;
1046
1047    /* If we have a pidfile set, claim it now, exiting if already taken */
1048    if (pfile != NULL &&
1049        write_pid_file(pfile) < 0)
1050       goto out;
1051
1052    sig_action.sa_sigaction = sig_handler;
1053    sig_action.sa_flags = SA_SIGINFO;
1054    sigemptyset(&sig_action.sa_mask);
1055
1056    sigaction(SIGHUP, &sig_action, NULL);
1057    sigaction(SIGINT, &sig_action, NULL);
1058    sigaction(SIGQUIT, &sig_action, NULL);
1059    sigaction(SIGTERM, &sig_action, NULL);
1060
1061    xmlInitParser();
1062
1063    if (validate_config_file(cfile)) {
1064       xmlCleanupParser();
1065       vu_log(VHOSTMD_ERR, "Config file: %s, fails DTD validation ", cfile);
1066       goto out;
1067    }
1068
1069    if (parse_config_file(cfile)) {
1070       xmlCleanupParser();
1071       vu_log(VHOSTMD_ERR, "Please ensure configuration file "
1072                   "%s exists and is valid", cfile);
1073       goto out;
1074    }
1075
1076    xmlCleanupParser();
1077
1078    if (check_config()) {
1079       vu_log(VHOSTMD_ERR, "Configuration file %s contains invalid "
1080                   "setting(s)", cfile);
1081       goto out;
1082    }
1083
1084 #ifdef LIBXENSTAT
1085    if (xen_metrics(&metrics)) {
1086       vu_log(VHOSTMD_ERR, "Unable to load xen specific metrics, ignoring");
1087    }
1088 #endif
1089       
1090    if ((mdisk_fd = metrics_disk_create()) < 0) {
1091       vu_log(VHOSTMD_ERR, "Failed to create metrics disk %s", mdisk_path);
1092       goto out;
1093    }
1094
1095    /* Drop root privileges if requested.  Note: We do this after
1096     * opening the metrics disk, parsing the config file, etc.
1097     */
1098    if (user) {
1099        struct passwd *pw;
1100
1101        errno = 0;
1102        pw = getpwnam (user);
1103        if (!pw) {
1104            vu_log (VHOSTMD_ERR, "No entry in password file for user %s: %m",
1105                    user);
1106            goto out;
1107        }
1108
1109        if (pw->pw_uid == 0 || pw->pw_gid == 0) {
1110            vu_log (VHOSTMD_ERR, "Cannot switch to root using the '-u' command line flag.");
1111            goto out;
1112        }
1113
1114        if (setgid (pw->pw_gid) == -1) {
1115            vu_log (VHOSTMD_ERR, "setgid: %d: %m", pw->pw_gid);
1116            goto out;
1117        }
1118
1119        if (initgroups (user, pw->pw_gid) == -1) {
1120            vu_log (VHOSTMD_ERR, "initgroups: %m");
1121            goto out;
1122        }
1123
1124        if (setuid (pw->pw_uid) == -1) {
1125            vu_log (VHOSTMD_ERR, "setuid: %d: %m", pw->pw_uid);
1126            goto out;
1127        }
1128
1129        vu_log (VHOSTMD_INFO, "Switched to uid:gid %d:%d",
1130                pw->pw_uid, pw->pw_gid);
1131    }
1132
1133    ret = vhostmd_run(mdisk_fd);
1134
1135  out:
1136    metrics_disk_close(mdisk_fd);
1137    if (pfile)
1138       unlink(pfile);
1139
1140    vu_log_close();
1141    vu_vm_connect_close();
1142
1143    return ret;
1144 }