]> git.decadent.org.uk Git - nfs-utils.git/blob - support/nsm/file.c
Imported Upstream version 1.2.4
[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 #ifdef HAVE_SYS_CAPABILITY_H
71 #include <sys/capability.h>
72 #endif
73 #include <sys/prctl.h>
74 #include <sys/stat.h>
75
76 #include <ctype.h>
77 #include <string.h>
78 #include <stdint.h>
79 #ifndef S_SPLINT_S
80 #include <unistd.h>
81 #endif
82 #include <libgen.h>
83 #include <stdio.h>
84 #include <errno.h>
85 #include <fcntl.h>
86 #include <dirent.h>
87 #include <grp.h>
88
89 #include "xlog.h"
90 #include "nsm.h"
91
92 #define RPCARGSLEN      (4 * (8 + 1))
93 #define LINELEN         (RPCARGSLEN + SM_PRIV_SIZE * 2 + 1)
94
95 #define NSM_KERNEL_STATE_FILE   "/proc/sys/fs/nfs/nsm_local_state"
96
97 /*
98  * Some distributions place statd's files in a subdirectory
99  */
100 #define NSM_PATH_EXTENSION
101 /* #define NSM_PATH_EXTENSION   "/statd" */
102
103 #define NSM_DEFAULT_STATEDIR            NFS_STATEDIR NSM_PATH_EXTENSION
104
105 static char nsm_base_dirname[PATH_MAX] = NSM_DEFAULT_STATEDIR;
106
107 #define NSM_MONITOR_DIR "sm"
108 #define NSM_NOTIFY_DIR  "sm.bak"
109 #define NSM_STATE_FILE  "state"
110
111
112 static _Bool
113 error_check(const int len, const size_t buflen)
114 {
115         return (len < 0) || ((size_t)len >= buflen);
116 }
117
118 static _Bool
119 exact_error_check(const ssize_t len, const size_t buflen)
120 {
121         return (len < 0) || ((size_t)len != buflen);
122 }
123
124 /*
125  * Returns a dynamically allocated, '\0'-terminated buffer
126  * containing an appropriate pathname, or NULL if an error
127  * occurs.  Caller must free the returned result with free(3).
128  */
129 __attribute__((__malloc__))
130 static char *
131 nsm_make_record_pathname(const char *directory, const char *hostname)
132 {
133         const char *c;
134         size_t size;
135         char *path;
136         int len;
137
138         /*
139          * Block hostnames that contain characters that have
140          * meaning to the file system (like '/'), or that can
141          * be confusing on visual inspection (like ' ').
142          */
143         for (c = hostname; *c != '\0'; c++)
144                 if (*c == '/' || isspace((int)*c) != 0) {
145                         xlog(D_GENERAL, "Hostname contains invalid characters");
146                         return NULL;
147                 }
148
149         size = strlen(nsm_base_dirname) + strlen(directory) + strlen(hostname) + 3;
150         if (size > PATH_MAX) {
151                 xlog(D_GENERAL, "Hostname results in pathname that is too long");
152                 return NULL;
153         }
154
155         path = malloc(size);
156         if (path == NULL) {
157                 xlog(D_GENERAL, "Failed to allocate memory for pathname");
158                 return NULL;
159         }
160
161         len = snprintf(path, size, "%s/%s/%s",
162                         nsm_base_dirname, directory, hostname);
163         if (error_check(len, size)) {
164                 xlog(D_GENERAL, "Pathname did not fit in specified buffer");
165                 free(path);
166                 return NULL;
167         }
168
169         return path;
170 }
171
172 /*
173  * Returns a dynamically allocated, '\0'-terminated buffer
174  * containing an appropriate pathname, or NULL if an error
175  * occurs.  Caller must free the returned result with free(3).
176  */
177 __attribute__((__malloc__))
178 static char *
179 nsm_make_pathname(const char *directory)
180 {
181         size_t size;
182         char *path;
183         int len;
184
185         size = strlen(nsm_base_dirname) + strlen(directory) + 2;
186         if (size > PATH_MAX)
187                 return NULL;
188
189         path = malloc(size);
190         if (path == NULL)
191                 return NULL;
192
193         len = snprintf(path, size, "%s/%s", nsm_base_dirname, directory);
194         if (error_check(len, size)) {
195                 free(path);
196                 return NULL;
197         }
198
199         return path;
200 }
201
202 /*
203  * Returns a dynamically allocated, '\0'-terminated buffer
204  * containing an appropriate pathname, or NULL if an error
205  * occurs.  Caller must free the returned result with free(3).
206  */
207 __attribute__((__malloc__))
208 static char *
209 nsm_make_temp_pathname(const char *pathname)
210 {
211         size_t size;
212         char *path;
213         int len;
214
215         size = strlen(pathname) + sizeof(".new") + 2;
216         if (size > PATH_MAX)
217                 return NULL;
218
219         path = malloc(size);
220         if (path == NULL)
221                 return NULL;
222
223         len = snprintf(path, size, "%s.new", pathname);
224         if (error_check(len, size)) {
225                 free(path);
226                 return NULL;
227         }
228
229         return path;
230 }
231
232 /*
233  * Use "mktemp, write, rename" to update the contents of a file atomically.
234  *
235  * Returns true if completely successful, or false if some error occurred.
236  */
237 static _Bool
238 nsm_atomic_write(const char *path, const void *buf, const size_t buflen)
239 {
240         _Bool result = false;
241         ssize_t len;
242         char *temp;
243         int fd;
244
245         temp = nsm_make_temp_pathname(path);
246         if (temp == NULL) {
247                 xlog(L_ERROR, "Failed to create new path for %s", path);
248                 goto out;
249         }
250
251         fd = open(temp, O_CREAT | O_TRUNC | O_SYNC | O_WRONLY, 0644);
252         if (fd == -1) {
253                 xlog(L_ERROR, "Failed to create %s: %m", temp);
254                 goto out;
255         }
256
257         len = write(fd, buf, buflen);
258         if (exact_error_check(len, buflen)) {
259                 xlog(L_ERROR, "Failed to write %s: %m", temp);
260                 (void)close(fd);
261                 (void)unlink(temp);
262                 goto out;
263         }
264
265         if (close(fd) == -1) {
266                 xlog(L_ERROR, "Failed to close %s: %m", temp);
267                 (void)unlink(temp);
268                 goto out;
269         }
270
271         if (rename(temp, path) == -1) {
272                 xlog(L_ERROR, "Failed to rename %s -> %s: %m",
273                                 temp, path);
274                 (void)unlink(temp);
275                 goto out;
276         }
277
278         /* Ostensibly, a sync(2) is not needed here because
279          * open(O_CREAT), write(O_SYNC), and rename(2) are
280          * already synchronous with persistent storage, for
281          * any file system we care about. */
282
283         result = true;
284
285 out:
286         free(temp);
287         return result;
288 }
289
290 /**
291  * nsm_setup_pathnames - set up pathname
292  * @progname: C string containing name of program, for error messages
293  * @parentdir: C string containing pathname to on-disk state, or NULL
294  *
295  * This runs before logging is set up, so error messages are directed
296  * to stderr.
297  *
298  * Returns true and sets up our pathnames, if @parentdir was valid
299  * and usable; otherwise false is returned.
300  */
301 _Bool
302 nsm_setup_pathnames(const char *progname, const char *parentdir)
303 {
304         static char buf[PATH_MAX];
305         struct stat st;
306         char *path;
307
308         /* First: test length of name and whether it exists */
309         if (lstat(parentdir, &st) == -1) {
310                 (void)fprintf(stderr, "%s: Failed to stat %s: %s",
311                                 progname, parentdir, strerror(errno));
312                 return false;
313         }
314
315         /* Ensure we have a clean directory pathname */
316         strncpy(buf, parentdir, sizeof(buf));
317         path = dirname(buf);
318         if (*path == '.') {
319                 (void)fprintf(stderr, "%s: Unusable directory %s",
320                                 progname, parentdir);
321                 return false;
322         }
323
324         xlog(D_CALL, "Using %s as the state directory", parentdir);
325         strncpy(nsm_base_dirname, parentdir, sizeof(nsm_base_dirname));
326         return true;
327 }
328
329 /**
330  * nsm_is_default_parentdir - check if parent directory is default
331  *
332  * Returns true if the active statd parent directory, set by
333  * nsm_change_pathname(), is the same as the built-in default
334  * parent directory; otherwise false is returned.
335  */
336 _Bool
337 nsm_is_default_parentdir(void)
338 {
339         return strcmp(nsm_base_dirname, NSM_DEFAULT_STATEDIR) == 0;
340 }
341
342 /*
343  * Clear all capabilities but CAP_NET_BIND_SERVICE.  This permits
344  * callers to acquire privileged source ports, but all other root
345  * capabilities are disallowed.
346  *
347  * Returns true if successful, or false if some error occurred.
348  */
349 static _Bool
350 nsm_clear_capabilities(void)
351 {
352 #ifdef HAVE_SYS_CAPABILITY_H
353         cap_t caps;
354
355         caps = cap_from_text("cap_net_bind_service=ep");
356         if (caps == NULL) {
357                 xlog(L_ERROR, "Failed to allocate capability: %m");
358                 return false;
359         }
360
361         if (cap_set_proc(caps) == -1) {
362                 xlog(L_ERROR, "Failed to set capability flags: %m");
363                 (void)cap_free(caps);
364                 return false;
365         }
366
367         (void)cap_free(caps);
368 #endif
369         return true;
370 }
371
372 /**
373  * nsm_drop_privileges - drop root privileges
374  * @pidfd: file descriptor of a pid file
375  *
376  * Returns true if successful, or false if some error occurred.
377  *
378  * Set our effective UID and GID to that of our on-disk database.
379  */
380 _Bool
381 nsm_drop_privileges(const int pidfd)
382 {
383         struct stat st;
384
385         (void)umask(S_IRWXO);
386
387         /*
388          * XXX: If we can't stat dirname, or if dirname is owned by
389          *      root, we should use "statduser" instead, which is set up
390          *      by configure.ac.  Nothing in nfs-utils seems to use
391          *      "statduser," though.
392          */
393         if (lstat(nsm_base_dirname, &st) == -1) {
394                 xlog(L_ERROR, "Failed to stat %s: %m", nsm_base_dirname);
395                 return false;
396         }
397
398         if (st.st_uid == 0) {
399                 xlog_warn("Running as root.  "
400                         "chown %s to choose different user", nsm_base_dirname);
401                 return true;
402         }
403
404         if (chdir(nsm_base_dirname) == -1) {
405                 xlog(L_ERROR, "Failed to change working directory to %s: %m",
406                                 nsm_base_dirname);
407                 return false;
408         }
409
410         /*
411          * If the pidfile happens to reside on NFS, dropping privileges
412          * will probably cause us to lose access, even though we are
413          * holding it open.  Chown it to prevent this.
414          */
415         if (pidfd >= 0)
416                 if (fchown(pidfd, st.st_uid, st.st_gid) == -1)
417                         xlog_warn("Failed to change owner of pidfile: %m");
418
419         /*
420          * Don't clear capabilities when dropping root.
421          */
422         if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) == -1) {
423                 xlog(L_ERROR, "prctl(PR_SET_KEEPCAPS) failed: %m");
424                 return false;
425         }
426
427         if (setgroups(0, NULL) == -1) {
428                 xlog(L_ERROR, "Failed to drop supplementary groups: %m");
429                 return false;
430         }
431
432         /*
433          * ORDER
434          *
435          * setgid(2) first, as setuid(2) may remove privileges needed
436          * to set the group id.
437          */
438         if (setgid(st.st_gid) == -1 || setuid(st.st_uid) == -1) {
439                 xlog(L_ERROR, "Failed to drop privileges: %m");
440                 return false;
441         }
442
443         xlog(D_CALL, "Effective UID, GID: %u, %u", st.st_uid, st.st_gid);
444
445         return nsm_clear_capabilities();
446 }
447
448 /**
449  * nsm_get_state - retrieve on-disk NSM state number
450  *
451  * Returns an odd NSM state number read from disk, or an initial
452  * state number.  Zero is returned if some error occurs.
453  */
454 int
455 nsm_get_state(_Bool update)
456 {
457         int fd, state = 0;
458         ssize_t result;
459         char *path = NULL;
460
461         path = nsm_make_pathname(NSM_STATE_FILE);
462         if (path == NULL) {
463                 xlog(L_ERROR, "Failed to allocate path for " NSM_STATE_FILE);
464                 goto out;
465         }
466
467         fd = open(path, O_RDONLY);
468         if (fd == -1) {
469                 if (errno != ENOENT) {
470                         xlog(L_ERROR, "Failed to open %s: %m", path);
471                         goto out;
472                 }
473
474                 xlog(L_NOTICE, "Initializing NSM state");
475                 state = 1;
476                 update = true;
477                 goto update;
478         }
479
480         result = read(fd, &state, sizeof(state));
481         if (exact_error_check(result, sizeof(state))) {
482                 xlog_warn("Failed to read %s: %m", path);
483
484                 xlog(L_NOTICE, "Initializing NSM state");
485                 state = 1;
486                 update = true;
487                 goto update;
488         }
489
490         if ((state & 1) == 0)
491                 state++;
492
493 update:
494         (void)close(fd);
495
496         if (update) {
497                 state += 2;
498                 if (!nsm_atomic_write(path, &state, sizeof(state)))
499                         state = 0;
500         }
501
502 out:
503         free(path);
504         return state;
505 }
506
507 /**
508  * nsm_update_kernel_state - attempt to post new NSM state to kernel
509  * @state: NSM state number
510  *
511  */
512 void
513 nsm_update_kernel_state(const int state)
514 {
515         ssize_t result;
516         char buf[20];
517         int fd, len;
518
519         fd = open(NSM_KERNEL_STATE_FILE, O_WRONLY);
520         if (fd == -1) {
521                 xlog(D_GENERAL, "Failed to open " NSM_KERNEL_STATE_FILE ": %m");
522                 return;
523         }
524
525         len = snprintf(buf, sizeof(buf), "%d", state);
526         if (error_check(len, sizeof(buf))) {
527                 xlog_warn("Failed to form NSM state number string");
528                 return;
529         }
530
531         result = write(fd, buf, strlen(buf));
532         if (exact_error_check(result, strlen(buf)))
533                 xlog_warn("Failed to write NSM state number: %m");
534
535         if (close(fd) == -1)
536                 xlog(L_ERROR, "Failed to close NSM state file "
537                                 NSM_KERNEL_STATE_FILE ": %m");
538 }
539
540 /**
541  * nsm_retire_monitored_hosts - back up all hosts from "sm/" to "sm.bak/"
542  *
543  * Returns the count of host records that were moved.
544  *
545  * Note that if any error occurs during this process, some monitor
546  * records may be left in the "sm" directory.
547  */
548 unsigned int
549 nsm_retire_monitored_hosts(void)
550 {
551         unsigned int count = 0;
552         struct dirent *de;
553         char *path;
554         DIR *dir;
555
556         path = nsm_make_pathname(NSM_MONITOR_DIR);
557         if (path == NULL) {
558                 xlog(L_ERROR, "Failed to allocate path for " NSM_MONITOR_DIR);
559                 return count;
560         }
561
562         dir = opendir(path);
563         free(path);
564         if (dir == NULL) {
565                 xlog_warn("Failed to open " NSM_MONITOR_DIR ": %m");
566                 return count;
567         }
568
569         while ((de = readdir(dir)) != NULL) {
570                 char *src, *dst;
571                 struct stat stb;
572
573                 if (de->d_name[0] == '.')
574                         continue;
575
576                 src = nsm_make_record_pathname(NSM_MONITOR_DIR, de->d_name);
577                 if (src == NULL) {
578                         xlog_warn("Bad monitor file name, skipping");
579                         continue;
580                 }
581
582                 /* NB: not all file systems fill in d_type correctly */
583                 if (lstat(src, &stb) == -1) {
584                         xlog_warn("Bad monitor file %s, skipping: %m",
585                                         de->d_name);
586                         free(src);
587                         continue;
588                 }
589                 if (!S_ISREG(stb.st_mode)) {
590                         xlog(D_GENERAL, "Skipping non-regular file %s",
591                                         de->d_name);
592                         free(src);
593                         continue;
594                 }
595
596                 dst = nsm_make_record_pathname(NSM_NOTIFY_DIR, de->d_name);
597                 if (dst == NULL) {
598                         free(src);
599                         xlog_warn("Bad notify file name, skipping");
600                         continue;
601                 }
602
603                 if (rename(src, dst) == -1)
604                         xlog_warn("Failed to rename %s -> %s: %m",
605                                 src, dst);
606                 else {
607                         xlog(D_GENERAL, "Retired record for mon_name %s",
608                                         de->d_name);
609                         count++;
610                 }
611
612                 free(dst);
613                 free(src);
614         }
615
616         (void)closedir(dir);
617         return count;
618 }
619
620 /*
621  * nsm_priv_to_hex - convert a NSM private cookie to a hex string.
622  *
623  * @priv: buffer holding the binary NSM private cookie
624  * @buf: output buffer for NULL terminated hex string
625  * @buflen: size of output buffer
626  *
627  * Returns the length of the resulting string or 0 on error
628  */
629 size_t
630 nsm_priv_to_hex(const char *priv, char *buf, const size_t buflen)
631 {
632         int i, len;
633         size_t remaining = buflen;
634
635         for (i = 0; i < SM_PRIV_SIZE; i++) {
636                 len = snprintf(buf, remaining, "%02x",
637                                 (unsigned int)(0xff & priv[i]));
638                 if (error_check(len, remaining))
639                         return 0;
640                 buf += len;
641                 remaining -= (size_t)len;
642         }
643
644         return buflen - remaining;
645 }
646
647 /*
648  * Returns the length in bytes of the created record.
649  */
650 __attribute__((__noinline__))
651 static size_t
652 nsm_create_monitor_record(char *buf, const size_t buflen,
653                 const struct sockaddr *sap, const struct mon *m)
654 {
655         const struct sockaddr_in *sin = (const struct sockaddr_in *)sap;
656         size_t hexlen, remaining = buflen;
657         int len;
658
659         len = snprintf(buf, remaining, "%08x %08x %08x %08x ",
660                         (unsigned int)sin->sin_addr.s_addr,
661                         (unsigned int)m->mon_id.my_id.my_prog,
662                         (unsigned int)m->mon_id.my_id.my_vers,
663                         (unsigned int)m->mon_id.my_id.my_proc);
664         if (error_check(len, remaining))
665                 return 0;
666         buf += len;
667         remaining -= (size_t)len;
668
669         hexlen = nsm_priv_to_hex(m->priv, buf, remaining);
670         if (hexlen == 0)
671                 return 0;
672         buf += hexlen;
673         remaining -= hexlen;
674
675         len = snprintf(buf, remaining, " %s %s\n",
676                         m->mon_id.mon_name, m->mon_id.my_id.my_name);
677         if (error_check(len, remaining))
678                 return 0;
679         remaining -= (size_t)len;
680
681         return buflen - remaining;
682 }
683
684 static _Bool
685 nsm_append_monitored_host(const char *path, const char *line)
686 {
687         _Bool result = false;
688         char *buf = NULL;
689         struct stat stb;
690         size_t buflen;
691         ssize_t len;
692         int fd;
693
694         if (stat(path, &stb) == -1) {
695                 xlog(L_ERROR, "Failed to insert: "
696                         "could not stat original file %s: %m", path);
697                 goto out;
698         }
699         buflen = (size_t)stb.st_size + strlen(line);
700
701         buf = malloc(buflen + 1);
702         if (buf == NULL) {
703                 xlog(L_ERROR, "Failed to insert: no memory");
704                 goto out;
705         }
706         memset(buf, 0, buflen + 1);
707
708         fd = open(path, O_RDONLY);
709         if (fd == -1) {
710                 xlog(L_ERROR, "Failed to insert: "
711                         "could not open original file %s: %m", path);
712                 goto out;
713         }
714
715         len = read(fd, buf, (size_t)stb.st_size);
716         if (exact_error_check(len, (size_t)stb.st_size)) {
717                 xlog(L_ERROR, "Failed to insert: "
718                         "could not read original file %s: %m", path);
719                 (void)close(fd);
720                 goto out;
721         }
722         (void)close(fd);
723
724         strcat(buf, line);
725
726         if (nsm_atomic_write(path, buf, buflen))
727                 result = true;
728
729 out:
730         free(buf);
731         return result;
732 }
733
734 /**
735  * nsm_insert_monitored_host - write callback data for one host to disk
736  * @hostname: C string containing a hostname
737  * @sap: sockaddr containing NLM callback address
738  * @mon: SM_MON arguments to save
739  *
740  * Returns true if successful, otherwise false if some error occurs.
741  */
742 _Bool
743 nsm_insert_monitored_host(const char *hostname, const struct sockaddr *sap,
744                 const struct mon *m)
745 {
746         static char buf[LINELEN + 1 + SM_MAXSTRLEN + 2];
747         char *path;
748         _Bool result = false;
749         ssize_t len;
750         size_t size;
751         int fd;
752
753         path = nsm_make_record_pathname(NSM_MONITOR_DIR, hostname);
754         if (path == NULL) {
755                 xlog(L_ERROR, "Failed to insert: bad monitor hostname '%s'",
756                                 hostname);
757                 return false;
758         }
759
760         size = nsm_create_monitor_record(buf, sizeof(buf), sap, m);
761         if (size == 0) {
762                 xlog(L_ERROR, "Failed to insert: record too long");
763                 goto out;
764         }
765
766         /*
767          * If exclusive create fails, we're adding a new line to an
768          * existing file.
769          */
770         fd = open(path, O_WRONLY | O_CREAT | O_EXCL | O_SYNC, S_IRUSR | S_IWUSR);
771         if (fd == -1) {
772                 if (errno != EEXIST) {
773                         xlog(L_ERROR, "Failed to insert: creating %s: %m", path);
774                         goto out;
775                 }
776
777                 result = nsm_append_monitored_host(path, buf);
778                 goto out;
779         }
780         result = true;
781
782         len = write(fd, buf, size);
783         if (exact_error_check(len, size)) {
784                 xlog_warn("Failed to insert: writing %s: %m", path);
785                 (void)unlink(path);
786                 result = false;
787         }
788
789         if (close(fd) == -1) {
790                 xlog(L_ERROR, "Failed to insert: closing %s: %m", path);
791                 (void)unlink(path);
792                 result = false;
793         }
794
795 out:
796         free(path);
797         return result;
798 }
799
800 __attribute__((__noinline__))
801 static _Bool
802 nsm_parse_line(char *line, struct sockaddr_in *sin, struct mon *m)
803 {
804         unsigned int i, tmp;
805         int count;
806         char *c;
807
808         c = strchr(line, '\n');
809         if (c != NULL)
810                 *c = '\0';
811
812         count = sscanf(line, "%8x %8x %8x %8x ",
813                         (unsigned int *)&sin->sin_addr.s_addr,
814                         (unsigned int *)&m->mon_id.my_id.my_prog,
815                         (unsigned int *)&m->mon_id.my_id.my_vers,
816                         (unsigned int *)&m->mon_id.my_id.my_proc);
817         if (count != 4)
818                 return false;
819
820         c = line + RPCARGSLEN;
821         for (i = 0; i < SM_PRIV_SIZE; i++) {
822                 if (sscanf(c, "%2x", &tmp) != 1)
823                         return false;
824                 m->priv[i] = (char)tmp;
825                 c += 2;
826         }
827
828         c++;
829         m->mon_id.mon_name = c;
830         while (*c != '\0' && *c != ' ')
831                 c++;
832         if (*c != '\0')
833                 *c++ = '\0';
834         while (*c == ' ')
835                 c++;
836         m->mon_id.my_id.my_name = c;
837
838         return true;
839 }
840
841 /*
842  * Stuff a 'struct mon' with callback data, and call @func.
843  *
844  * Returns the count of in-core records created.
845  */
846 static unsigned int
847 nsm_read_line(const char *hostname, const time_t timestamp, char *line,
848                 nsm_populate_t func)
849 {
850         struct sockaddr_in sin = {
851                 .sin_family             = AF_INET,
852         };
853         struct mon m;
854
855         if (!nsm_parse_line(line, &sin, &m))
856                 return 0;
857
858         return func(hostname, (struct sockaddr *)(char *)&sin, &m, timestamp);
859 }
860
861 /*
862  * Given a filename, reads data from a file under "directory"
863  * and invokes @func so caller can populate their in-core
864  * database with this data.
865  */
866 static unsigned int
867 nsm_load_host(const char *directory, const char *filename, nsm_populate_t func)
868 {
869         char buf[LINELEN + 1 + SM_MAXSTRLEN + 2];
870         unsigned int result = 0;
871         struct stat stb;
872         char *path;
873         FILE *f;
874
875         path = nsm_make_record_pathname(directory, filename);
876         if (path == NULL)
877                 goto out_err;
878
879         if (lstat(path, &stb) == -1) {
880                 xlog(L_ERROR, "Failed to stat %s: %m", path);
881                 goto out_freepath;
882         }
883         if (!S_ISREG(stb.st_mode)) {
884                 xlog(D_GENERAL, "Skipping non-regular file %s",
885                                 path);
886                 goto out_freepath;
887         }
888
889         f = fopen(path, "r");
890         if (f == NULL) {
891                 xlog(L_ERROR, "Failed to open %s: %m", path);
892                 goto out_freepath;
893         }
894
895         while (fgets(buf, (int)sizeof(buf), f) != NULL) {
896                 buf[sizeof(buf) - 1] = '\0';
897                 result += nsm_read_line(filename, stb.st_mtime, buf, func);
898         }
899         if (result == 0)
900                 xlog(L_ERROR, "Failed to read monitor data from %s", path);
901
902         (void)fclose(f);
903
904 out_freepath:
905         free(path);
906 out_err:
907         return result;
908 }
909
910 static unsigned int
911 nsm_load_dir(const char *directory, nsm_populate_t func)
912 {
913         unsigned int count = 0;
914         struct dirent *de;
915         char *path;
916         DIR *dir;
917
918         path = nsm_make_pathname(directory);
919         if (path == NULL) {
920                 xlog(L_ERROR, "Failed to allocate path for directory %s",
921                                 directory);
922                 return 0;
923         }
924
925         dir = opendir(path);
926         free(path);
927         if (dir == NULL) {
928                 xlog(L_ERROR, "Failed to open directory %s: %m",
929                                 directory);
930                 return 0;
931         }
932
933         while ((de = readdir(dir)) != NULL) {
934                 if (de->d_name[0] == '.')
935                         continue;
936
937                 count += nsm_load_host(directory, de->d_name, func);
938         }
939
940         (void)closedir(dir);
941         return count;
942 }
943
944 /**
945  * nsm_load_monitor_list - load list of hosts to monitor
946  * @func: callback function to create entry for one host
947  *
948  * Returns the count of hosts that were found in the directory.
949  */
950 unsigned int
951 nsm_load_monitor_list(nsm_populate_t func)
952 {
953         return nsm_load_dir(NSM_MONITOR_DIR, func);
954 }
955
956 /**
957  * nsm_load_notify_list - load list of hosts to notify
958  * @func: callback function to create entry for one host
959  *
960  * Returns the count of hosts that were found in the directory.
961  */
962 unsigned int
963 nsm_load_notify_list(nsm_populate_t func)
964 {
965         return nsm_load_dir(NSM_NOTIFY_DIR, func);
966 }
967
968 static void
969 nsm_delete_host(const char *directory, const char *hostname,
970                 const char *mon_name, const char *my_name)
971 {
972         char line[LINELEN + 1 + SM_MAXSTRLEN + 2];
973         char *outbuf = NULL;
974         struct stat stb;
975         char *path, *next;
976         size_t remaining;
977         FILE *f;
978
979         path = nsm_make_record_pathname(directory, hostname);
980         if (path == NULL) {
981                 xlog(L_ERROR, "Bad filename, not deleting");
982                 return;
983         }
984
985         if (stat(path, &stb) == -1) {
986                 xlog(L_ERROR, "Failed to delete: "
987                         "could not stat original file %s: %m", path);
988                 goto out;
989         }
990         remaining = (size_t)stb.st_size + 1;
991
992         outbuf = malloc(remaining);
993         if (outbuf == NULL) {
994                 xlog(L_ERROR, "Failed to delete: no memory");
995                 goto out;
996         }
997
998         f = fopen(path, "r");
999         if (f == NULL) {
1000                 xlog(L_ERROR, "Failed to delete: "
1001                         "could not open original file %s: %m", path);
1002                 goto out;
1003         }
1004
1005         /*
1006          * Walk the records in the file, and copy the non-matching
1007          * ones to our output buffer.
1008          */
1009         next = outbuf;
1010         while (fgets(line, (int)sizeof(line), f) != NULL) {
1011                 struct sockaddr_in sin;
1012                 struct mon m;
1013                 size_t len;
1014
1015                 if (!nsm_parse_line(line, &sin, &m)) {
1016                         xlog(L_ERROR, "Failed to delete: "
1017                                 "could not parse original file %s", path);
1018                         (void)fclose(f);
1019                         goto out;
1020                 }
1021
1022                 if (strcmp(mon_name, m.mon_id.mon_name) == 0 &&
1023                          strcmp(my_name, m.mon_id.my_id.my_name) == 0)
1024                         continue;
1025
1026                 /* nsm_parse_line destroys the contents of line[], so
1027                  * reconstruct the copy in our output buffer. */
1028                 len = nsm_create_monitor_record(next, remaining,
1029                                         (struct sockaddr *)(char *)&sin, &m);
1030                 if (len == 0) {
1031                         xlog(L_ERROR, "Failed to delete: "
1032                                 "could not construct output record");
1033                         (void)fclose(f);
1034                         goto out;
1035                 }
1036                 next += len;
1037                 remaining -= len;
1038         }
1039
1040         (void)fclose(f);
1041
1042         /*
1043          * If nothing was copied when we're done, then unlink the file.
1044          * Otherwise, atomically update the contents of the file.
1045          */
1046         if (next != outbuf) {
1047                 if (!nsm_atomic_write(path, outbuf, strlen(outbuf)))
1048                         xlog(L_ERROR, "Failed to delete: "
1049                                 "could not write new file %s: %m", path);
1050         } else {
1051                 if (unlink(path) == -1)
1052                         xlog(L_ERROR, "Failed to delete: "
1053                                 "could not unlink file %s: %m", path);
1054         }
1055
1056 out:
1057         free(outbuf);
1058         free(path);
1059 }
1060
1061 /**
1062  * nsm_delete_monitored_host - delete on-disk record for monitored host
1063  * @hostname: '\0'-terminated C string containing hostname of record to delete
1064  * @mon_name: '\0'-terminated C string containing monname of record to delete
1065  * @my_name: '\0'-terminated C string containing myname of record to delete
1066  *
1067  */
1068 void
1069 nsm_delete_monitored_host(const char *hostname, const char *mon_name,
1070                 const char *my_name)
1071 {
1072         nsm_delete_host(NSM_MONITOR_DIR, hostname, mon_name, my_name);
1073 }
1074
1075 /**
1076  * nsm_delete_notified_host - delete on-disk host record after notification
1077  * @hostname: '\0'-terminated C string containing hostname of record to delete
1078  * @mon_name: '\0'-terminated C string containing monname of record to delete
1079  * @my_name: '\0'-terminated C string containing myname of record to delete
1080  *
1081  */
1082 void
1083 nsm_delete_notified_host(const char *hostname, const char *mon_name,
1084                 const char *my_name)
1085 {
1086         nsm_delete_host(NSM_NOTIFY_DIR, hostname, mon_name, my_name);
1087 }