/*
* Copyright 2009 Oracle. All rights reserved.
*
* This file is part of nfs-utils.
*
* nfs-utils is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* nfs-utils 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 nfs-utils. If not, see .
*/
/*
* NSM for Linux.
*
* Callback information and NSM state is stored in files, usually
* under /var/lib/nfs. A database of information contained in local
* files stores NLM callback data and what remote peers to notify of
* reboots.
*
* For each monitored remote peer, a text file is created under the
* directory specified by NSM_MONITOR_DIR. The name of the file
* is a valid DNS hostname. The hostname string must be a valid
* ASCII DNS name, and must not contain slash characters, white space,
* or '\0' (ie. anything that might have some special meaning in a
* path name).
*
* The contents of each file include seven blank-separated fields of
* text, finished with '\n'. The first field contains the network
* address of the NLM service to call back. The current implementation
* supports using only IPv4 addresses, so the only contents of this
* field are a network order IPv4 address expressed in 8 hexadecimal
* characters.
*
* The next four fields are text strings of hexadecimal characters,
* representing:
*
* 2. A 4 byte RPC program number of the NLM service to call back
* 3. A 4 byte RPC version number of the NLM service to call back
* 4. A 4 byte RPC procedure number of the NLM service to call back
* 5. A 16 byte opaque cookie that the NLM service uses to identify
* the monitored host
*
* The sixth field is the monitored host's mon_name, passed to statd
* via an SM_MON request.
*
* The seventh field is the my_name for this peer, which is the
* hostname of the local NLM (currently on Linux, the result of
* `uname -n`). This can be used as the source address/hostname
* when sending SM_NOTIFY requests.
*
* The NSM protocol does not limit the contents of these strings
* in any way except that they must fit into 1024 bytes. Our
* implementation requires that these strings not contain
* white space or '\0'.
*/
#ifdef HAVE_CONFIG_H
#include
#endif
#include
#ifdef HAVE_SYS_CAPABILITY_H
#include
#endif
#include
#include
#include
#include
#include
#ifndef S_SPLINT_S
#include
#endif
#include
#include
#include
#include
#include
#include
#include "xlog.h"
#include "nsm.h"
#define RPCARGSLEN (4 * (8 + 1))
#define LINELEN (RPCARGSLEN + SM_PRIV_SIZE * 2 + 1)
#define NSM_KERNEL_STATE_FILE "/proc/sys/fs/nfs/nsm_local_state"
static char nsm_base_dirname[PATH_MAX] = NSM_DEFAULT_STATEDIR;
#define NSM_MONITOR_DIR "sm"
#define NSM_NOTIFY_DIR "sm.bak"
#define NSM_STATE_FILE "state"
static _Bool
error_check(const int len, const size_t buflen)
{
return (len < 0) || ((size_t)len >= buflen);
}
static _Bool
exact_error_check(const ssize_t len, const size_t buflen)
{
return (len < 0) || ((size_t)len != buflen);
}
/*
* Returns a dynamically allocated, '\0'-terminated buffer
* containing an appropriate pathname, or NULL if an error
* occurs. Caller must free the returned result with free(3).
*/
__attribute__((__malloc__))
static char *
nsm_make_record_pathname(const char *directory, const char *hostname)
{
const char *c;
size_t size;
char *path;
int len;
/*
* Block hostnames that contain characters that have
* meaning to the file system (like '/'), or that can
* be confusing on visual inspection (like ' ').
*/
for (c = hostname; *c != '\0'; c++)
if (*c == '/' || isspace((int)*c) != 0) {
xlog(D_GENERAL, "Hostname contains invalid characters");
return NULL;
}
size = strlen(nsm_base_dirname) + strlen(directory) + strlen(hostname) + 3;
if (size > PATH_MAX) {
xlog(D_GENERAL, "Hostname results in pathname that is too long");
return NULL;
}
path = malloc(size);
if (path == NULL) {
xlog(D_GENERAL, "Failed to allocate memory for pathname");
return NULL;
}
len = snprintf(path, size, "%s/%s/%s",
nsm_base_dirname, directory, hostname);
if (error_check(len, size)) {
xlog(D_GENERAL, "Pathname did not fit in specified buffer");
free(path);
return NULL;
}
return path;
}
/*
* Returns a dynamically allocated, '\0'-terminated buffer
* containing an appropriate pathname, or NULL if an error
* occurs. Caller must free the returned result with free(3).
*/
__attribute__((__malloc__))
static char *
nsm_make_pathname(const char *directory)
{
size_t size;
char *path;
int len;
size = strlen(nsm_base_dirname) + strlen(directory) + 2;
if (size > PATH_MAX)
return NULL;
path = malloc(size);
if (path == NULL)
return NULL;
len = snprintf(path, size, "%s/%s", nsm_base_dirname, directory);
if (error_check(len, size)) {
free(path);
return NULL;
}
return path;
}
/*
* Returns a dynamically allocated, '\0'-terminated buffer
* containing an appropriate pathname, or NULL if an error
* occurs. Caller must free the returned result with free(3).
*/
__attribute__((__malloc__))
static char *
nsm_make_temp_pathname(const char *pathname)
{
size_t size;
char *path;
int len;
size = strlen(pathname) + sizeof(".new") + 2;
if (size > PATH_MAX)
return NULL;
path = malloc(size);
if (path == NULL)
return NULL;
len = snprintf(path, size, "%s.new", pathname);
if (error_check(len, size)) {
free(path);
return NULL;
}
return path;
}
/*
* Use "mktemp, write, rename" to update the contents of a file atomically.
*
* Returns true if completely successful, or false if some error occurred.
*/
static _Bool
nsm_atomic_write(const char *path, const void *buf, const size_t buflen)
{
_Bool result = false;
ssize_t len;
char *temp;
int fd;
temp = nsm_make_temp_pathname(path);
if (temp == NULL) {
xlog(L_ERROR, "Failed to create new path for %s", path);
goto out;
}
fd = open(temp, O_CREAT | O_TRUNC | O_SYNC | O_WRONLY, 0644);
if (fd == -1) {
xlog(L_ERROR, "Failed to create %s: %m", temp);
goto out;
}
len = write(fd, buf, buflen);
if (exact_error_check(len, buflen)) {
xlog(L_ERROR, "Failed to write %s: %m", temp);
(void)close(fd);
(void)unlink(temp);
goto out;
}
if (close(fd) == -1) {
xlog(L_ERROR, "Failed to close %s: %m", temp);
(void)unlink(temp);
goto out;
}
if (rename(temp, path) == -1) {
xlog(L_ERROR, "Failed to rename %s -> %s: %m",
temp, path);
(void)unlink(temp);
goto out;
}
/* Ostensibly, a sync(2) is not needed here because
* open(O_CREAT), write(O_SYNC), and rename(2) are
* already synchronous with persistent storage, for
* any file system we care about. */
result = true;
out:
free(temp);
return result;
}
/**
* nsm_setup_pathnames - set up pathname
* @progname: C string containing name of program, for error messages
* @parentdir: C string containing pathname to on-disk state, or NULL
*
* This runs before logging is set up, so error messages are directed
* to stderr.
*
* Returns true and sets up our pathnames, if @parentdir was valid
* and usable; otherwise false is returned.
*/
_Bool
nsm_setup_pathnames(const char *progname, const char *parentdir)
{
static char buf[PATH_MAX];
struct stat st;
char *path;
/* First: test length of name and whether it exists */
if (lstat(parentdir, &st) == -1) {
(void)fprintf(stderr, "%s: Failed to stat %s: %s",
progname, parentdir, strerror(errno));
return false;
}
/* Ensure we have a clean directory pathname */
strncpy(buf, parentdir, sizeof(buf));
path = dirname(buf);
if (*path == '.') {
(void)fprintf(stderr, "%s: Unusable directory %s",
progname, parentdir);
return false;
}
xlog(D_CALL, "Using %s as the state directory", parentdir);
strncpy(nsm_base_dirname, parentdir, sizeof(nsm_base_dirname));
return true;
}
/**
* nsm_is_default_parentdir - check if parent directory is default
*
* Returns true if the active statd parent directory, set by
* nsm_change_pathname(), is the same as the built-in default
* parent directory; otherwise false is returned.
*/
_Bool
nsm_is_default_parentdir(void)
{
return strcmp(nsm_base_dirname, NSM_DEFAULT_STATEDIR) == 0;
}
/*
* Clear all capabilities but CAP_NET_BIND_SERVICE. This permits
* callers to acquire privileged source ports, but all other root
* capabilities are disallowed.
*
* Returns true if successful, or false if some error occurred.
*/
#ifdef HAVE_SYS_CAPABILITY_H
static _Bool
nsm_clear_capabilities(void)
{
cap_t caps;
caps = cap_from_text("cap_net_bind_service=ep");
if (caps == NULL) {
xlog(L_ERROR, "Failed to allocate capability: %m");
return false;
}
if (cap_set_proc(caps) == -1) {
xlog(L_ERROR, "Failed to set capability flags: %m");
(void)cap_free(caps);
return false;
}
(void)cap_free(caps);
return true;
}
#define CAP_BOUND_PROCFILE "/proc/sys/kernel/cap-bound"
static _Bool
prune_bounding_set(void)
{
#ifdef PR_CAPBSET_DROP
int ret;
unsigned long i;
struct stat st;
/*
* Prior to kernel 2.6.25, the capabilities bounding set was a global
* value. Check to see if /proc/sys/kernel/cap-bound exists and don't
* bother to clear the bounding set if it does.
*/
ret = stat(CAP_BOUND_PROCFILE, &st);
if (!ret) {
xlog(L_WARNING, "%s exists. Not attempting to clear "
"capabilities bounding set.",
CAP_BOUND_PROCFILE);
return true;
} else if (errno != ENOENT) {
/* Warn, but attempt to clear the bounding set anyway. */
xlog(L_WARNING, "Unable to stat %s: %m", CAP_BOUND_PROCFILE);
}
/* prune the bounding set to nothing */
for (i = 0; prctl(PR_CAPBSET_READ, i, 0, 0, 0) >=0 ; ++i) {
ret = prctl(PR_CAPBSET_DROP, i, 0, 0, 0);
if (ret) {
xlog(L_ERROR, "Unable to prune capability %lu from "
"bounding set: %m", i);
return false;
}
}
#endif /* PR_CAPBSET_DROP */
return true;
}
#else /* !HAVE_SYS_CAPABILITY_H */
static _Bool
nsm_clear_capabilities(void)
{
return true;
}
static _Bool
prune_bounding_set(void)
{
return true;
}
#endif /* HAVE_SYS_CAPABILITY_H */
/**
* nsm_drop_privileges - drop root privileges
* @pidfd: file descriptor of a pid file
*
* Returns true if successful, or false if some error occurred.
*
* Set our effective UID and GID to that of our on-disk database.
*/
_Bool
nsm_drop_privileges(const int pidfd)
{
struct stat st;
(void)umask(S_IRWXO);
/*
* XXX: If we can't stat dirname, or if dirname is owned by
* root, we should use "statduser" instead, which is set up
* by configure.ac. Nothing in nfs-utils seems to use
* "statduser," though.
*/
if (lstat(nsm_base_dirname, &st) == -1) {
xlog(L_ERROR, "Failed to stat %s: %m", nsm_base_dirname);
return false;
}
if (chdir(nsm_base_dirname) == -1) {
xlog(L_ERROR, "Failed to change working directory to %s: %m",
nsm_base_dirname);
return false;
}
if (!prune_bounding_set())
return false;
if (st.st_uid == 0) {
xlog_warn("Running as root. "
"chown %s to choose different user", nsm_base_dirname);
return true;
}
/*
* If the pidfile happens to reside on NFS, dropping privileges
* will probably cause us to lose access, even though we are
* holding it open. Chown it to prevent this.
*/
if (pidfd >= 0)
if (fchown(pidfd, st.st_uid, st.st_gid) == -1)
xlog_warn("Failed to change owner of pidfile: %m");
/*
* Don't clear capabilities when dropping root.
*/
if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) == -1) {
xlog(L_ERROR, "prctl(PR_SET_KEEPCAPS) failed: %m");
return false;
}
if (setgroups(0, NULL) == -1) {
xlog(L_ERROR, "Failed to drop supplementary groups: %m");
return false;
}
/*
* ORDER
*
* setgid(2) first, as setuid(2) may remove privileges needed
* to set the group id.
*/
if (setgid(st.st_gid) == -1 || setuid(st.st_uid) == -1) {
xlog(L_ERROR, "Failed to drop privileges: %m");
return false;
}
xlog(D_CALL, "Effective UID, GID: %u, %u", st.st_uid, st.st_gid);
return nsm_clear_capabilities();
}
/**
* nsm_get_state - retrieve on-disk NSM state number
*
* Returns an odd NSM state number read from disk, or an initial
* state number. Zero is returned if some error occurs.
*/
int
nsm_get_state(_Bool update)
{
int fd, state = 0;
ssize_t result;
char *path = NULL;
path = nsm_make_pathname(NSM_STATE_FILE);
if (path == NULL) {
xlog(L_ERROR, "Failed to allocate path for " NSM_STATE_FILE);
goto out;
}
fd = open(path, O_RDONLY);
if (fd == -1) {
if (errno != ENOENT) {
xlog(L_ERROR, "Failed to open %s: %m", path);
goto out;
}
xlog(L_NOTICE, "Initializing NSM state");
state = 1;
update = true;
goto update;
}
result = read(fd, &state, sizeof(state));
if (exact_error_check(result, sizeof(state))) {
xlog_warn("Failed to read %s: %m", path);
xlog(L_NOTICE, "Initializing NSM state");
state = 1;
update = true;
goto update;
}
if ((state & 1) == 0)
state++;
update:
(void)close(fd);
if (update) {
state += 2;
if (!nsm_atomic_write(path, &state, sizeof(state)))
state = 0;
}
out:
free(path);
return state;
}
/**
* nsm_update_kernel_state - attempt to post new NSM state to kernel
* @state: NSM state number
*
*/
void
nsm_update_kernel_state(const int state)
{
ssize_t result;
char buf[20];
int fd, len;
fd = open(NSM_KERNEL_STATE_FILE, O_WRONLY);
if (fd == -1) {
xlog(D_GENERAL, "Failed to open " NSM_KERNEL_STATE_FILE ": %m");
return;
}
len = snprintf(buf, sizeof(buf), "%d", state);
if (error_check(len, sizeof(buf))) {
xlog_warn("Failed to form NSM state number string");
return;
}
result = write(fd, buf, strlen(buf));
if (exact_error_check(result, strlen(buf)))
xlog_warn("Failed to write NSM state number: %m");
if (close(fd) == -1)
xlog(L_ERROR, "Failed to close NSM state file "
NSM_KERNEL_STATE_FILE ": %m");
}
/**
* nsm_retire_monitored_hosts - back up all hosts from "sm/" to "sm.bak/"
*
* Returns the count of host records that were moved.
*
* Note that if any error occurs during this process, some monitor
* records may be left in the "sm" directory.
*/
unsigned int
nsm_retire_monitored_hosts(void)
{
unsigned int count = 0;
struct dirent *de;
char *path;
DIR *dir;
path = nsm_make_pathname(NSM_MONITOR_DIR);
if (path == NULL) {
xlog(L_ERROR, "Failed to allocate path for " NSM_MONITOR_DIR);
return count;
}
dir = opendir(path);
free(path);
if (dir == NULL) {
xlog_warn("Failed to open " NSM_MONITOR_DIR ": %m");
return count;
}
while ((de = readdir(dir)) != NULL) {
char *src, *dst;
struct stat stb;
if (de->d_name[0] == '.')
continue;
src = nsm_make_record_pathname(NSM_MONITOR_DIR, de->d_name);
if (src == NULL) {
xlog_warn("Bad monitor file name, skipping");
continue;
}
/* NB: not all file systems fill in d_type correctly */
if (lstat(src, &stb) == -1) {
xlog_warn("Bad monitor file %s, skipping: %m",
de->d_name);
free(src);
continue;
}
if (!S_ISREG(stb.st_mode)) {
xlog(D_GENERAL, "Skipping non-regular file %s",
de->d_name);
free(src);
continue;
}
dst = nsm_make_record_pathname(NSM_NOTIFY_DIR, de->d_name);
if (dst == NULL) {
free(src);
xlog_warn("Bad notify file name, skipping");
continue;
}
if (rename(src, dst) == -1)
xlog_warn("Failed to rename %s -> %s: %m",
src, dst);
else {
xlog(D_GENERAL, "Retired record for mon_name %s",
de->d_name);
count++;
}
free(dst);
free(src);
}
(void)closedir(dir);
return count;
}
/*
* nsm_priv_to_hex - convert a NSM private cookie to a hex string.
*
* @priv: buffer holding the binary NSM private cookie
* @buf: output buffer for NULL terminated hex string
* @buflen: size of output buffer
*
* Returns the length of the resulting string or 0 on error
*/
size_t
nsm_priv_to_hex(const char *priv, char *buf, const size_t buflen)
{
int i, len;
size_t remaining = buflen;
for (i = 0; i < SM_PRIV_SIZE; i++) {
len = snprintf(buf, remaining, "%02x",
(unsigned int)(0xff & priv[i]));
if (error_check(len, remaining))
return 0;
buf += len;
remaining -= (size_t)len;
}
return buflen - remaining;
}
/*
* Returns the length in bytes of the created record.
*/
__attribute__((__noinline__))
static size_t
nsm_create_monitor_record(char *buf, const size_t buflen,
const struct sockaddr *sap, const struct mon *m)
{
const struct sockaddr_in *sin = (const struct sockaddr_in *)sap;
size_t hexlen, remaining = buflen;
int len;
len = snprintf(buf, remaining, "%08x %08x %08x %08x ",
(unsigned int)sin->sin_addr.s_addr,
(unsigned int)m->mon_id.my_id.my_prog,
(unsigned int)m->mon_id.my_id.my_vers,
(unsigned int)m->mon_id.my_id.my_proc);
if (error_check(len, remaining))
return 0;
buf += len;
remaining -= (size_t)len;
hexlen = nsm_priv_to_hex(m->priv, buf, remaining);
if (hexlen == 0)
return 0;
buf += hexlen;
remaining -= hexlen;
len = snprintf(buf, remaining, " %s %s\n",
m->mon_id.mon_name, m->mon_id.my_id.my_name);
if (error_check(len, remaining))
return 0;
remaining -= (size_t)len;
return buflen - remaining;
}
static _Bool
nsm_append_monitored_host(const char *path, const char *line)
{
_Bool result = false;
char *buf = NULL;
struct stat stb;
size_t buflen;
ssize_t len;
int fd;
if (stat(path, &stb) == -1) {
xlog(L_ERROR, "Failed to insert: "
"could not stat original file %s: %m", path);
goto out;
}
buflen = (size_t)stb.st_size + strlen(line);
buf = malloc(buflen + 1);
if (buf == NULL) {
xlog(L_ERROR, "Failed to insert: no memory");
goto out;
}
memset(buf, 0, buflen + 1);
fd = open(path, O_RDONLY);
if (fd == -1) {
xlog(L_ERROR, "Failed to insert: "
"could not open original file %s: %m", path);
goto out;
}
len = read(fd, buf, (size_t)stb.st_size);
if (exact_error_check(len, (size_t)stb.st_size)) {
xlog(L_ERROR, "Failed to insert: "
"could not read original file %s: %m", path);
(void)close(fd);
goto out;
}
(void)close(fd);
strcat(buf, line);
if (nsm_atomic_write(path, buf, buflen))
result = true;
out:
free(buf);
return result;
}
/**
* nsm_insert_monitored_host - write callback data for one host to disk
* @hostname: C string containing a hostname
* @sap: sockaddr containing NLM callback address
* @mon: SM_MON arguments to save
*
* Returns true if successful, otherwise false if some error occurs.
*/
_Bool
nsm_insert_monitored_host(const char *hostname, const struct sockaddr *sap,
const struct mon *m)
{
static char buf[LINELEN + 1 + SM_MAXSTRLEN + 2];
char *path;
_Bool result = false;
ssize_t len;
size_t size;
int fd;
path = nsm_make_record_pathname(NSM_MONITOR_DIR, hostname);
if (path == NULL) {
xlog(L_ERROR, "Failed to insert: bad monitor hostname '%s'",
hostname);
return false;
}
size = nsm_create_monitor_record(buf, sizeof(buf), sap, m);
if (size == 0) {
xlog(L_ERROR, "Failed to insert: record too long");
goto out;
}
/*
* If exclusive create fails, we're adding a new line to an
* existing file.
*/
fd = open(path, O_WRONLY | O_CREAT | O_EXCL | O_SYNC, S_IRUSR | S_IWUSR);
if (fd == -1) {
if (errno != EEXIST) {
xlog(L_ERROR, "Failed to insert: creating %s: %m", path);
goto out;
}
result = nsm_append_monitored_host(path, buf);
goto out;
}
result = true;
len = write(fd, buf, size);
if (exact_error_check(len, size)) {
xlog_warn("Failed to insert: writing %s: %m", path);
(void)unlink(path);
result = false;
}
if (close(fd) == -1) {
xlog(L_ERROR, "Failed to insert: closing %s: %m", path);
(void)unlink(path);
result = false;
}
out:
free(path);
return result;
}
__attribute__((__noinline__))
static _Bool
nsm_parse_line(char *line, struct sockaddr_in *sin, struct mon *m)
{
unsigned int i, tmp;
int count;
char *c;
c = strchr(line, '\n');
if (c != NULL)
*c = '\0';
count = sscanf(line, "%8x %8x %8x %8x ",
(unsigned int *)&sin->sin_addr.s_addr,
(unsigned int *)&m->mon_id.my_id.my_prog,
(unsigned int *)&m->mon_id.my_id.my_vers,
(unsigned int *)&m->mon_id.my_id.my_proc);
if (count != 4)
return false;
c = line + RPCARGSLEN;
for (i = 0; i < SM_PRIV_SIZE; i++) {
if (sscanf(c, "%2x", &tmp) != 1)
return false;
m->priv[i] = (char)tmp;
c += 2;
}
c++;
m->mon_id.mon_name = c;
while (*c != '\0' && *c != ' ')
c++;
if (*c != '\0')
*c++ = '\0';
while (*c == ' ')
c++;
m->mon_id.my_id.my_name = c;
return true;
}
/*
* Stuff a 'struct mon' with callback data, and call @func.
*
* Returns the count of in-core records created.
*/
static unsigned int
nsm_read_line(const char *hostname, const time_t timestamp, char *line,
nsm_populate_t func)
{
struct sockaddr_in sin = {
.sin_family = AF_INET,
};
struct mon m;
if (!nsm_parse_line(line, &sin, &m))
return 0;
return func(hostname, (struct sockaddr *)(char *)&sin, &m, timestamp);
}
/*
* Given a filename, reads data from a file under "directory"
* and invokes @func so caller can populate their in-core
* database with this data.
*/
static unsigned int
nsm_load_host(const char *directory, const char *filename, nsm_populate_t func)
{
char buf[LINELEN + 1 + SM_MAXSTRLEN + 2];
unsigned int result = 0;
struct stat stb;
char *path;
FILE *f;
path = nsm_make_record_pathname(directory, filename);
if (path == NULL)
goto out_err;
if (lstat(path, &stb) == -1) {
xlog(L_ERROR, "Failed to stat %s: %m", path);
goto out_freepath;
}
if (!S_ISREG(stb.st_mode)) {
xlog(D_GENERAL, "Skipping non-regular file %s",
path);
goto out_freepath;
}
f = fopen(path, "r");
if (f == NULL) {
xlog(L_ERROR, "Failed to open %s: %m", path);
goto out_freepath;
}
while (fgets(buf, (int)sizeof(buf), f) != NULL) {
buf[sizeof(buf) - 1] = '\0';
result += nsm_read_line(filename, stb.st_mtime, buf, func);
}
if (result == 0)
xlog(L_ERROR, "Failed to read monitor data from %s", path);
(void)fclose(f);
out_freepath:
free(path);
out_err:
return result;
}
static unsigned int
nsm_load_dir(const char *directory, nsm_populate_t func)
{
unsigned int count = 0;
struct dirent *de;
char *path;
DIR *dir;
path = nsm_make_pathname(directory);
if (path == NULL) {
xlog(L_ERROR, "Failed to allocate path for directory %s",
directory);
return 0;
}
dir = opendir(path);
free(path);
if (dir == NULL) {
xlog(L_ERROR, "Failed to open directory %s: %m",
directory);
return 0;
}
while ((de = readdir(dir)) != NULL) {
if (de->d_name[0] == '.')
continue;
count += nsm_load_host(directory, de->d_name, func);
}
(void)closedir(dir);
return count;
}
/**
* nsm_load_monitor_list - load list of hosts to monitor
* @func: callback function to create entry for one host
*
* Returns the count of hosts that were found in the directory.
*/
unsigned int
nsm_load_monitor_list(nsm_populate_t func)
{
return nsm_load_dir(NSM_MONITOR_DIR, func);
}
/**
* nsm_load_notify_list - load list of hosts to notify
* @func: callback function to create entry for one host
*
* Returns the count of hosts that were found in the directory.
*/
unsigned int
nsm_load_notify_list(nsm_populate_t func)
{
return nsm_load_dir(NSM_NOTIFY_DIR, func);
}
static void
nsm_delete_host(const char *directory, const char *hostname,
const char *mon_name, const char *my_name)
{
char line[LINELEN + 1 + SM_MAXSTRLEN + 2];
char *outbuf = NULL;
struct stat stb;
char *path, *next;
size_t remaining;
FILE *f;
path = nsm_make_record_pathname(directory, hostname);
if (path == NULL) {
xlog(L_ERROR, "Bad filename, not deleting");
return;
}
if (stat(path, &stb) == -1) {
xlog(L_ERROR, "Failed to delete: "
"could not stat original file %s: %m", path);
goto out;
}
remaining = (size_t)stb.st_size + 1;
outbuf = malloc(remaining);
if (outbuf == NULL) {
xlog(L_ERROR, "Failed to delete: no memory");
goto out;
}
f = fopen(path, "r");
if (f == NULL) {
xlog(L_ERROR, "Failed to delete: "
"could not open original file %s: %m", path);
goto out;
}
/*
* Walk the records in the file, and copy the non-matching
* ones to our output buffer.
*/
next = outbuf;
while (fgets(line, (int)sizeof(line), f) != NULL) {
struct sockaddr_in sin;
struct mon m;
size_t len;
if (!nsm_parse_line(line, &sin, &m)) {
xlog(L_ERROR, "Failed to delete: "
"could not parse original file %s", path);
(void)fclose(f);
goto out;
}
if (strcmp(mon_name, m.mon_id.mon_name) == 0 &&
strcmp(my_name, m.mon_id.my_id.my_name) == 0)
continue;
/* nsm_parse_line destroys the contents of line[], so
* reconstruct the copy in our output buffer. */
len = nsm_create_monitor_record(next, remaining,
(struct sockaddr *)(char *)&sin, &m);
if (len == 0) {
xlog(L_ERROR, "Failed to delete: "
"could not construct output record");
(void)fclose(f);
goto out;
}
next += len;
remaining -= len;
}
(void)fclose(f);
/*
* If nothing was copied when we're done, then unlink the file.
* Otherwise, atomically update the contents of the file.
*/
if (next != outbuf) {
if (!nsm_atomic_write(path, outbuf, strlen(outbuf)))
xlog(L_ERROR, "Failed to delete: "
"could not write new file %s: %m", path);
} else {
if (unlink(path) == -1)
xlog(L_ERROR, "Failed to delete: "
"could not unlink file %s: %m", path);
}
out:
free(outbuf);
free(path);
}
/**
* nsm_delete_monitored_host - delete on-disk record for monitored host
* @hostname: '\0'-terminated C string containing hostname of record to delete
* @mon_name: '\0'-terminated C string containing monname of record to delete
* @my_name: '\0'-terminated C string containing myname of record to delete
*
*/
void
nsm_delete_monitored_host(const char *hostname, const char *mon_name,
const char *my_name)
{
nsm_delete_host(NSM_MONITOR_DIR, hostname, mon_name, my_name);
}
/**
* nsm_delete_notified_host - delete on-disk host record after notification
* @hostname: '\0'-terminated C string containing hostname of record to delete
* @mon_name: '\0'-terminated C string containing monname of record to delete
* @my_name: '\0'-terminated C string containing myname of record to delete
*
*/
void
nsm_delete_notified_host(const char *hostname, const char *mon_name,
const char *my_name)
{
nsm_delete_host(NSM_NOTIFY_DIR, hostname, mon_name, my_name);
}