]> git.decadent.org.uk Git - nfs-utils.git/blob - support/nsm/file.c
nfs-utils: make private cookie to hex conversion a library routine
[nfs-utils.git] / support / nsm / file.c
1 /*
2  * Copyright 2009 Oracle.  All rights reserved.
3  *
4  * This file is part of nfs-utils.
5  *
6  * nfs-utils is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * nfs-utils is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with nfs-utils.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 /*
21  * NSM for Linux.
22  *
23  * Callback information and NSM state is stored in files, usually
24  * under /var/lib/nfs.  A database of information contained in local
25  * files stores NLM callback data and what remote peers to notify of
26  * reboots.
27  *
28  * For each monitored remote peer, a text file is created under the
29  * directory specified by NSM_MONITOR_DIR.  The name of the file
30  * is a valid DNS hostname.  The hostname string must be a valid
31  * ASCII DNS name, and must not contain slash characters, white space,
32  * or '\0' (ie. anything that might have some special meaning in a
33  * path name).
34  *
35  * The contents of each file include seven blank-separated fields of
36  * text, finished with '\n'.  The first field contains the network
37  * address of the NLM service to call back.  The current implementation
38  * supports using only IPv4 addresses, so the only contents of this
39  * field are a network order IPv4 address expressed in 8 hexadecimal
40  * characters.
41  *
42  * The next four fields are text strings of hexadecimal characters,
43  * representing:
44  *
45  * 2. A 4 byte RPC program number of the NLM service to call back
46  * 3. A 4 byte RPC version number of the NLM service to call back
47  * 4. A 4 byte RPC procedure number of the NLM service to call back
48  * 5. A 16 byte opaque cookie that the NLM service uses to identify
49  *    the monitored host
50  *
51  * The sixth field is the monitored host's mon_name, passed to statd
52  * via an SM_MON request.
53  *
54  * The seventh field is the my_name for this peer, which is the
55  * hostname of the local NLM (currently on Linux, the result of
56  * `uname -n`).  This can be used as the source address/hostname
57  * when sending SM_NOTIFY requests.
58  *
59  * The NSM protocol does not limit the contents of these strings
60  * in any way except that they must fit into 1024 bytes.  Our
61  * implementation requires that these strings not contain
62  * white space or '\0'.
63  */
64
65 #ifdef HAVE_CONFIG_H
66 #include <config.h>
67 #endif
68
69 #include <sys/types.h>
70 #include <sys/stat.h>
71
72 #include <ctype.h>
73 #include <string.h>
74 #include <stdint.h>
75 #ifndef S_SPLINT_S
76 #include <unistd.h>
77 #endif
78 #include <libgen.h>
79 #include <stdio.h>
80 #include <errno.h>
81 #include <fcntl.h>
82 #include <dirent.h>
83 #include <grp.h>
84
85 #include "xlog.h"
86 #include "nsm.h"
87
88 #define RPCARGSLEN      (4 * (8 + 1))
89 #define LINELEN         (RPCARGSLEN + SM_PRIV_SIZE * 2 + 1)
90
91 #define NSM_KERNEL_STATE_FILE   "/proc/sys/fs/nfs/nsm_local_state"
92
93 /*
94  * Some distributions place statd's files in a subdirectory
95  */
96 #define NSM_PATH_EXTENSION
97 /* #define NSM_PATH_EXTENSION   "/statd" */
98
99 #define NSM_DEFAULT_STATEDIR            NFS_STATEDIR NSM_PATH_EXTENSION
100
101 static char nsm_base_dirname[PATH_MAX] = NSM_DEFAULT_STATEDIR;
102
103 #define NSM_MONITOR_DIR "sm"
104 #define NSM_NOTIFY_DIR  "sm.bak"
105 #define NSM_STATE_FILE  "state"
106
107
108 static _Bool
109 error_check(const int len, const size_t buflen)
110 {
111         return (len < 0) || ((size_t)len >= buflen);
112 }
113
114 static _Bool
115 exact_error_check(const ssize_t len, const size_t buflen)
116 {
117         return (len < 0) || ((size_t)len != buflen);
118 }
119
120 /*
121  * Returns a dynamically allocated, '\0'-terminated buffer
122  * containing an appropriate pathname, or NULL if an error
123  * occurs.  Caller must free the returned result with free(3).
124  */
125 __attribute_malloc__
126 static char *
127 nsm_make_record_pathname(const char *directory, const char *hostname)
128 {
129         const char *c;
130         size_t size;
131         char *path;
132         int len;
133
134         /*
135          * Block hostnames that contain characters that have
136          * meaning to the file system (like '/'), or that can
137          * be confusing on visual inspection (like ' ').
138          */
139         for (c = hostname; *c != '\0'; c++)
140                 if (*c == '/' || isspace((int)*c) != 0) {
141                         xlog(D_GENERAL, "Hostname contains invalid characters");
142                         return NULL;
143                 }
144
145         size = strlen(nsm_base_dirname) + strlen(directory) + strlen(hostname) + 3;
146         if (size > PATH_MAX) {
147                 xlog(D_GENERAL, "Hostname results in pathname that is too long");
148                 return NULL;
149         }
150
151         path = malloc(size);
152         if (path == NULL) {
153                 xlog(D_GENERAL, "Failed to allocate memory for pathname");
154                 return NULL;
155         }
156
157         len = snprintf(path, size, "%s/%s/%s",
158                         nsm_base_dirname, directory, hostname);
159         if (error_check(len, size)) {
160                 xlog(D_GENERAL, "Pathname did not fit in specified buffer");
161                 free(path);
162                 return NULL;
163         }
164
165         return path;
166 }
167
168 /*
169  * Returns a dynamically allocated, '\0'-terminated buffer
170  * containing an appropriate pathname, or NULL if an error
171  * occurs.  Caller must free the returned result with free(3).
172  */
173 __attribute_malloc__
174 static char *
175 nsm_make_pathname(const char *directory)
176 {
177         size_t size;
178         char *path;
179         int len;
180
181         size = strlen(nsm_base_dirname) + strlen(directory) + 2;
182         if (size > PATH_MAX)
183                 return NULL;
184
185         path = malloc(size);
186         if (path == NULL)
187                 return NULL;
188
189         len = snprintf(path, size, "%s/%s", nsm_base_dirname, directory);
190         if (error_check(len, size)) {
191                 free(path);
192                 return NULL;
193         }
194
195         return path;
196 }
197
198 /**
199  * nsm_setup_pathnames - set up pathname
200  * @progname: C string containing name of program, for error messages
201  * @parentdir: C string containing pathname to on-disk state, or NULL
202  *
203  * This runs before logging is set up, so error messages are directed
204  * to stderr.
205  *
206  * Returns true and sets up our pathnames, if @parentdir was valid
207  * and usable; otherwise false is returned.
208  */
209 _Bool
210 nsm_setup_pathnames(const char *progname, const char *parentdir)
211 {
212         static char buf[PATH_MAX];
213         struct stat st;
214         char *path;
215
216         /* First: test length of name and whether it exists */
217         if (lstat(parentdir, &st) == -1) {
218                 (void)fprintf(stderr, "%s: Failed to stat %s: %s",
219                                 progname, parentdir, strerror(errno));
220                 return false;
221         }
222
223         /* Ensure we have a clean directory pathname */
224         strncpy(buf, parentdir, sizeof(buf));
225         path = dirname(buf);
226         if (*path == '.') {
227                 (void)fprintf(stderr, "%s: Unusable directory %s",
228                                 progname, parentdir);
229                 return false;
230         }
231
232         xlog(D_CALL, "Using %s as the state directory", parentdir);
233         strncpy(nsm_base_dirname, parentdir, sizeof(nsm_base_dirname));
234         return true;
235 }
236
237 /**
238  * nsm_is_default_parentdir - check if parent directory is default
239  *
240  * Returns true if the active statd parent directory, set by
241  * nsm_change_pathname(), is the same as the built-in default
242  * parent directory; otherwise false is returned.
243  */
244 _Bool
245 nsm_is_default_parentdir(void)
246 {
247         return strcmp(nsm_base_dirname, NSM_DEFAULT_STATEDIR) == 0;
248 }
249
250 /**
251  * nsm_drop_privileges - drop root privileges
252  * @pidfd: file descriptor of a pid file
253  *
254  * Returns true if successful, or false if some error occurred.
255  *
256  * Set our effective UID and GID to that of our on-disk database.
257  */
258 _Bool
259 nsm_drop_privileges(const int pidfd)
260 {
261         struct stat st;
262
263         (void)umask(S_IRWXO);
264
265         /*
266          * XXX: If we can't stat dirname, or if dirname is owned by
267          *      root, we should use "statduser" instead, which is set up
268          *      by configure.ac.  Nothing in nfs-utils seems to use
269          *      "statduser," though.
270          */
271         if (lstat(nsm_base_dirname, &st) == -1) {
272                 xlog(L_ERROR, "Failed to stat %s: %m", nsm_base_dirname);
273                 return false;
274         }
275
276         if (st.st_uid == 0) {
277                 xlog_warn("Running as root.  "
278                         "chown %s to choose different user", nsm_base_dirname);
279                 return true;
280         }
281
282         if (chdir(nsm_base_dirname) == -1) {
283                 xlog(L_ERROR, "Failed to change working directory to %s: %m",
284                                 nsm_base_dirname);
285                 return false;
286         }
287
288         /*
289          * If the pidfile happens to reside on NFS, dropping privileges
290          * will probably cause us to lose access, even though we are
291          * holding it open.  Chown it to prevent this.
292          */
293         if (pidfd >= 0)
294                 if (fchown(pidfd, st.st_uid, st.st_gid) == -1)
295                         xlog_warn("Failed to change owner of pidfile: %m");
296
297         if (setgroups(0, NULL) == -1) {
298                 xlog(L_ERROR, "Failed to drop supplementary groups: %m");
299                 return false;
300         }
301
302         /*
303          * ORDER
304          *
305          * setgid(2) first, as setuid(2) may remove privileges needed
306          * to set the group id.
307          */
308         if (setgid(st.st_gid) == -1 || setuid(st.st_uid) == -1) {
309                 xlog(L_ERROR, "Failed to drop privileges: %m");
310                 return false;
311         }
312
313         xlog(D_CALL, "Effective UID, GID: %u, %u", st.st_uid, st.st_gid);
314         return true;
315 }
316
317 /**
318  * nsm_get_state - retrieve on-disk NSM state number
319  *
320  * Returns an odd NSM state number read from disk, or an initial
321  * state number.  Zero is returned if some error occurs.
322  */
323 int
324 nsm_get_state(_Bool update)
325 {
326         int fd, state = 0;
327         ssize_t result;
328         char *path = NULL;
329         char *newpath = NULL;
330
331         path = nsm_make_pathname(NSM_STATE_FILE);
332         if (path == NULL) {
333                 xlog(L_ERROR, "Failed to allocate path for " NSM_STATE_FILE);
334                 goto out;
335         }
336
337         fd = open(path, O_RDONLY);
338         if (fd == -1) {
339                 if (errno != ENOENT) {
340                         xlog(L_ERROR, "Failed to open %s: %m", path);
341                         goto out;
342                 }
343
344                 xlog(L_NOTICE, "Initializing NSM state");
345                 state = 1;
346                 update = true;
347                 goto update;
348         }
349
350         result = read(fd, &state, sizeof(state));
351         if (exact_error_check(result, sizeof(state))) {
352                 xlog_warn("Failed to read %s: %m", path);
353
354                 xlog(L_NOTICE, "Initializing NSM state");
355                 state = 1;
356                 update = true;
357                 goto update;
358         }
359
360         if ((state & 1) == 0)
361                 state++;
362
363 update:
364         (void)close(fd);
365
366         if (update) {
367                 state += 2;
368
369                 newpath = nsm_make_pathname(NSM_STATE_FILE ".new");
370                 if (newpath == NULL) {
371                         xlog(L_ERROR,
372                           "Failed to create path for " NSM_STATE_FILE ".new");
373                         state = 0;
374                         goto out;
375                 }
376
377                 fd = open(newpath, O_CREAT | O_SYNC | O_WRONLY, 0644);
378                 if (fd == -1) {
379                         xlog(L_ERROR, "Failed to create %s: %m", newpath);
380                         state = 0;
381                         goto out;
382                 }
383
384                 result = write(fd, &state, sizeof(state));
385                 if (exact_error_check(result, sizeof(state))) {
386                         xlog(L_ERROR, "Failed to write %s: %m", newpath);
387                         (void)close(fd);
388                         (void)unlink(newpath);
389                         state = 0;
390                         goto out;
391                 }
392
393                 if (close(fd) == -1) {
394                         xlog(L_ERROR, "Failed to close %s: %m", newpath);
395                         (void)unlink(newpath);
396                         state = 0;
397                         goto out;
398                 }
399
400                 if (rename(newpath, path) == -1) {
401                         xlog(L_ERROR, "Failed to rename %s -> %s: %m",
402                                         newpath, path);
403                         (void)unlink(newpath);
404                         state = 0;
405                         goto out;
406                 }
407
408                 /* Ostensibly, a sync(2) is not needed here because
409                  * open(O_CREAT), write(O_SYNC), and rename(2) are
410                  * already synchronous with persistent storage, for
411                  * any file system we care about. */
412         }
413
414 out:
415         free(newpath);
416         free(path);
417         return state;
418 }
419
420 /**
421  * nsm_update_kernel_state - attempt to post new NSM state to kernel
422  * @state: NSM state number
423  *
424  */
425 void
426 nsm_update_kernel_state(const int state)
427 {
428         ssize_t result;
429         char buf[20];
430         int fd, len;
431
432         fd = open(NSM_KERNEL_STATE_FILE, O_WRONLY);
433         if (fd == -1) {
434                 xlog(D_GENERAL, "Failed to open " NSM_KERNEL_STATE_FILE ": %m");
435                 return;
436         }
437
438         len = snprintf(buf, sizeof(buf), "%d", state);
439         if (error_check(len, sizeof(buf))) {
440                 xlog_warn("Failed to form NSM state number string");
441                 return;
442         }
443
444         result = write(fd, buf, strlen(buf));
445         if (exact_error_check(result, strlen(buf)))
446                 xlog_warn("Failed to write NSM state number: %m");
447
448         if (close(fd) == -1)
449                 xlog(L_ERROR, "Failed to close NSM state file "
450                                 NSM_KERNEL_STATE_FILE ": %m");
451 }
452
453 /**
454  * nsm_retire_monitored_hosts - back up all hosts from "sm/" to "sm.bak/"
455  *
456  * Returns the count of host records that were moved.
457  *
458  * Note that if any error occurs during this process, some monitor
459  * records may be left in the "sm" directory.
460  */
461 unsigned int
462 nsm_retire_monitored_hosts(void)
463 {
464         unsigned int count = 0;
465         struct dirent *de;
466         char *path;
467         DIR *dir;
468
469         path = nsm_make_pathname(NSM_MONITOR_DIR);
470         if (path == NULL) {
471                 xlog(L_ERROR, "Failed to allocate path for " NSM_MONITOR_DIR);
472                 return count;
473         }
474
475         dir = opendir(path);
476         free(path);
477         if (dir == NULL) {
478                 xlog_warn("Failed to open " NSM_MONITOR_DIR ": %m");
479                 return count;
480         }
481
482         while ((de = readdir(dir)) != NULL) {
483                 char *src, *dst;
484
485                 if (de->d_type != (unsigned char)DT_REG)
486                         continue;
487                 if (de->d_name[0] == '.')
488                         continue;
489
490                 src = nsm_make_record_pathname(NSM_MONITOR_DIR, de->d_name);
491                 if (src == NULL) {
492                         xlog_warn("Bad monitor file name, skipping");
493                         continue;
494                 }
495
496                 dst = nsm_make_record_pathname(NSM_NOTIFY_DIR, de->d_name);
497                 if (dst == NULL) {
498                         free(src);
499                         xlog_warn("Bad notify file name, skipping");
500                         continue;
501                 }
502
503                 if (rename(src, dst) == -1)
504                         xlog_warn("Failed to rename %s -> %s: %m",
505                                 src, dst);
506                 else {
507                         xlog(D_GENERAL, "Retired record for mon_name %s",
508                                         de->d_name);
509                         count++;
510                 }
511
512                 free(dst);
513                 free(src);
514         }
515
516         (void)closedir(dir);
517         return count;
518 }
519
520 /*
521  * nsm_priv_to_hex - convert a NSM private cookie to a hex string.
522  *
523  * @priv: buffer holding the binary NSM private cookie
524  * @buf: output buffer for NULL terminated hex string
525  * @buflen: size of output buffer
526  *
527  * Returns the length of the resulting string or 0 on error
528  */
529 size_t
530 nsm_priv_to_hex(const char *priv, char *buf, const size_t buflen)
531 {
532         int i, len;
533         size_t remaining = buflen;
534
535         for (i = 0; i < SM_PRIV_SIZE; i++) {
536                 len = snprintf(buf, remaining, "%02x",
537                                 (unsigned int)(0xff & priv[i]));
538                 if (error_check(len, remaining))
539                         return 0;
540                 buf += len;
541                 remaining -= (size_t)len;
542         }
543
544         return buflen - remaining;
545 }
546
547 /*
548  * Returns the length in bytes of the created record.
549  */
550 __attribute_noinline__
551 static size_t
552 nsm_create_monitor_record(char *buf, const size_t buflen,
553                 const struct sockaddr *sap, const struct mon *m)
554 {
555         const struct sockaddr_in *sin = (const struct sockaddr_in *)sap;
556         size_t hexlen, remaining = buflen;
557         int len;
558
559         len = snprintf(buf, remaining, "%08x %08x %08x %08x ",
560                         (unsigned int)sin->sin_addr.s_addr,
561                         (unsigned int)m->mon_id.my_id.my_prog,
562                         (unsigned int)m->mon_id.my_id.my_vers,
563                         (unsigned int)m->mon_id.my_id.my_proc);
564         if (error_check(len, remaining))
565                 return 0;
566         buf += len;
567         remaining -= (size_t)len;
568
569         hexlen = nsm_priv_to_hex(m->priv, buf, remaining);
570         if (hexlen == 0)
571                 return 0;
572         buf += hexlen;
573         remaining -= hexlen;
574
575         len = snprintf(buf, remaining, " %s %s\n",
576                         m->mon_id.mon_name, m->mon_id.my_id.my_name);
577         if (error_check(len, remaining))
578                 return 0;
579         remaining -= (size_t)len;
580
581         return buflen - remaining;
582 }
583
584 /**
585  * nsm_insert_monitored_host - write callback data for one host to disk
586  * @hostname: C string containing a hostname
587  * @sap: sockaddr containing NLM callback address
588  * @mon: SM_MON arguments to save
589  *
590  * Returns true if successful, otherwise false if some error occurs.
591  */
592 _Bool
593 nsm_insert_monitored_host(const char *hostname, const struct sockaddr *sap,
594                 const struct mon *m)
595 {
596         static char buf[LINELEN + 1 + SM_MAXSTRLEN + 2];
597         char *path;
598         _Bool result = false;
599         ssize_t len;
600         size_t size;
601         int fd;
602
603         path = nsm_make_record_pathname(NSM_MONITOR_DIR, hostname);
604         if (path == NULL) {
605                 xlog(L_ERROR, "Failed to insert: bad monitor hostname '%s'",
606                                 hostname);
607                 return false;
608         }
609
610         size = nsm_create_monitor_record(buf, sizeof(buf), sap, m);
611         if (size == 0) {
612                 xlog(L_ERROR, "Failed to insert: record too long");
613                 goto out;
614         }
615
616         fd = open(path, O_WRONLY | O_CREAT | O_SYNC, S_IRUSR | S_IWUSR);
617         if (fd == -1) {
618                 xlog(L_ERROR, "Failed to insert: creating %s: %m", path);
619                 goto out;
620         }
621         result = true;
622
623         len = write(fd, buf, size);
624         if (exact_error_check(len, size)) {
625                 xlog_warn("Failed to insert: writing %s: %m", path);
626                 (void)unlink(path);
627                 result = false;
628         }
629
630         if (close(fd) == -1) {
631                 xlog(L_ERROR, "Failed to insert: closing %s: %m", path);
632                 (void)unlink(path);
633                 result = false;
634         }
635
636 out:
637         free(path);
638         return result;
639 }
640
641 __attribute_noinline__
642 static _Bool
643 nsm_parse_line(char *line, struct sockaddr_in *sin, struct mon *m)
644 {
645         unsigned int i, tmp;
646         int count;
647         char *c;
648
649         c = strchr(line, '\n');
650         if (c != NULL)
651                 *c = '\0';
652
653         count = sscanf(line, "%8x %8x %8x %8x ",
654                         (unsigned int *)&sin->sin_addr.s_addr,
655                         (unsigned int *)&m->mon_id.my_id.my_prog,
656                         (unsigned int *)&m->mon_id.my_id.my_vers,
657                         (unsigned int *)&m->mon_id.my_id.my_proc);
658         if (count != 4)
659                 return false;
660
661         c = line + RPCARGSLEN;
662         for (i = 0; i < SM_PRIV_SIZE; i++) {
663                 if (sscanf(c, "%2x", &tmp) != 1)
664                         return false;
665                 m->priv[i] = (char)tmp;
666                 c += 2;
667         }
668
669         c++;
670         m->mon_id.mon_name = c;
671         while (*c != '\0' && *c != ' ')
672                 c++;
673         if (*c != '\0')
674                 *c++ = '\0';
675         while (*c == ' ')
676                 c++;
677         m->mon_id.my_id.my_name = c;
678
679         return true;
680 }
681
682 /*
683  * Stuff a 'struct mon' with callback data, and call @func.
684  *
685  * Returns the count of in-core records created.
686  */
687 static unsigned int
688 nsm_read_line(const char *hostname, const time_t timestamp, char *line,
689                 nsm_populate_t func)
690 {
691         struct sockaddr_in sin = {
692                 .sin_family             = AF_INET,
693         };
694         struct mon m;
695
696         if (!nsm_parse_line(line, &sin, &m))
697                 return 0;
698
699         return func(hostname, (struct sockaddr *)(char *)&sin, &m, timestamp);
700 }
701
702 /*
703  * Given a filename, reads data from a file under NSM_MONITOR_DIR
704  * and invokes @func so caller can populate their in-core
705  * database with this data.
706  */
707 static unsigned int
708 nsm_load_host(const char *directory, const char *filename, nsm_populate_t func)
709 {
710         char buf[LINELEN + 1 + SM_MAXSTRLEN + 2];
711         unsigned int result = 0;
712         struct stat stb;
713         char *path;
714         FILE *f;
715
716         path = nsm_make_record_pathname(directory, filename);
717         if (path == NULL)
718                 goto out_err;
719
720         if (stat(path, &stb) == -1) {
721                 xlog(L_ERROR, "Failed to stat %s: %m", path);
722                 goto out_freepath;
723         }
724
725         f = fopen(path, "r");
726         if (f == NULL) {
727                 xlog(L_ERROR, "Failed to open %s: %m", path);
728                 goto out_freepath;
729         }
730
731         while (fgets(buf, (int)sizeof(buf), f) != NULL) {
732                 buf[sizeof(buf) - 1] = '\0';
733                 result += nsm_read_line(filename, stb.st_mtime, buf, func);
734         }
735         if (result == 0)
736                 xlog(L_ERROR, "Failed to read monitor data from %s", path);
737
738         (void)fclose(f);
739
740 out_freepath:
741         free(path);
742 out_err:
743         return result;
744 }
745
746 static unsigned int
747 nsm_load_dir(const char *directory, nsm_populate_t func)
748 {
749         unsigned int count = 0;
750         struct dirent *de;
751         char *path;
752         DIR *dir;
753
754         path = nsm_make_pathname(directory);
755         if (path == NULL) {
756                 xlog(L_ERROR, "Failed to allocate path for directory %s",
757                                 directory);
758                 return 0;
759         }
760
761         dir = opendir(path);
762         free(path);
763         if (dir == NULL) {
764                 xlog(L_ERROR, "Failed to open directory %s: %m",
765                                 directory);
766                 return 0;
767         }
768
769         while ((de = readdir(dir)) != NULL) {
770                 if (de->d_type != (unsigned char)DT_REG)
771                         continue;
772                 if (de->d_name[0] == '.')
773                         continue;
774
775                 count += nsm_load_host(directory, de->d_name, func);
776         }
777
778         (void)closedir(dir);
779         return count;
780 }
781
782 /**
783  * nsm_load_monitor_list - load list of hosts to monitor
784  * @func: callback function to create entry for one host
785  *
786  * Returns the count of hosts that were found in the directory.
787  */
788 unsigned int
789 nsm_load_monitor_list(nsm_populate_t func)
790 {
791         return nsm_load_dir(NSM_MONITOR_DIR, func);
792 }
793
794 /**
795  * nsm_load_notify_list - load list of hosts to notify
796  * @func: callback function to create entry for one host
797  *
798  * Returns the count of hosts that were found in the directory.
799  */
800 unsigned int
801 nsm_load_notify_list(nsm_populate_t func)
802 {
803         return nsm_load_dir(NSM_NOTIFY_DIR, func);
804 }
805
806 static void
807 nsm_delete_host(const char *directory, const char *hostname)
808 {
809         char *path;
810
811         path = nsm_make_record_pathname(directory, hostname);
812         if (path == NULL) {
813                 xlog(L_ERROR, "Bad filename, not deleting");
814                 return;
815         }
816
817         if (unlink(path) == -1)
818                 xlog(L_ERROR, "Failed to unlink %s: %m", path);
819
820         free(path);
821 }
822
823 /**
824  * nsm_delete_monitored_host - delete on-disk record for monitored host
825  * @hostname: '\0'-terminated C string containing hostname of record to delete
826  *
827  */
828 void
829 nsm_delete_monitored_host(const char *hostname)
830 {
831         nsm_delete_host(NSM_MONITOR_DIR, hostname);
832 }
833
834 /**
835  * nsm_delete_notified_host - delete on-disk host record after notification
836  * @hostname: '\0'-terminated C string containing hostname of record to delete
837  *
838  */
839 void
840 nsm_delete_notified_host(const char *hostname)
841 {
842         nsm_delete_host(NSM_NOTIFY_DIR, hostname);
843 }