]> git.decadent.org.uk Git - nfs-utils.git/blobdiff - support/nsm/file.c
libnsm.a: Introduce common routines to handle persistent storage
[nfs-utils.git] / support / nsm / file.c
diff --git a/support/nsm/file.c b/support/nsm/file.c
new file mode 100644 (file)
index 0000000..4dfc235
--- /dev/null
@@ -0,0 +1,819 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * 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 <config.h>
+#endif
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <ctype.h>
+#include <string.h>
+#include <stdint.h>
+#ifndef S_SPLINT_S
+#include <unistd.h>
+#endif
+#include <libgen.h>
+#include <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <grp.h>
+
+#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"
+
+/*
+ * Some distributions place statd's files in a subdirectory
+ */
+#define NSM_PATH_EXTENSION
+/* #define NSM_PATH_EXTENSION  "/statd" */
+
+#define NSM_DEFAULT_STATEDIR           NFS_STATEDIR NSM_PATH_EXTENSION
+
+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;
+}
+
+/**
+ * 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;
+}
+
+/**
+ * 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 (st.st_uid == 0) {
+               xlog_warn("Running as root.  "
+                       "chown %s to choose different user", nsm_base_dirname);
+               return true;
+       }
+
+       if (chdir(nsm_base_dirname) == -1) {
+               xlog(L_ERROR, "Failed to change working directory to %s: %m",
+                               nsm_base_dirname);
+               return false;
+       }
+
+       /*
+        * 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");
+
+       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 true;
+}
+
+/**
+ * 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;
+       char *newpath = 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;
+
+               newpath = nsm_make_pathname(NSM_STATE_FILE ".new");
+               if (newpath == NULL) {
+                       xlog(L_ERROR,
+                         "Failed to create path for " NSM_STATE_FILE ".new");
+                       state = 0;
+                       goto out;
+               }
+
+               fd = open(newpath, O_CREAT | O_SYNC | O_WRONLY, 0644);
+               if (fd == -1) {
+                       xlog(L_ERROR, "Failed to create %s: %m", newpath);
+                       state = 0;
+                       goto out;
+               }
+
+               result = write(fd, &state, sizeof(state));
+               if (exact_error_check(result, sizeof(state))) {
+                       xlog(L_ERROR, "Failed to write %s: %m", newpath);
+                       (void)close(fd);
+                       (void)unlink(newpath);
+                       state = 0;
+                       goto out;
+               }
+
+               if (close(fd) == -1) {
+                       xlog(L_ERROR, "Failed to close %s: %m", newpath);
+                       (void)unlink(newpath);
+                       state = 0;
+                       goto out;
+               }
+
+               if (rename(newpath, path) == -1) {
+                       xlog(L_ERROR, "Failed to rename %s -> %s: %m",
+                                       newpath, path);
+                       (void)unlink(newpath);
+                       state = 0;
+                       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. */
+       }
+
+out:
+       free(newpath);
+       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;
+
+               if (de->d_type != (unsigned char)DT_REG)
+                       continue;
+               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;
+               }
+
+               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;
+}
+
+/*
+ * 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 remaining = buflen;
+       int i, 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;
+
+       for (i = 0; i < SM_PRIV_SIZE; i++) {
+               len = snprintf(buf, remaining, "%02x",
+                               (unsigned int)(0xff & m->priv[i]));
+               if (error_check(len, remaining))
+                       return 0;
+               buf += len;
+               remaining -= (size_t)len;
+       }
+
+       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;
+}
+
+/**
+ * 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;
+       }
+
+       fd = open(path, O_WRONLY | O_CREAT | O_SYNC, S_IRUSR | S_IWUSR);
+       if (fd == -1) {
+               xlog(L_ERROR, "Failed to insert: creating %s: %m", path);
+               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 NSM_MONITOR_DIR
+ * 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 (stat(path, &stb) == -1) {
+               xlog(L_ERROR, "Failed to stat %s: %m", 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_type != (unsigned char)DT_REG)
+                       continue;
+               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)
+{
+       char *path;
+
+       path = nsm_make_record_pathname(directory, hostname);
+       if (path == NULL) {
+               xlog(L_ERROR, "Bad filename, not deleting");
+               return;
+       }
+
+       if (unlink(path) == -1)
+               xlog(L_ERROR, "Failed to unlink %s: %m", path);
+
+       free(path);
+}
+
+/**
+ * nsm_delete_monitored_host - delete on-disk record for monitored host
+ * @hostname: '\0'-terminated C string containing hostname of record to delete
+ *
+ */
+void
+nsm_delete_monitored_host(const char *hostname)
+{
+       nsm_delete_host(NSM_MONITOR_DIR, hostname);
+}
+
+/**
+ * nsm_delete_notified_host - delete on-disk host record after notification
+ * @hostname: '\0'-terminated C string containing hostname of record to delete
+ *
+ */
+void
+nsm_delete_notified_host(const char *hostname)
+{
+       nsm_delete_host(NSM_NOTIFY_DIR, hostname);
+}