virdnsmasq.c 20.0 KB
Newer Older
S
Satoru SATOH 已提交
1
/*
2 3
 * virdnsmasq.c: Helper APIs for managing dnsmasq
 *
E
Eric Blake 已提交
4
 * Copyright (C) 2007-2013 Red Hat, Inc.
S
Satoru SATOH 已提交
5 6 7 8 9 10 11 12 13 14 15 16 17
 * Copyright (C) 2010 Satoru SATOH <satoru.satoh@gmail.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
18
 * License along with this library.  If not, see
O
Osier Yang 已提交
19
 * <http://www.gnu.org/licenses/>.
S
Satoru SATOH 已提交
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
 *
 * Based on iptables.c
 */

#include <config.h>

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <limits.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <signal.h>

#ifdef HAVE_PATHS_H
# include <paths.h>
#endif

#include "internal.h"
#include "datatypes.h"
44
#include "virbitmap.h"
45
#include "virdnsmasq.h"
46
#include "virutil.h"
47
#include "vircommand.h"
48
#include "viralloc.h"
49
#include "virerror.h"
50
#include "virlog.h"
E
Eric Blake 已提交
51
#include "virfile.h"
52
#include "virstring.h"
S
Satoru SATOH 已提交
53 54 55

#define VIR_FROM_THIS VIR_FROM_NETWORK
#define DNSMASQ_HOSTSFILE_SUFFIX "hostsfile"
56
#define DNSMASQ_ADDNHOSTSFILE_SUFFIX "addnhosts"
S
Satoru SATOH 已提交
57 58 59 60 61 62 63

static void
dhcphostFree(dnsmasqDhcpHost *host)
{
    VIR_FREE(host->host);
}

64 65 66
static void
addnhostFree(dnsmasqAddnHost *host)
{
67
    size_t i;
68 69 70 71 72 73 74 75 76 77

    for (i = 0; i < host->nhostnames; i++)
        VIR_FREE(host->hostnames[i]);
    VIR_FREE(host->hostnames);
    VIR_FREE(host->ip);
}

static void
addnhostsFree(dnsmasqAddnHostsfile *addnhostsfile)
{
78
    size_t i;
79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100

    if (addnhostsfile->hosts) {
        for (i = 0; i < addnhostsfile->nhosts; i++)
            addnhostFree(&addnhostsfile->hosts[i]);

        VIR_FREE(addnhostsfile->hosts);

        addnhostsfile->nhosts = 0;
    }

    VIR_FREE(addnhostsfile->path);

    VIR_FREE(addnhostsfile);
}

static int
addnhostsAdd(dnsmasqAddnHostsfile *addnhostsfile,
             virSocketAddr *ip,
             const char *name)
{
    char *ipstr = NULL;
    int idx = -1;
101
    size_t i;
102

103
    if (!(ipstr = virSocketAddrFormat(ip)))
104 105 106 107 108 109 110 111 112 113 114
        return -1;

    for (i = 0; i < addnhostsfile->nhosts; i++) {
        if (STREQ((const char *)addnhostsfile->hosts[i].ip, (const char *)ipstr)) {
            idx = i;
            break;
        }
    }

    if (idx < 0) {
        if (VIR_REALLOC_N(addnhostsfile->hosts, addnhostsfile->nhosts + 1) < 0)
115
            goto error;
116 117 118

        idx = addnhostsfile->nhosts;
        if (VIR_ALLOC(addnhostsfile->hosts[idx].hostnames) < 0)
119
            goto error;
120

121 122
        if (VIR_STRDUP(addnhostsfile->hosts[idx].ip, ipstr) < 0)
            goto error;
123 124 125 126 127 128

        addnhostsfile->hosts[idx].nhostnames = 0;
        addnhostsfile->nhosts++;
    }

    if (VIR_REALLOC_N(addnhostsfile->hosts[idx].hostnames, addnhostsfile->hosts[idx].nhostnames + 1) < 0)
129
        goto error;
130

131 132 133
    if (VIR_STRDUP(addnhostsfile->hosts[idx].hostnames[addnhostsfile->hosts[idx].nhostnames],
                   name) < 0)
        goto error;
134 135 136 137 138 139 140

    VIR_FREE(ipstr);

    addnhostsfile->hosts[idx].nhostnames++;

    return 0;

141
 error:
142 143 144 145 146 147 148 149 150 151
    VIR_FREE(ipstr);
    return -1;
}

static dnsmasqAddnHostsfile *
addnhostsNew(const char *name,
             const char *config_dir)
{
    dnsmasqAddnHostsfile *addnhostsfile;

152
    if (VIR_ALLOC(addnhostsfile) < 0)
153 154 155 156 157 158
        return NULL;

    addnhostsfile->hosts = NULL;
    addnhostsfile->nhosts = 0;

    if (virAsprintf(&addnhostsfile->path, "%s/%s.%s", config_dir, name,
159
                    DNSMASQ_ADDNHOSTSFILE_SUFFIX) < 0)
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176
        goto error;

    return addnhostsfile;

 error:
    addnhostsFree(addnhostsfile);
    return NULL;
}

static int
addnhostsWrite(const char *path,
               dnsmasqAddnHost *hosts,
               unsigned int nhosts)
{
    char *tmp;
    FILE *f;
    bool istmp = true;
177
    size_t i, j;
178 179
    int rc = 0;

180 181 182
    /* even if there are 0 hosts, create a 0 length file, to allow
     * for runtime addition.
     */
183 184 185 186 187 188 189

    if (virAsprintf(&tmp, "%s.new", path) < 0)
        return -ENOMEM;

    if (!(f = fopen(tmp, "w"))) {
        istmp = false;
        if (!(f = fopen(path, "w"))) {
190
            rc = -errno;
191 192 193 194 195 196
            goto cleanup;
        }
    }

    for (i = 0; i < nhosts; i++) {
        if (fputs(hosts[i].ip, f) == EOF || fputc('\t', f) == EOF) {
197
            rc = -errno;
198 199 200 201 202 203 204 205
            VIR_FORCE_FCLOSE(f);

            if (istmp)
                unlink(tmp);

            goto cleanup;
        }

206 207
        for (j = 0; j < hosts[i].nhostnames; j++) {
            if (fputs(hosts[i].hostnames[j], f) == EOF || fputc('\t', f) == EOF) {
208
                rc = -errno;
209 210 211 212 213 214 215 216 217 218
                VIR_FORCE_FCLOSE(f);

                if (istmp)
                    unlink(tmp);

                goto cleanup;
            }
        }

        if (fputc('\n', f) == EOF) {
219
            rc = -errno;
220 221 222 223 224 225 226 227 228 229
            VIR_FORCE_FCLOSE(f);

            if (istmp)
                unlink(tmp);

            goto cleanup;
        }
    }

    if (VIR_FCLOSE(f) == EOF) {
230
        rc = -errno;
231 232 233
        goto cleanup;
    }

234 235 236 237
    if (istmp && rename(tmp, path) < 0) {
        rc = -errno;
        unlink(tmp);
        goto cleanup;
238 239 240 241 242 243 244 245 246 247 248 249 250 251 252
    }

 cleanup:
    VIR_FREE(tmp);

    return rc;
}

static int
addnhostsSave(dnsmasqAddnHostsfile *addnhostsfile)
{
    int err = addnhostsWrite(addnhostsfile->path, addnhostsfile->hosts,
                             addnhostsfile->nhosts);

    if (err < 0) {
253
        virReportSystemError(-err, _("cannot write config file '%s'"),
254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275
                             addnhostsfile->path);
        return -1;
    }

    return 0;
}

static int
genericFileDelete(char *path)
{
    if (!virFileExists(path))
        return 0;

    if (unlink(path) < 0) {
        virReportSystemError(errno, _("cannot remove config file '%s'"),
                             path);
        return -1;
    }

    return 0;
}

S
Satoru SATOH 已提交
276 277 278
static void
hostsfileFree(dnsmasqHostsfile *hostsfile)
{
279
    size_t i;
S
Satoru SATOH 已提交
280 281 282 283 284 285 286 287 288 289 290 291 292 293 294

    if (hostsfile->hosts) {
        for (i = 0; i < hostsfile->nhosts; i++)
            dhcphostFree(&hostsfile->hosts[i]);

        VIR_FREE(hostsfile->hosts);

        hostsfile->nhosts = 0;
    }

    VIR_FREE(hostsfile->path);

    VIR_FREE(hostsfile);
}

G
Gene Czarcinski 已提交
295 296 297
/* Note:  There are many additional dhcp-host specifications
 * supported by dnsmasq.  There are only the basic ones.
 */
S
Satoru SATOH 已提交
298 299 300
static int
hostsfileAdd(dnsmasqHostsfile *hostsfile,
             const char *mac,
301
             virSocketAddr *ip,
G
Gene Czarcinski 已提交
302
             const char *name,
303
             const char *id,
G
Gene Czarcinski 已提交
304
             bool ipv6)
S
Satoru SATOH 已提交
305
{
E
Eric Blake 已提交
306
    char *ipstr = NULL;
S
Satoru SATOH 已提交
307
    if (VIR_REALLOC_N(hostsfile->hosts, hostsfile->nhosts + 1) < 0)
308
        goto error;
S
Satoru SATOH 已提交
309

310
    if (!(ipstr = virSocketAddrFormat(ip)))
311 312
        return -1;

G
Gene Czarcinski 已提交
313 314
    /* the first test determines if it is a dhcpv6 host */
    if (ipv6) {
315 316 317
        if (name && id) {
            if (virAsprintf(&hostsfile->hosts[hostsfile->nhosts].host,
                            "id:%s,%s,[%s]", id, name, ipstr) < 0)
318
                goto error;
319 320 321
        } else if (name && !id) {
            if (virAsprintf(&hostsfile->hosts[hostsfile->nhosts].host,
                            "%s,[%s]", name, ipstr) < 0)
322
                goto error;
323 324 325
        } else if (!name && id) {
            if (virAsprintf(&hostsfile->hosts[hostsfile->nhosts].host,
                            "id:%s,[%s]", id, ipstr) < 0)
326
                goto error;
327 328
        }
    } else if (name && mac) {
S
Satoru SATOH 已提交
329
        if (virAsprintf(&hostsfile->hosts[hostsfile->nhosts].host, "%s,%s,%s",
G
Gene Czarcinski 已提交
330
                        mac, ipstr, name) < 0)
331
            goto error;
G
Gene Czarcinski 已提交
332 333 334
    } else if (name && !mac){
        if (virAsprintf(&hostsfile->hosts[hostsfile->nhosts].host, "%s,%s",
                        name, ipstr) < 0)
335
            goto error;
S
Satoru SATOH 已提交
336 337
    } else {
        if (virAsprintf(&hostsfile->hosts[hostsfile->nhosts].host, "%s,%s",
G
Gene Czarcinski 已提交
338
                        mac, ipstr) < 0)
339
            goto error;
S
Satoru SATOH 已提交
340
    }
341
    VIR_FREE(ipstr);
S
Satoru SATOH 已提交
342 343 344 345 346

    hostsfile->nhosts++;

    return 0;

347
 error:
348
    VIR_FREE(ipstr);
S
Satoru SATOH 已提交
349 350 351 352 353 354 355 356 357
    return -1;
}

static dnsmasqHostsfile *
hostsfileNew(const char *name,
             const char *config_dir)
{
    dnsmasqHostsfile *hostsfile;

358
    if (VIR_ALLOC(hostsfile) < 0)
S
Satoru SATOH 已提交
359 360 361 362 363 364
        return NULL;

    hostsfile->hosts = NULL;
    hostsfile->nhosts = 0;

    if (virAsprintf(&hostsfile->path, "%s/%s.%s", config_dir, name,
365
                    DNSMASQ_HOSTSFILE_SUFFIX) < 0)
S
Satoru SATOH 已提交
366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382
        goto error;

    return hostsfile;

 error:
    hostsfileFree(hostsfile);
    return NULL;
}

static int
hostsfileWrite(const char *path,
               dnsmasqDhcpHost *hosts,
               unsigned int nhosts)
{
    char *tmp;
    FILE *f;
    bool istmp = true;
383
    size_t i;
S
Satoru SATOH 已提交
384 385
    int rc = 0;

386 387 388
    /* even if there are 0 hosts, create a 0 length file, to allow
     * for runtime addition.
     */
S
Satoru SATOH 已提交
389 390

    if (virAsprintf(&tmp, "%s.new", path) < 0)
391
        return -ENOMEM;
S
Satoru SATOH 已提交
392 393 394 395

    if (!(f = fopen(tmp, "w"))) {
        istmp = false;
        if (!(f = fopen(path, "w"))) {
396
            rc = -errno;
S
Satoru SATOH 已提交
397 398 399 400 401 402
            goto cleanup;
        }
    }

    for (i = 0; i < nhosts; i++) {
        if (fputs(hosts[i].host, f) == EOF || fputc('\n', f) == EOF) {
403
            rc = -errno;
404
            VIR_FORCE_FCLOSE(f);
S
Satoru SATOH 已提交
405 406 407 408 409 410 411 412

            if (istmp)
                unlink(tmp);

            goto cleanup;
        }
    }

413
    if (VIR_FCLOSE(f) == EOF) {
414
        rc = -errno;
S
Satoru SATOH 已提交
415 416 417
        goto cleanup;
    }

418 419 420 421
    if (istmp && rename(tmp, path) < 0) {
        rc = -errno;
        unlink(tmp);
        goto cleanup;
S
Satoru SATOH 已提交
422 423 424 425 426 427 428 429 430 431 432 433
    }

 cleanup:
    VIR_FREE(tmp);

    return rc;
}

static int
hostsfileSave(dnsmasqHostsfile *hostsfile)
{
    int err = hostsfileWrite(hostsfile->path, hostsfile->hosts,
434
                             hostsfile->nhosts);
S
Satoru SATOH 已提交
435 436

    if (err < 0) {
437
        virReportSystemError(-err, _("cannot write config file '%s'"),
438
                             hostsfile->path);
S
Satoru SATOH 已提交
439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457
        return -1;
    }

    return 0;
}

/**
 * dnsmasqContextNew:
 *
 * Create a new Dnsmasq context
 *
 * Returns a pointer to the new structure or NULL in case of error
 */
dnsmasqContext *
dnsmasqContextNew(const char *network_name,
                  const char *config_dir)
{
    dnsmasqContext *ctx;

458
    if (VIR_ALLOC(ctx) < 0)
S
Satoru SATOH 已提交
459 460
        return NULL;

461
    if (VIR_STRDUP(ctx->config_dir, config_dir) < 0)
462 463
        goto error;

S
Satoru SATOH 已提交
464 465
    if (!(ctx->hostsfile = hostsfileNew(network_name, config_dir)))
        goto error;
466 467
    if (!(ctx->addnhostsfile = addnhostsNew(network_name, config_dir)))
        goto error;
S
Satoru SATOH 已提交
468 469 470 471 472 473 474 475 476 477 478 479

    return ctx;

 error:
    dnsmasqContextFree(ctx);
    return NULL;
}

/**
 * dnsmasqContextFree:
 * @ctx: pointer to the dnsmasq context
 *
E
Eric Blake 已提交
480
 * Free the resources associated with a dnsmasq context
S
Satoru SATOH 已提交
481 482 483 484 485 486 487
 */
void
dnsmasqContextFree(dnsmasqContext *ctx)
{
    if (!ctx)
        return;

488 489
    VIR_FREE(ctx->config_dir);

S
Satoru SATOH 已提交
490 491
    if (ctx->hostsfile)
        hostsfileFree(ctx->hostsfile);
492 493
    if (ctx->addnhostsfile)
        addnhostsFree(ctx->addnhostsfile);
S
Satoru SATOH 已提交
494 495 496 497 498 499 500 501

    VIR_FREE(ctx);
}

/**
 * dnsmasqAddDhcpHost:
 * @ctx: pointer to the dnsmasq context for each network
 * @mac: pointer to the string contains mac address of the host
502
 * @ip: pointer to the socket address contains ip of the host
S
Satoru SATOH 已提交
503 504 505 506
 * @name: pointer to the string contains hostname of the host or NULL
 *
 * Add dhcp-host entry.
 */
507
int
S
Satoru SATOH 已提交
508 509
dnsmasqAddDhcpHost(dnsmasqContext *ctx,
                   const char *mac,
510
                   virSocketAddr *ip,
G
Gene Czarcinski 已提交
511
                   const char *name,
512
                   const char *id,
G
Gene Czarcinski 已提交
513
                   bool ipv6)
S
Satoru SATOH 已提交
514
{
515
    return hostsfileAdd(ctx->hostsfile, mac, ip, name, id, ipv6);
S
Satoru SATOH 已提交
516 517
}

518 519 520 521 522 523 524 525 526
/*
 * dnsmasqAddHost:
 * @ctx: pointer to the dnsmasq context for each network
 * @ip: pointer to the socket address contains ip of the host
 * @name: pointer to the string contains hostname of the host
 *
 * Add additional host entry.
 */

527
int
528 529 530 531
dnsmasqAddHost(dnsmasqContext *ctx,
               virSocketAddr *ip,
               const char *name)
{
532
    return addnhostsAdd(ctx->addnhostsfile, ip, name);
533 534
}

S
Satoru SATOH 已提交
535 536 537 538 539 540 541 542 543
/**
 * dnsmasqSave:
 * @ctx: pointer to the dnsmasq context for each network
 *
 * Saves all the configurations associated with a context to disk.
 */
int
dnsmasqSave(const dnsmasqContext *ctx)
{
544 545
    int ret = 0;

546 547
    if (virFileMakePath(ctx->config_dir) < 0) {
        virReportSystemError(errno, _("cannot create config directory '%s'"),
548 549 550 551
                             ctx->config_dir);
        return -1;
    }

S
Satoru SATOH 已提交
552
    if (ctx->hostsfile)
553 554 555 556 557
        ret = hostsfileSave(ctx->hostsfile);
    if (ret == 0) {
        if (ctx->addnhostsfile)
            ret = addnhostsSave(ctx->addnhostsfile);
    }
S
Satoru SATOH 已提交
558

559
    return ret;
S
Satoru SATOH 已提交
560 561 562 563 564 565 566 567 568 569 570 571
}


/**
 * dnsmasqDelete:
 * @ctx: pointer to the dnsmasq context for each network
 *
 * Delete all the configuration files associated with a context.
 */
int
dnsmasqDelete(const dnsmasqContext *ctx)
{
572 573
    int ret = 0;

S
Satoru SATOH 已提交
574
    if (ctx->hostsfile)
575 576 577
        ret = genericFileDelete(ctx->hostsfile->path);
    if (ctx->addnhostsfile)
        ret = genericFileDelete(ctx->addnhostsfile->path);
S
Satoru SATOH 已提交
578

579
    return ret;
S
Satoru SATOH 已提交
580 581 582 583 584 585 586 587 588
}

/**
 * dnsmasqReload:
 * @pid: the pid of the target dnsmasq process
 *
 * Reloads all the configurations associated to a context
 */
int
589
dnsmasqReload(pid_t pid ATTRIBUTE_UNUSED)
S
Satoru SATOH 已提交
590
{
591
#ifndef WIN32
S
Satoru SATOH 已提交
592 593
    if (kill(pid, SIGHUP) != 0) {
        virReportSystemError(errno,
594
            _("Failed to make dnsmasq (PID: %d) reload config files."),
S
Satoru SATOH 已提交
595 596 597
            pid);
        return -1;
    }
598
#endif /* WIN32 */
S
Satoru SATOH 已提交
599 600 601

    return 0;
}
602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629

/*
 * dnsmasqCapabilities functions - provide useful information about the
 * version of dnsmasq on this machine.
 *
 */
struct _dnsmasqCaps {
    virObject object;
    char *binaryPath;
    bool noRefresh;
    time_t mtime;
    virBitmapPtr flags;
    unsigned long version;
};

static virClassPtr dnsmasqCapsClass;

static void
dnsmasqCapsDispose(void *obj)
{
    dnsmasqCapsPtr caps = obj;

    virBitmapFree(caps->flags);
    VIR_FREE(caps->binaryPath);
}

static int dnsmasqCapsOnceInit(void)
{
630 631
    if (!(dnsmasqCapsClass = virClassNew(virClassForObject(),
                                         "dnsmasqCaps",
632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658
                                         sizeof(dnsmasqCaps),
                                         dnsmasqCapsDispose))) {
        return -1;
    }

    return 0;
}

VIR_ONCE_GLOBAL_INIT(dnsmasqCaps)

static void
dnsmasqCapsSet(dnsmasqCapsPtr caps,
               dnsmasqCapsFlags flag)
{
    ignore_value(virBitmapSetBit(caps->flags, flag));
}


#define DNSMASQ_VERSION_STR "Dnsmasq version "

static int
dnsmasqCapsSetFromBuffer(dnsmasqCapsPtr caps, const char *buf)
{
    const char *p;

    caps->noRefresh = true;

659 660
    p = STRSKIP(buf, DNSMASQ_VERSION_STR);
    if (!p)
661 662 663 664 665 666 667 668
       goto fail;
    virSkipSpaces(&p);
    if (virParseVersionString(p, &caps->version, true) < 0)
        goto fail;

    if (strstr(buf, "--bind-dynamic"))
        dnsmasqCapsSet(caps, DNSMASQ_CAPS_BIND_DYNAMIC);

669 670 671 672 673 674 675 676 677 678 679 680 681 682
    /* if this string is a part of the --version output, dnsmasq
     * has been patched to use SO_BINDTODEVICE when listening,
     * so that it will only accept requests that arrived on the
     * listening interface(s)
     */
    if (strstr(buf, "--bind-interfaces with SO_BINDTODEVICE"))
        dnsmasqCapsSet(caps, DNSMASQ_CAPS_BINDTODEVICE);

    VIR_INFO("dnsmasq version is %d.%d, --bind-dynamic is %spresent, "
             "SO_BINDTODEVICE is %sin use",
             (int)caps->version / 1000000,
             (int)(caps->version % 1000000) / 1000,
             dnsmasqCapsGet(caps, DNSMASQ_CAPS_BIND_DYNAMIC) ? "" : "NOT ",
             dnsmasqCapsGet(caps, DNSMASQ_CAPS_BIND_DYNAMIC) ? "" : "NOT ");
683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761
    return 0;

fail:
    p = strchrnul(buf, '\n');
    virReportError(VIR_ERR_INTERNAL_ERROR,
                   _("cannot parse %s version number in '%.*s'"),
                   caps->binaryPath, (int) (p - buf), buf);
    return -1;

}

static int
dnsmasqCapsSetFromFile(dnsmasqCapsPtr caps, const char *path)
{
    int ret = -1;
    char *buf = NULL;

    if (virFileReadAll(path, 1024 * 1024, &buf) < 0)
        goto cleanup;

    ret = dnsmasqCapsSetFromBuffer(caps, buf);

cleanup:
    VIR_FREE(buf);
    return ret;
}

static int
dnsmasqCapsRefreshInternal(dnsmasqCapsPtr caps, bool force)
{
    int ret = -1;
    struct stat sb;
    virCommandPtr cmd = NULL;
    char *help = NULL, *version = NULL, *complete = NULL;

    if (!caps || caps->noRefresh)
        return 0;

    if (stat(caps->binaryPath, &sb) < 0) {
        virReportSystemError(errno, _("Cannot check dnsmasq binary %s"),
                             caps->binaryPath);
        return -1;
    }
    if (!force && caps->mtime == sb.st_mtime) {
        return 0;
    }
    caps->mtime = sb.st_mtime;

    /* Make sure the binary we are about to try exec'ing exists.
     * Technically we could catch the exec() failure, but that's
     * in a sub-process so it's hard to feed back a useful error.
     */
    if (!virFileIsExecutable(caps->binaryPath)) {
        virReportSystemError(errno, _("dnsmasq binary %s is not executable"),
                             caps->binaryPath);
        goto cleanup;
    }

    cmd = virCommandNewArgList(caps->binaryPath, "--version", NULL);
    virCommandSetOutputBuffer(cmd, &version);
    virCommandAddEnvPassCommon(cmd);
    virCommandClearCaps(cmd);
    if (virCommandRun(cmd, NULL) < 0) {
        virReportSystemError(errno, _("failed to run '%s --version': %s"),
                             caps->binaryPath, version);
        goto cleanup;
    }
    virCommandFree(cmd);

    cmd = virCommandNewArgList(caps->binaryPath, "--help", NULL);
    virCommandSetOutputBuffer(cmd, &help);
    virCommandAddEnvPassCommon(cmd);
    virCommandClearCaps(cmd);
    if (virCommandRun(cmd, NULL) < 0) {
        virReportSystemError(errno, _("failed to run '%s --help': %s"),
                             caps->binaryPath, help);
        goto cleanup;
    }

762
    if (virAsprintf(&complete, "%s\n%s", version, help) < 0)
763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783
        goto cleanup;

    ret = dnsmasqCapsSetFromBuffer(caps, complete);

cleanup:
    virCommandFree(cmd);
    VIR_FREE(help);
    VIR_FREE(version);
    VIR_FREE(complete);
    return ret;
}

static dnsmasqCapsPtr
dnsmasqCapsNewEmpty(const char *binaryPath)
{
    dnsmasqCapsPtr caps;

    if (dnsmasqCapsInitialize() < 0)
        return NULL;
    if (!(caps = virObjectNew(dnsmasqCapsClass)))
        return NULL;
784
    if (!(caps->flags = virBitmapNew(DNSMASQ_CAPS_LAST)))
785
        goto error;
786
    if (VIR_STRDUP(caps->binaryPath, binaryPath ? binaryPath : DNSMASQ) < 0)
787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881
        goto error;
    return caps;

error:
    virObjectUnref(caps);
    return NULL;
}

dnsmasqCapsPtr
dnsmasqCapsNewFromBuffer(const char *buf, const char *binaryPath)
{
    dnsmasqCapsPtr caps = dnsmasqCapsNewEmpty(binaryPath);

    if (!caps)
        return NULL;

    if (dnsmasqCapsSetFromBuffer(caps, buf) < 0) {
        virObjectUnref(caps);
        return NULL;
    }
    return caps;
}

dnsmasqCapsPtr
dnsmasqCapsNewFromFile(const char *dataPath, const char *binaryPath)
{
    dnsmasqCapsPtr caps = dnsmasqCapsNewEmpty(binaryPath);

    if (!caps)
        return NULL;

    if (dnsmasqCapsSetFromFile(caps, dataPath) < 0) {
        virObjectUnref(caps);
        return NULL;
    }
    return caps;
}

dnsmasqCapsPtr
dnsmasqCapsNewFromBinary(const char *binaryPath)
{
    dnsmasqCapsPtr caps = dnsmasqCapsNewEmpty(binaryPath);

    if (!caps)
        return NULL;

    if (dnsmasqCapsRefreshInternal(caps, true) < 0) {
        virObjectUnref(caps);
        return NULL;
    }
    return caps;
}

/** dnsmasqCapsRefresh:
 *
 *   Refresh an existing caps object if the binary has changed. If
 *   there isn't yet a caps object (if it's NULL), create a new one.
 *
 *   Returns 0 on success, -1 on failure
 */
int
dnsmasqCapsRefresh(dnsmasqCapsPtr *caps, const char *binaryPath)
{
    if (!*caps) {
        *caps = dnsmasqCapsNewFromBinary(binaryPath);
        return *caps ? 0 : -1;
    }
    return dnsmasqCapsRefreshInternal(*caps, false);
}

const char *
dnsmasqCapsGetBinaryPath(dnsmasqCapsPtr caps)
{
    return caps ? caps->binaryPath : DNSMASQ;
}

unsigned long
dnsmasqCapsGetVersion(dnsmasqCapsPtr caps)
{
    if (caps)
        return caps->version;
    else
        return 0;
}

bool
dnsmasqCapsGet(dnsmasqCapsPtr caps, dnsmasqCapsFlags flag)
{
    bool b;

    if (!caps || virBitmapGetBit(caps->flags, flag, &b) < 0)
        return false;
    else
        return b;
}