1 /*
2  * virdnsmasq.c: Helper APIs for managing dnsmasq
3  *
4  * Copyright (C) 2007-2013 Red Hat, Inc.
5  * Copyright (C) 2010 Satoru SATOH <satoru.satoh@gmail.com>
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library.  If not, see
19  * <http://www.gnu.org/licenses/>.
20  *
21  * Based on iptables.c
22  */
23 
24 #include <config.h>
25 
26 #include <stdarg.h>
27 #include <unistd.h>
28 #include <fcntl.h>
29 #include <sys/types.h>
30 #include <sys/stat.h>
31 #include <signal.h>
32 
33 #include "internal.h"
34 #include "datatypes.h"
35 #include "virbitmap.h"
36 #include "virdnsmasq.h"
37 #include "virutil.h"
38 #include "vircommand.h"
39 #include "viralloc.h"
40 #include "virerror.h"
41 #include "virlog.h"
42 #include "virfile.h"
43 #include "virstring.h"
44 
45 #define VIR_FROM_THIS VIR_FROM_NETWORK
46 
47 VIR_LOG_INIT("util.dnsmasq");
48 
49 #define DNSMASQ_HOSTSFILE_SUFFIX "hostsfile"
50 #define DNSMASQ_ADDNHOSTSFILE_SUFFIX "addnhosts"
51 
52 static void
dhcphostFreeContent(dnsmasqDhcpHost * host)53 dhcphostFreeContent(dnsmasqDhcpHost *host)
54 {
55     g_free(host->host);
56 }
57 
58 static void
addnhostFreeContent(dnsmasqAddnHost * host)59 addnhostFreeContent(dnsmasqAddnHost *host)
60 {
61     size_t i;
62 
63     for (i = 0; i < host->nhostnames; i++)
64         g_free(host->hostnames[i]);
65     g_free(host->hostnames);
66     g_free(host->ip);
67 }
68 
69 static void
addnhostsFree(dnsmasqAddnHostsfile * addnhostsfile)70 addnhostsFree(dnsmasqAddnHostsfile *addnhostsfile)
71 {
72     size_t i;
73 
74     if (addnhostsfile->hosts) {
75         for (i = 0; i < addnhostsfile->nhosts; i++)
76             addnhostFreeContent(&addnhostsfile->hosts[i]);
77 
78         g_free(addnhostsfile->hosts);
79 
80         addnhostsfile->nhosts = 0;
81     }
82 
83     g_free(addnhostsfile->path);
84 
85     g_free(addnhostsfile);
86 }
87 
88 static int
addnhostsAdd(dnsmasqAddnHostsfile * addnhostsfile,virSocketAddr * ip,const char * name)89 addnhostsAdd(dnsmasqAddnHostsfile *addnhostsfile,
90              virSocketAddr *ip,
91              const char *name)
92 {
93     char *ipstr = NULL;
94     int idx = -1;
95     size_t i;
96 
97     if (!(ipstr = virSocketAddrFormat(ip)))
98         return -1;
99 
100     for (i = 0; i < addnhostsfile->nhosts; i++) {
101         if (STREQ((const char *)addnhostsfile->hosts[i].ip, (const char *)ipstr)) {
102             idx = i;
103             break;
104         }
105     }
106 
107     if (idx < 0) {
108         VIR_REALLOC_N(addnhostsfile->hosts, addnhostsfile->nhosts + 1);
109 
110         idx = addnhostsfile->nhosts;
111         addnhostsfile->hosts[idx].hostnames = g_new0(char *, 1);
112 
113         addnhostsfile->hosts[idx].ip = g_strdup(ipstr);
114 
115         addnhostsfile->hosts[idx].nhostnames = 0;
116         addnhostsfile->nhosts++;
117     }
118 
119     VIR_REALLOC_N(addnhostsfile->hosts[idx].hostnames, addnhostsfile->hosts[idx].nhostnames + 1);
120 
121     addnhostsfile->hosts[idx].hostnames[addnhostsfile->hosts[idx].nhostnames] = g_strdup(name);
122 
123     VIR_FREE(ipstr);
124 
125     addnhostsfile->hosts[idx].nhostnames++;
126 
127     return 0;
128 }
129 
130 static dnsmasqAddnHostsfile *
addnhostsNew(const char * name,const char * config_dir)131 addnhostsNew(const char *name,
132              const char *config_dir)
133 {
134     dnsmasqAddnHostsfile *addnhostsfile;
135     g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
136 
137     addnhostsfile = g_new0(dnsmasqAddnHostsfile, 1);
138 
139     addnhostsfile->hosts = NULL;
140     addnhostsfile->nhosts = 0;
141 
142     virBufferAsprintf(&buf, "%s", config_dir);
143     virBufferEscapeString(&buf, "/%s", name);
144     virBufferAsprintf(&buf, ".%s", DNSMASQ_ADDNHOSTSFILE_SUFFIX);
145 
146     if (!(addnhostsfile->path = virBufferContentAndReset(&buf)))
147         goto error;
148 
149     return addnhostsfile;
150 
151  error:
152     addnhostsFree(addnhostsfile);
153     return NULL;
154 }
155 
156 static int
addnhostsWrite(const char * path,dnsmasqAddnHost * hosts,unsigned int nhosts)157 addnhostsWrite(const char *path,
158                dnsmasqAddnHost *hosts,
159                unsigned int nhosts)
160 {
161     g_autofree char *tmp = NULL;
162     FILE *f;
163     bool istmp = true;
164     size_t i, j;
165     int rc = 0;
166 
167     /* even if there are 0 hosts, create a 0 length file, to allow
168      * for runtime addition.
169      */
170 
171     tmp = g_strdup_printf("%s.new", path);
172 
173     if (!(f = fopen(tmp, "w"))) {
174         istmp = false;
175         if (!(f = fopen(path, "w")))
176             return -errno;
177     }
178 
179     for (i = 0; i < nhosts; i++) {
180         if (fputs(hosts[i].ip, f) == EOF || fputc('\t', f) == EOF) {
181             rc = -errno;
182             VIR_FORCE_FCLOSE(f);
183 
184             if (istmp)
185                 unlink(tmp);
186 
187             return rc;
188         }
189 
190         for (j = 0; j < hosts[i].nhostnames; j++) {
191             if (fputs(hosts[i].hostnames[j], f) == EOF || fputc('\t', f) == EOF) {
192                 rc = -errno;
193                 VIR_FORCE_FCLOSE(f);
194 
195                 if (istmp)
196                     unlink(tmp);
197 
198                 return rc;
199             }
200         }
201 
202         if (fputc('\n', f) == EOF) {
203             rc = -errno;
204             VIR_FORCE_FCLOSE(f);
205 
206             if (istmp)
207                 unlink(tmp);
208 
209             return rc;
210         }
211     }
212 
213     if (VIR_FCLOSE(f) == EOF)
214         return -errno;
215 
216     if (istmp && rename(tmp, path) < 0) {
217         rc = -errno;
218         unlink(tmp);
219         return rc;
220     }
221 
222     return 0;
223 }
224 
225 static int
addnhostsSave(dnsmasqAddnHostsfile * addnhostsfile)226 addnhostsSave(dnsmasqAddnHostsfile *addnhostsfile)
227 {
228     int err = addnhostsWrite(addnhostsfile->path, addnhostsfile->hosts,
229                              addnhostsfile->nhosts);
230 
231     if (err < 0) {
232         virReportSystemError(-err, _("cannot write config file '%s'"),
233                              addnhostsfile->path);
234         return -1;
235     }
236 
237     return 0;
238 }
239 
240 static int
genericFileDelete(char * path)241 genericFileDelete(char *path)
242 {
243     if (!virFileExists(path))
244         return 0;
245 
246     if (unlink(path) < 0) {
247         virReportSystemError(errno, _("cannot remove config file '%s'"),
248                              path);
249         return -1;
250     }
251 
252     return 0;
253 }
254 
255 static void
hostsfileFree(dnsmasqHostsfile * hostsfile)256 hostsfileFree(dnsmasqHostsfile *hostsfile)
257 {
258     size_t i;
259 
260     if (hostsfile->hosts) {
261         for (i = 0; i < hostsfile->nhosts; i++)
262             dhcphostFreeContent(&hostsfile->hosts[i]);
263 
264         g_free(hostsfile->hosts);
265 
266         hostsfile->nhosts = 0;
267     }
268 
269     g_free(hostsfile->path);
270 
271     g_free(hostsfile);
272 }
273 
274 /* Note:  There are many additional dhcp-host specifications
275  * supported by dnsmasq.  There are only the basic ones.
276  */
277 static int
hostsfileAdd(dnsmasqHostsfile * hostsfile,const char * mac,virSocketAddr * ip,const char * name,const char * id,const char * leasetime,bool ipv6)278 hostsfileAdd(dnsmasqHostsfile *hostsfile,
279              const char *mac,
280              virSocketAddr *ip,
281              const char *name,
282              const char *id,
283              const char *leasetime,
284              bool ipv6)
285 {
286     g_autofree char *ipstr = NULL;
287     g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
288 
289     VIR_REALLOC_N(hostsfile->hosts, hostsfile->nhosts + 1);
290 
291     if (!(ipstr = virSocketAddrFormat(ip)))
292         return -1;
293 
294     /* the first test determines if it is a dhcpv6 host */
295     if (ipv6) {
296         if (name && id) {
297             virBufferAsprintf(&buf, "id:%s,%s", id, name);
298         } else if (name && !id) {
299             virBufferAsprintf(&buf, "%s", name);
300         } else if (!name && id) {
301             virBufferAsprintf(&buf, "id:%s", id);
302         }
303         virBufferAsprintf(&buf, ",[%s]", ipstr);
304     } else if (name && mac) {
305         virBufferAsprintf(&buf, "%s,%s,%s", mac, ipstr, name);
306     } else if (name && !mac) {
307         virBufferAsprintf(&buf, "%s,%s", name, ipstr);
308     } else {
309         virBufferAsprintf(&buf, "%s,%s", mac, ipstr);
310     }
311 
312     if (leasetime)
313         virBufferAsprintf(&buf, ",%s", leasetime);
314 
315     if (!(hostsfile->hosts[hostsfile->nhosts].host = virBufferContentAndReset(&buf)))
316         return -1;
317 
318     hostsfile->nhosts++;
319 
320     return 0;
321 }
322 
323 static dnsmasqHostsfile *
hostsfileNew(const char * name,const char * config_dir)324 hostsfileNew(const char *name,
325              const char *config_dir)
326 {
327     dnsmasqHostsfile *hostsfile;
328     g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
329 
330     hostsfile = g_new0(dnsmasqHostsfile, 1);
331 
332     hostsfile->hosts = NULL;
333     hostsfile->nhosts = 0;
334 
335     virBufferAsprintf(&buf, "%s", config_dir);
336     virBufferEscapeString(&buf, "/%s", name);
337     virBufferAsprintf(&buf, ".%s", DNSMASQ_HOSTSFILE_SUFFIX);
338 
339     if (!(hostsfile->path = virBufferContentAndReset(&buf)))
340         goto error;
341     return hostsfile;
342 
343  error:
344     hostsfileFree(hostsfile);
345     return NULL;
346 }
347 
348 static int
hostsfileWrite(const char * path,dnsmasqDhcpHost * hosts,unsigned int nhosts)349 hostsfileWrite(const char *path,
350                dnsmasqDhcpHost *hosts,
351                unsigned int nhosts)
352 {
353     g_autofree char *tmp = NULL;
354     FILE *f;
355     bool istmp = true;
356     size_t i;
357     int rc = 0;
358 
359     /* even if there are 0 hosts, create a 0 length file, to allow
360      * for runtime addition.
361      */
362 
363     tmp = g_strdup_printf("%s.new", path);
364 
365     if (!(f = fopen(tmp, "w"))) {
366         istmp = false;
367         if (!(f = fopen(path, "w")))
368             return -errno;
369     }
370 
371     for (i = 0; i < nhosts; i++) {
372         if (fputs(hosts[i].host, f) == EOF || fputc('\n', f) == EOF) {
373             rc = -errno;
374             VIR_FORCE_FCLOSE(f);
375 
376             if (istmp)
377                 unlink(tmp);
378 
379             return rc;
380         }
381     }
382 
383     if (VIR_FCLOSE(f) == EOF)
384         return -errno;
385 
386     if (istmp && rename(tmp, path) < 0) {
387         rc = -errno;
388         unlink(tmp);
389         return rc;
390     }
391 
392     return 0;
393 }
394 
395 static int
hostsfileSave(dnsmasqHostsfile * hostsfile)396 hostsfileSave(dnsmasqHostsfile *hostsfile)
397 {
398     int err = hostsfileWrite(hostsfile->path, hostsfile->hosts,
399                              hostsfile->nhosts);
400 
401     if (err < 0) {
402         virReportSystemError(-err, _("cannot write config file '%s'"),
403                              hostsfile->path);
404         return -1;
405     }
406 
407     return 0;
408 }
409 
410 /**
411  * dnsmasqContextNew:
412  *
413  * Create a new Dnsmasq context
414  *
415  * Returns a pointer to the new structure or NULL in case of error
416  */
417 dnsmasqContext *
dnsmasqContextNew(const char * network_name,const char * config_dir)418 dnsmasqContextNew(const char *network_name,
419                   const char *config_dir)
420 {
421     dnsmasqContext *ctx;
422 
423     ctx = g_new0(dnsmasqContext, 1);
424 
425     ctx->config_dir = g_strdup(config_dir);
426 
427     if (!(ctx->hostsfile = hostsfileNew(network_name, config_dir)))
428         goto error;
429     if (!(ctx->addnhostsfile = addnhostsNew(network_name, config_dir)))
430         goto error;
431 
432     return ctx;
433 
434  error:
435     dnsmasqContextFree(ctx);
436     return NULL;
437 }
438 
439 /**
440  * dnsmasqContextFree:
441  * @ctx: pointer to the dnsmasq context
442  *
443  * Free the resources associated with a dnsmasq context
444  */
445 void
dnsmasqContextFree(dnsmasqContext * ctx)446 dnsmasqContextFree(dnsmasqContext *ctx)
447 {
448     if (!ctx)
449         return;
450 
451     g_free(ctx->config_dir);
452 
453     if (ctx->hostsfile)
454         hostsfileFree(ctx->hostsfile);
455     if (ctx->addnhostsfile)
456         addnhostsFree(ctx->addnhostsfile);
457 
458     g_free(ctx);
459 }
460 
461 /**
462  * dnsmasqAddDhcpHost:
463  * @ctx: pointer to the dnsmasq context for each network
464  * @mac: pointer to the string contains mac address of the host
465  * @ip: pointer to the socket address contains ip of the host
466  * @name: pointer to the string contains hostname of the host or NULL
467  *
468  * Add dhcp-host entry.
469  */
470 int
dnsmasqAddDhcpHost(dnsmasqContext * ctx,const char * mac,virSocketAddr * ip,const char * name,const char * id,const char * leasetime,bool ipv6)471 dnsmasqAddDhcpHost(dnsmasqContext *ctx,
472                    const char *mac,
473                    virSocketAddr *ip,
474                    const char *name,
475                    const char *id,
476                    const char *leasetime,
477                    bool ipv6)
478 {
479     return hostsfileAdd(ctx->hostsfile, mac, ip, name, id, leasetime, ipv6);
480 }
481 
482 /*
483  * dnsmasqAddHost:
484  * @ctx: pointer to the dnsmasq context for each network
485  * @ip: pointer to the socket address contains ip of the host
486  * @name: pointer to the string contains hostname of the host
487  *
488  * Add additional host entry.
489  */
490 
491 int
dnsmasqAddHost(dnsmasqContext * ctx,virSocketAddr * ip,const char * name)492 dnsmasqAddHost(dnsmasqContext *ctx,
493                virSocketAddr *ip,
494                const char *name)
495 {
496     return addnhostsAdd(ctx->addnhostsfile, ip, name);
497 }
498 
499 /**
500  * dnsmasqSave:
501  * @ctx: pointer to the dnsmasq context for each network
502  *
503  * Saves all the configurations associated with a context to disk.
504  */
505 int
dnsmasqSave(const dnsmasqContext * ctx)506 dnsmasqSave(const dnsmasqContext *ctx)
507 {
508     int ret = 0;
509 
510     if (g_mkdir_with_parents(ctx->config_dir, 0777) < 0) {
511         virReportSystemError(errno, _("cannot create config directory '%s'"),
512                              ctx->config_dir);
513         return -1;
514     }
515 
516     if (ctx->hostsfile)
517         ret = hostsfileSave(ctx->hostsfile);
518     if (ret == 0) {
519         if (ctx->addnhostsfile)
520             ret = addnhostsSave(ctx->addnhostsfile);
521     }
522 
523     return ret;
524 }
525 
526 
527 /**
528  * dnsmasqDelete:
529  * @ctx: pointer to the dnsmasq context for each network
530  *
531  * Delete all the configuration files associated with a context.
532  */
533 int
dnsmasqDelete(const dnsmasqContext * ctx)534 dnsmasqDelete(const dnsmasqContext *ctx)
535 {
536     int ret = 0;
537 
538     if (ctx->hostsfile)
539         ret = genericFileDelete(ctx->hostsfile->path);
540     if (ctx->addnhostsfile)
541         ret = genericFileDelete(ctx->addnhostsfile->path);
542 
543     return ret;
544 }
545 
546 /**
547  * dnsmasqReload:
548  * @pid: the pid of the target dnsmasq process
549  *
550  * Reloads all the configurations associated to a context
551  */
552 int
dnsmasqReload(pid_t pid G_GNUC_UNUSED)553 dnsmasqReload(pid_t pid G_GNUC_UNUSED)
554 {
555 #ifndef WIN32
556     if (kill(pid, SIGHUP) != 0) {
557         virReportSystemError(errno,
558                              _("Failed to make dnsmasq (PID: %d)"
559                                " reload config files."),
560                              pid);
561         return -1;
562     }
563 #endif /* WIN32 */
564 
565     return 0;
566 }
567 
568 /*
569  * dnsmasqCapabilities functions - provide useful information about the
570  * version of dnsmasq on this machine.
571  *
572  */
573 struct _dnsmasqCaps {
574     virObject parent;
575     char *binaryPath;
576     bool noRefresh;
577     time_t mtime;
578     virBitmap *flags;
579     unsigned long version;
580 };
581 
582 static virClass *dnsmasqCapsClass;
583 
584 static void
dnsmasqCapsDispose(void * obj)585 dnsmasqCapsDispose(void *obj)
586 {
587     dnsmasqCaps *caps = obj;
588 
589     virBitmapFree(caps->flags);
590     g_free(caps->binaryPath);
591 }
592 
dnsmasqCapsOnceInit(void)593 static int dnsmasqCapsOnceInit(void)
594 {
595     if (!VIR_CLASS_NEW(dnsmasqCaps, virClassForObject()))
596         return -1;
597 
598     return 0;
599 }
600 
601 VIR_ONCE_GLOBAL_INIT(dnsmasqCaps);
602 
603 static void
dnsmasqCapsSet(dnsmasqCaps * caps,dnsmasqCapsFlags flag)604 dnsmasqCapsSet(dnsmasqCaps *caps,
605                dnsmasqCapsFlags flag)
606 {
607     ignore_value(virBitmapSetBit(caps->flags, flag));
608 }
609 
610 
611 #define DNSMASQ_VERSION_STR "Dnsmasq version "
612 
613 static int
dnsmasqCapsSetFromBuffer(dnsmasqCaps * caps,const char * buf)614 dnsmasqCapsSetFromBuffer(dnsmasqCaps *caps, const char *buf)
615 {
616     int len;
617     const char *p;
618 
619     caps->noRefresh = true;
620 
621     p = STRSKIP(buf, DNSMASQ_VERSION_STR);
622     if (!p)
623        goto fail;
624 
625     virSkipToDigit(&p);
626 
627     if (virParseVersionString(p, &caps->version, true) < 0)
628         goto fail;
629 
630     if (strstr(buf, "--bind-dynamic"))
631         dnsmasqCapsSet(caps, DNSMASQ_CAPS_BIND_DYNAMIC);
632 
633     /* if this string is a part of the --version output, dnsmasq
634      * has been patched to use SO_BINDTODEVICE when listening,
635      * so that it will only accept requests that arrived on the
636      * listening interface(s)
637      */
638     if (strstr(buf, "--bind-interfaces with SO_BINDTODEVICE"))
639         dnsmasqCapsSet(caps, DNSMASQ_CAPS_BINDTODEVICE);
640 
641     if (strstr(buf, "--ra-param"))
642         dnsmasqCapsSet(caps, DNSMASQ_CAPS_RA_PARAM);
643 
644     VIR_INFO("dnsmasq version is %d.%d, --bind-dynamic is %spresent, "
645              "SO_BINDTODEVICE is %sin use, --ra-param is %spresent",
646              (int)caps->version / 1000000,
647              (int)(caps->version % 1000000) / 1000,
648              dnsmasqCapsGet(caps, DNSMASQ_CAPS_BIND_DYNAMIC) ? "" : "NOT ",
649              dnsmasqCapsGet(caps, DNSMASQ_CAPS_BINDTODEVICE) ? "" : "NOT ",
650              dnsmasqCapsGet(caps, DNSMASQ_CAPS_RA_PARAM) ? "" : "NOT ");
651     return 0;
652 
653  fail:
654     p = strchr(buf, '\n');
655     if (!p)
656         len = strlen(buf);
657     else
658         len = p - buf;
659     virReportError(VIR_ERR_INTERNAL_ERROR,
660                    _("cannot parse %s version number in '%.*s'"),
661                    caps->binaryPath, len, buf);
662     return -1;
663 
664 }
665 
666 static int
dnsmasqCapsRefreshInternal(dnsmasqCaps * caps,bool force)667 dnsmasqCapsRefreshInternal(dnsmasqCaps *caps, bool force)
668 {
669     int ret = -1;
670     struct stat sb;
671     virCommand *cmd = NULL;
672     g_autofree char *help = NULL;
673     g_autofree char *version = NULL;
674     g_autofree char *complete = NULL;
675 
676     if (!caps || caps->noRefresh)
677         return 0;
678 
679     if (stat(caps->binaryPath, &sb) < 0) {
680         virReportSystemError(errno, _("Cannot check dnsmasq binary %s"),
681                              caps->binaryPath);
682         return -1;
683     }
684     if (!force && caps->mtime == sb.st_mtime)
685         return 0;
686     caps->mtime = sb.st_mtime;
687 
688     /* Make sure the binary we are about to try exec'ing exists.
689      * Technically we could catch the exec() failure, but that's
690      * in a sub-process so it's hard to feed back a useful error.
691      */
692     if (!virFileIsExecutable(caps->binaryPath)) {
693         virReportSystemError(errno, _("dnsmasq binary %s is not executable"),
694                              caps->binaryPath);
695         goto cleanup;
696     }
697 
698     cmd = virCommandNewArgList(caps->binaryPath, "--version", NULL);
699     virCommandSetOutputBuffer(cmd, &version);
700     virCommandAddEnvPassCommon(cmd);
701     virCommandClearCaps(cmd);
702     if (virCommandRun(cmd, NULL) < 0)
703         goto cleanup;
704     virCommandFree(cmd);
705 
706     cmd = virCommandNewArgList(caps->binaryPath, "--help", NULL);
707     virCommandSetOutputBuffer(cmd, &help);
708     virCommandAddEnvPassCommon(cmd);
709     virCommandClearCaps(cmd);
710     if (virCommandRun(cmd, NULL) < 0)
711         goto cleanup;
712 
713     complete = g_strdup_printf("%s\n%s", version, help);
714 
715     ret = dnsmasqCapsSetFromBuffer(caps, complete);
716 
717  cleanup:
718     virCommandFree(cmd);
719     return ret;
720 }
721 
722 static dnsmasqCaps *
dnsmasqCapsNewEmpty(const char * binaryPath)723 dnsmasqCapsNewEmpty(const char *binaryPath)
724 {
725     dnsmasqCaps *caps;
726 
727     if (dnsmasqCapsInitialize() < 0)
728         return NULL;
729     if (!(caps = virObjectNew(dnsmasqCapsClass)))
730         return NULL;
731     caps->flags = virBitmapNew(DNSMASQ_CAPS_LAST);
732     caps->binaryPath = g_strdup(binaryPath ? binaryPath : DNSMASQ);
733     return caps;
734 }
735 
736 dnsmasqCaps *
dnsmasqCapsNewFromBuffer(const char * buf)737 dnsmasqCapsNewFromBuffer(const char *buf)
738 {
739     dnsmasqCaps *caps = dnsmasqCapsNewEmpty(DNSMASQ);
740 
741     if (!caps)
742         return NULL;
743 
744     if (dnsmasqCapsSetFromBuffer(caps, buf) < 0) {
745         virObjectUnref(caps);
746         return NULL;
747     }
748     return caps;
749 }
750 
751 dnsmasqCaps *
dnsmasqCapsNewFromBinary(void)752 dnsmasqCapsNewFromBinary(void)
753 {
754     dnsmasqCaps *caps = dnsmasqCapsNewEmpty(DNSMASQ);
755 
756     if (!caps)
757         return NULL;
758 
759     if (dnsmasqCapsRefreshInternal(caps, true) < 0) {
760         virObjectUnref(caps);
761         return NULL;
762     }
763     return caps;
764 }
765 
766 const char *
dnsmasqCapsGetBinaryPath(dnsmasqCaps * caps)767 dnsmasqCapsGetBinaryPath(dnsmasqCaps *caps)
768 {
769     return caps ? caps->binaryPath : DNSMASQ;
770 }
771 
772 unsigned long
dnsmasqCapsGetVersion(dnsmasqCaps * caps)773 dnsmasqCapsGetVersion(dnsmasqCaps *caps)
774 {
775     if (caps)
776         return caps->version;
777     else
778         return 0;
779 }
780 
781 bool
dnsmasqCapsGet(dnsmasqCaps * caps,dnsmasqCapsFlags flag)782 dnsmasqCapsGet(dnsmasqCaps *caps, dnsmasqCapsFlags flag)
783 {
784     return caps && virBitmapIsBitSet(caps->flags, flag);
785 }
786 
787 
788 /** dnsmasqDhcpHostsToString:
789  *
790  *   Turns a vector of dnsmasqDhcpHost into the string that is ought to be
791  *   stored in the hostsfile, this functionality is split to make hostsfiles
792  *   testable. Returns NULL if nhosts is 0.
793  */
794 char *
dnsmasqDhcpHostsToString(dnsmasqDhcpHost * hosts,unsigned int nhosts)795 dnsmasqDhcpHostsToString(dnsmasqDhcpHost *hosts,
796                          unsigned int nhosts)
797 {
798     size_t i;
799     g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
800 
801     for (i = 0; i < nhosts; i++)
802         virBufferAsprintf(&buf, "%s\n", hosts[i].host);
803 
804     return virBufferContentAndReset(&buf);
805 }
806