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