blob: f326349831f67cacfec5cda7e118aa635524a832 [file] [log] [blame]
/*
*
* Connection Manager
*
* Copyright (c) 2010 The Chromium OS Authors.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include "connman.h"
#define CONNMAN_API_SUBJECT_TO_CHANGE
#include <connman/plugin.h>
#include <connman/notifier.h>
#include <connman/resolver.h>
#include <connman/log.h>
#include <glib.h>
#define _DBG_RESOLVFILES(fmt, arg...) DBG(DBG_RESOLVFILES, fmt, ## arg)
/*
* The resolvfiles resolver back end creates one file per interface
* suitable for being used as a resolv.conf file, based on the
* supplied domain, search list, and servers. As the default interface
* changes, STATEDIR/resolv.conf is updated to be a link to the
* current default. The link is removed when the default interface is
* NULL.
*/
/* Base name of the resolver file in the state directory */
#define RESOLVFILE_NAME "resolv.conf"
/* Full path to write the resolver file */
#define RESOLVFILE STATEDIR "/" RESOLVFILE_NAME
#define RESOLVFILE_NEW RESOLVFILE ".new"
static int resolvfiles_set(const struct connman_resolver_state *entry)
{
char *filename;
int ret;
_DBG_RESOLVFILES("append interface %s domain %s search_domains %p"
" servers %p", entry->interface, entry->domain,
entry->search_domains, entry->servers);
filename = g_strdup_printf("%s.%s", RESOLVFILE, entry->interface);
ret = connman_resolvfile_write(filename, entry->domain,
entry->search_domains, entry->servers);
g_free(filename);
return ret;
}
static int resolvfiles_unset(const struct connman_resolver_state *entry)
{
char *filename;
struct stat sb;
_DBG_RESOLVFILES("remove interface %s", entry->interface);
filename = g_strdup_printf("%s.%s", RESOLVFILE, entry->interface);
if (stat(filename, &sb) >= 0) {
/*
* If the per-interface resolv.conf file is still
* installed as the default just truncate it so
* subsequent writes update the active resolv.conf.
* Otherwise unlink the file. This handles the
* case where ipconfig records are changed via the
* dbus as blindly unlink'ing the file will leave
* stale data in resolv.conf.
*
* NB: This is inherently racey; we assume we are
* the only process mucking with these files.
*/
if (sb.st_nlink > 1) {
if (truncate(filename, 0) < 0)
connman_error("%s: truncate: %s",
entry->interface, strerror(errno));
} else
(void) unlink(filename);
}
g_free(filename);
return 0;
}
static struct connman_resolver resolvfiles_resolver = {
.name = "resolvfiles",
.priority = CONNMAN_RESOLVER_PRIORITY_DEFAULT,
.set = resolvfiles_set,
.unset = resolvfiles_unset,
};
static void resolvfiles_default_changed(struct connman_service *service)
{
const char *interface;
char *filename;
int err;
if (service == NULL) {
/*
* There is no default; this can happen when no
* devices are operational (e.g. wired unplugged
* and wireless down). Be paranoid here: there
* should be no servers as everything should be
* torn down; this is just in case.
*/
unlink(RESOLVFILE);
return;
}
interface = connman_service_get_interface(service);
if (interface == NULL) {
connman_error("%s: no interface for service %p", __func__,
service);
return;
}
_DBG_RESOLVFILES("Default changed to interface %s", interface);
filename = g_strdup_printf("%s.%s", RESOLVFILE, interface);
/* Set up resolv.conf link, atomically replacing existing one. */
err = link(filename, RESOLVFILE_NEW);
if (err != 0) {
connman_error("%s: Couldn't create %s link to %s (%s)",
__func__, RESOLVFILE_NEW, filename,
strerror(errno));
g_free(filename);
return;
}
g_free(filename);
err = rename(RESOLVFILE_NEW, RESOLVFILE);
if (err != 0)
connman_error("%s: Couldn't rename %s to %s",
__func__, RESOLVFILE_NEW, RESOLVFILE);
/* Clean up either in case of error or in case resolv.conf was
* already a link to resolv.conf.$INTERFACE, in which case
* rename() is a no-op.
*/
unlink(RESOLVFILE_NEW);
}
static struct connman_notifier resolvfiles_notifier = {
.name = "resolvfiles",
.priority = CONNMAN_NOTIFIER_PRIORITY_HIGH,
.default_changed= resolvfiles_default_changed,
};
static int resolvfiles_init(void)
{
int err;
err = connman_resolver_register(&resolvfiles_resolver);
if (err < 0)
return err;
return connman_notifier_register(&resolvfiles_notifier);
}
static void resolvfiles_exit(void)
{
connman_resolver_unregister(&resolvfiles_resolver);
connman_notifier_unregister(&resolvfiles_notifier);
unlink(RESOLVFILE);
}
CONNMAN_PLUGIN_DEFINE(resolvfiles, "Multiple-interface resolv.conf manager",
VERSION, CONNMAN_PLUGIN_PRIORITY_DEFAULT,
resolvfiles_init, resolvfiles_exit)