mirror of
https://gitlab.com/libvirt/libvirt.git
synced 2024-11-02 11:21:12 +00:00
0b73a763f3
Originally, only a host name was used to associate a DHCPv6 request with a specific IPv6 address. Further testing demonstrates that this is an unreliable method and, instead, a client-id or DUID needs to be used. According to DHCPv6 standards, this id can be a duid-LLT, duid-LL, or duid-UUID even though dnsmasq will accept almost any text string. Although validity checking of a specified string makes sure it is hexadecimal notation with bytes separated by colons, there is no rigorous check to make sure it meets the standard. Documentation and schemas have been updated. Signed-off-by: Gene Czarcinski <gene@czarc.net> Signed-off-by: Laine Stump <laine@laine.org>
897 lines
20 KiB
C
897 lines
20 KiB
C
/*
|
|
* virdnsmasq.c: Helper APIs for managing dnsmasq
|
|
*
|
|
* Copyright (C) 2007-2012 Red Hat, Inc.
|
|
* 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
|
|
* License along with this library. If not, see
|
|
* <http://www.gnu.org/licenses/>.
|
|
*
|
|
* 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"
|
|
#include "virbitmap.h"
|
|
#include "virdnsmasq.h"
|
|
#include "virutil.h"
|
|
#include "vircommand.h"
|
|
#include "viralloc.h"
|
|
#include "virerror.h"
|
|
#include "virlog.h"
|
|
#include "virfile.h"
|
|
|
|
#define VIR_FROM_THIS VIR_FROM_NETWORK
|
|
#define DNSMASQ_HOSTSFILE_SUFFIX "hostsfile"
|
|
#define DNSMASQ_ADDNHOSTSFILE_SUFFIX "addnhosts"
|
|
|
|
static void
|
|
dhcphostFree(dnsmasqDhcpHost *host)
|
|
{
|
|
VIR_FREE(host->host);
|
|
}
|
|
|
|
static void
|
|
addnhostFree(dnsmasqAddnHost *host)
|
|
{
|
|
int i;
|
|
|
|
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)
|
|
{
|
|
unsigned int i;
|
|
|
|
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;
|
|
int i;
|
|
|
|
if (!(ipstr = virSocketAddrFormat(ip)))
|
|
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)
|
|
goto alloc_error;
|
|
|
|
idx = addnhostsfile->nhosts;
|
|
if (VIR_ALLOC(addnhostsfile->hosts[idx].hostnames) < 0)
|
|
goto alloc_error;
|
|
|
|
if (virAsprintf(&addnhostsfile->hosts[idx].ip, "%s", ipstr) < 0)
|
|
goto alloc_error;
|
|
|
|
addnhostsfile->hosts[idx].nhostnames = 0;
|
|
addnhostsfile->nhosts++;
|
|
}
|
|
|
|
if (VIR_REALLOC_N(addnhostsfile->hosts[idx].hostnames, addnhostsfile->hosts[idx].nhostnames + 1) < 0)
|
|
goto alloc_error;
|
|
|
|
if (virAsprintf(&addnhostsfile->hosts[idx].hostnames[addnhostsfile->hosts[idx].nhostnames], "%s", name) < 0)
|
|
goto alloc_error;
|
|
|
|
VIR_FREE(ipstr);
|
|
|
|
addnhostsfile->hosts[idx].nhostnames++;
|
|
|
|
return 0;
|
|
|
|
alloc_error:
|
|
virReportOOMError();
|
|
VIR_FREE(ipstr);
|
|
return -1;
|
|
}
|
|
|
|
static dnsmasqAddnHostsfile *
|
|
addnhostsNew(const char *name,
|
|
const char *config_dir)
|
|
{
|
|
dnsmasqAddnHostsfile *addnhostsfile;
|
|
|
|
if (VIR_ALLOC(addnhostsfile) < 0) {
|
|
virReportOOMError();
|
|
return NULL;
|
|
}
|
|
|
|
addnhostsfile->hosts = NULL;
|
|
addnhostsfile->nhosts = 0;
|
|
|
|
if (virAsprintf(&addnhostsfile->path, "%s/%s.%s", config_dir, name,
|
|
DNSMASQ_ADDNHOSTSFILE_SUFFIX) < 0) {
|
|
virReportOOMError();
|
|
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;
|
|
unsigned int i, ii;
|
|
int rc = 0;
|
|
|
|
/* even if there are 0 hosts, create a 0 length file, to allow
|
|
* for runtime addition.
|
|
*/
|
|
|
|
if (virAsprintf(&tmp, "%s.new", path) < 0)
|
|
return -ENOMEM;
|
|
|
|
if (!(f = fopen(tmp, "w"))) {
|
|
istmp = false;
|
|
if (!(f = fopen(path, "w"))) {
|
|
rc = -errno;
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < nhosts; i++) {
|
|
if (fputs(hosts[i].ip, f) == EOF || fputc('\t', f) == EOF) {
|
|
rc = -errno;
|
|
VIR_FORCE_FCLOSE(f);
|
|
|
|
if (istmp)
|
|
unlink(tmp);
|
|
|
|
goto cleanup;
|
|
}
|
|
|
|
for (ii = 0; ii < hosts[i].nhostnames; ii++) {
|
|
if (fputs(hosts[i].hostnames[ii], f) == EOF || fputc('\t', f) == EOF) {
|
|
rc = -errno;
|
|
VIR_FORCE_FCLOSE(f);
|
|
|
|
if (istmp)
|
|
unlink(tmp);
|
|
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
if (fputc('\n', f) == EOF) {
|
|
rc = -errno;
|
|
VIR_FORCE_FCLOSE(f);
|
|
|
|
if (istmp)
|
|
unlink(tmp);
|
|
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
if (VIR_FCLOSE(f) == EOF) {
|
|
rc = -errno;
|
|
goto cleanup;
|
|
}
|
|
|
|
if (istmp && rename(tmp, path) < 0) {
|
|
rc = -errno;
|
|
unlink(tmp);
|
|
goto cleanup;
|
|
}
|
|
|
|
cleanup:
|
|
VIR_FREE(tmp);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int
|
|
addnhostsSave(dnsmasqAddnHostsfile *addnhostsfile)
|
|
{
|
|
int err = addnhostsWrite(addnhostsfile->path, addnhostsfile->hosts,
|
|
addnhostsfile->nhosts);
|
|
|
|
if (err < 0) {
|
|
virReportSystemError(-err, _("cannot write config file '%s'"),
|
|
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;
|
|
}
|
|
|
|
static void
|
|
hostsfileFree(dnsmasqHostsfile *hostsfile)
|
|
{
|
|
unsigned int i;
|
|
|
|
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);
|
|
}
|
|
|
|
/* Note: There are many additional dhcp-host specifications
|
|
* supported by dnsmasq. There are only the basic ones.
|
|
*/
|
|
static int
|
|
hostsfileAdd(dnsmasqHostsfile *hostsfile,
|
|
const char *mac,
|
|
virSocketAddr *ip,
|
|
const char *name,
|
|
const char *id,
|
|
bool ipv6)
|
|
{
|
|
char *ipstr = NULL;
|
|
if (VIR_REALLOC_N(hostsfile->hosts, hostsfile->nhosts + 1) < 0)
|
|
goto alloc_error;
|
|
|
|
if (!(ipstr = virSocketAddrFormat(ip)))
|
|
return -1;
|
|
|
|
/* the first test determines if it is a dhcpv6 host */
|
|
if (ipv6) {
|
|
if (name && id) {
|
|
if (virAsprintf(&hostsfile->hosts[hostsfile->nhosts].host,
|
|
"id:%s,%s,[%s]", id, name, ipstr) < 0)
|
|
goto alloc_error;
|
|
} else if (name && !id) {
|
|
if (virAsprintf(&hostsfile->hosts[hostsfile->nhosts].host,
|
|
"%s,[%s]", name, ipstr) < 0)
|
|
goto alloc_error;
|
|
} else if (!name && id) {
|
|
if (virAsprintf(&hostsfile->hosts[hostsfile->nhosts].host,
|
|
"id:%s,[%s]", id, ipstr) < 0)
|
|
goto alloc_error;
|
|
}
|
|
} else if (name && mac) {
|
|
if (virAsprintf(&hostsfile->hosts[hostsfile->nhosts].host, "%s,%s,%s",
|
|
mac, ipstr, name) < 0)
|
|
goto alloc_error;
|
|
} else if (name && !mac){
|
|
if (virAsprintf(&hostsfile->hosts[hostsfile->nhosts].host, "%s,%s",
|
|
name, ipstr) < 0)
|
|
goto alloc_error;
|
|
} else {
|
|
if (virAsprintf(&hostsfile->hosts[hostsfile->nhosts].host, "%s,%s",
|
|
mac, ipstr) < 0)
|
|
goto alloc_error;
|
|
}
|
|
VIR_FREE(ipstr);
|
|
|
|
hostsfile->nhosts++;
|
|
|
|
return 0;
|
|
|
|
alloc_error:
|
|
virReportOOMError();
|
|
VIR_FREE(ipstr);
|
|
return -1;
|
|
}
|
|
|
|
static dnsmasqHostsfile *
|
|
hostsfileNew(const char *name,
|
|
const char *config_dir)
|
|
{
|
|
dnsmasqHostsfile *hostsfile;
|
|
|
|
if (VIR_ALLOC(hostsfile) < 0) {
|
|
virReportOOMError();
|
|
return NULL;
|
|
}
|
|
|
|
hostsfile->hosts = NULL;
|
|
hostsfile->nhosts = 0;
|
|
|
|
if (virAsprintf(&hostsfile->path, "%s/%s.%s", config_dir, name,
|
|
DNSMASQ_HOSTSFILE_SUFFIX) < 0) {
|
|
virReportOOMError();
|
|
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;
|
|
unsigned int i;
|
|
int rc = 0;
|
|
|
|
/* even if there are 0 hosts, create a 0 length file, to allow
|
|
* for runtime addition.
|
|
*/
|
|
|
|
if (virAsprintf(&tmp, "%s.new", path) < 0)
|
|
return -ENOMEM;
|
|
|
|
if (!(f = fopen(tmp, "w"))) {
|
|
istmp = false;
|
|
if (!(f = fopen(path, "w"))) {
|
|
rc = -errno;
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < nhosts; i++) {
|
|
if (fputs(hosts[i].host, f) == EOF || fputc('\n', f) == EOF) {
|
|
rc = -errno;
|
|
VIR_FORCE_FCLOSE(f);
|
|
|
|
if (istmp)
|
|
unlink(tmp);
|
|
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
if (VIR_FCLOSE(f) == EOF) {
|
|
rc = -errno;
|
|
goto cleanup;
|
|
}
|
|
|
|
if (istmp && rename(tmp, path) < 0) {
|
|
rc = -errno;
|
|
unlink(tmp);
|
|
goto cleanup;
|
|
}
|
|
|
|
cleanup:
|
|
VIR_FREE(tmp);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int
|
|
hostsfileSave(dnsmasqHostsfile *hostsfile)
|
|
{
|
|
int err = hostsfileWrite(hostsfile->path, hostsfile->hosts,
|
|
hostsfile->nhosts);
|
|
|
|
if (err < 0) {
|
|
virReportSystemError(-err, _("cannot write config file '%s'"),
|
|
hostsfile->path);
|
|
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;
|
|
|
|
if (VIR_ALLOC(ctx) < 0) {
|
|
virReportOOMError();
|
|
return NULL;
|
|
}
|
|
|
|
if (!(ctx->config_dir = strdup(config_dir))) {
|
|
virReportOOMError();
|
|
goto error;
|
|
}
|
|
|
|
if (!(ctx->hostsfile = hostsfileNew(network_name, config_dir)))
|
|
goto error;
|
|
if (!(ctx->addnhostsfile = addnhostsNew(network_name, config_dir)))
|
|
goto error;
|
|
|
|
return ctx;
|
|
|
|
error:
|
|
dnsmasqContextFree(ctx);
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* dnsmasqContextFree:
|
|
* @ctx: pointer to the dnsmasq context
|
|
*
|
|
* Free the resources associated with a dnsmasq context
|
|
*/
|
|
void
|
|
dnsmasqContextFree(dnsmasqContext *ctx)
|
|
{
|
|
if (!ctx)
|
|
return;
|
|
|
|
VIR_FREE(ctx->config_dir);
|
|
|
|
if (ctx->hostsfile)
|
|
hostsfileFree(ctx->hostsfile);
|
|
if (ctx->addnhostsfile)
|
|
addnhostsFree(ctx->addnhostsfile);
|
|
|
|
VIR_FREE(ctx);
|
|
}
|
|
|
|
/**
|
|
* dnsmasqAddDhcpHost:
|
|
* @ctx: pointer to the dnsmasq context for each network
|
|
* @mac: pointer to the string contains mac address of the host
|
|
* @ip: pointer to the socket address contains ip of the host
|
|
* @name: pointer to the string contains hostname of the host or NULL
|
|
*
|
|
* Add dhcp-host entry.
|
|
*/
|
|
int
|
|
dnsmasqAddDhcpHost(dnsmasqContext *ctx,
|
|
const char *mac,
|
|
virSocketAddr *ip,
|
|
const char *name,
|
|
const char *id,
|
|
bool ipv6)
|
|
{
|
|
return hostsfileAdd(ctx->hostsfile, mac, ip, name, id, ipv6);
|
|
}
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
|
|
int
|
|
dnsmasqAddHost(dnsmasqContext *ctx,
|
|
virSocketAddr *ip,
|
|
const char *name)
|
|
{
|
|
return addnhostsAdd(ctx->addnhostsfile, ip, name);
|
|
}
|
|
|
|
/**
|
|
* 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)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (virFileMakePath(ctx->config_dir) < 0) {
|
|
virReportSystemError(errno, _("cannot create config directory '%s'"),
|
|
ctx->config_dir);
|
|
return -1;
|
|
}
|
|
|
|
if (ctx->hostsfile)
|
|
ret = hostsfileSave(ctx->hostsfile);
|
|
if (ret == 0) {
|
|
if (ctx->addnhostsfile)
|
|
ret = addnhostsSave(ctx->addnhostsfile);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/**
|
|
* dnsmasqDelete:
|
|
* @ctx: pointer to the dnsmasq context for each network
|
|
*
|
|
* Delete all the configuration files associated with a context.
|
|
*/
|
|
int
|
|
dnsmasqDelete(const dnsmasqContext *ctx)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (ctx->hostsfile)
|
|
ret = genericFileDelete(ctx->hostsfile->path);
|
|
if (ctx->addnhostsfile)
|
|
ret = genericFileDelete(ctx->addnhostsfile->path);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* dnsmasqReload:
|
|
* @pid: the pid of the target dnsmasq process
|
|
*
|
|
* Reloads all the configurations associated to a context
|
|
*/
|
|
int
|
|
dnsmasqReload(pid_t pid ATTRIBUTE_UNUSED)
|
|
{
|
|
#ifndef WIN32
|
|
if (kill(pid, SIGHUP) != 0) {
|
|
virReportSystemError(errno,
|
|
_("Failed to make dnsmasq (PID: %d) reload config files."),
|
|
pid);
|
|
return -1;
|
|
}
|
|
#endif /* WIN32 */
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* 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)
|
|
{
|
|
if (!(dnsmasqCapsClass = virClassNew(virClassForObject(),
|
|
"dnsmasqCaps",
|
|
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;
|
|
|
|
p = STRSKIP(buf, DNSMASQ_VERSION_STR);
|
|
if (!p)
|
|
goto fail;
|
|
virSkipSpaces(&p);
|
|
if (virParseVersionString(p, &caps->version, true) < 0)
|
|
goto fail;
|
|
|
|
if (strstr(buf, "--bind-dynamic"))
|
|
dnsmasqCapsSet(caps, DNSMASQ_CAPS_BIND_DYNAMIC);
|
|
|
|
/* 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 ");
|
|
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;
|
|
}
|
|
|
|
if (virAsprintf(&complete, "%s\n%s", version, help) < 0) {
|
|
virReportOOMError();
|
|
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;
|
|
if (!(caps->flags = virBitmapNew(DNSMASQ_CAPS_LAST)))
|
|
goto error;
|
|
if (!(caps->binaryPath = strdup(binaryPath ? binaryPath : DNSMASQ)))
|
|
goto error;
|
|
return caps;
|
|
|
|
error:
|
|
virReportOOMError();
|
|
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;
|
|
}
|