2 * Copyright 2009 Oracle. All rights reserved.
4 * This file is part of nfs-utils.
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.
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.
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/>.
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
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
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
42 * The next four fields are text strings of hexadecimal characters,
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
51 * The sixth field is the monitored host's mon_name, passed to statd
52 * via an SM_MON request.
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.
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'.
69 #include <sys/types.h>
88 #define RPCARGSLEN (4 * (8 + 1))
89 #define LINELEN (RPCARGSLEN + SM_PRIV_SIZE * 2 + 1)
91 #define NSM_KERNEL_STATE_FILE "/proc/sys/fs/nfs/nsm_local_state"
94 * Some distributions place statd's files in a subdirectory
96 #define NSM_PATH_EXTENSION
97 /* #define NSM_PATH_EXTENSION "/statd" */
99 #define NSM_DEFAULT_STATEDIR NFS_STATEDIR NSM_PATH_EXTENSION
101 static char nsm_base_dirname[PATH_MAX] = NSM_DEFAULT_STATEDIR;
103 #define NSM_MONITOR_DIR "sm"
104 #define NSM_NOTIFY_DIR "sm.bak"
105 #define NSM_STATE_FILE "state"
109 error_check(const int len, const size_t buflen)
111 return (len < 0) || ((size_t)len >= buflen);
115 exact_error_check(const ssize_t len, const size_t buflen)
117 return (len < 0) || ((size_t)len != buflen);
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).
127 nsm_make_record_pathname(const char *directory, const char *hostname)
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 ' ').
139 for (c = hostname; *c != '\0'; c++)
140 if (*c == '/' || isspace((int)*c) != 0) {
141 xlog(D_GENERAL, "Hostname contains invalid characters");
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");
153 xlog(D_GENERAL, "Failed to allocate memory for pathname");
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");
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).
175 nsm_make_pathname(const char *directory)
181 size = strlen(nsm_base_dirname) + strlen(directory) + 2;
189 len = snprintf(path, size, "%s/%s", nsm_base_dirname, directory);
190 if (error_check(len, size)) {
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
203 * This runs before logging is set up, so error messages are directed
206 * Returns true and sets up our pathnames, if @parentdir was valid
207 * and usable; otherwise false is returned.
210 nsm_setup_pathnames(const char *progname, const char *parentdir)
212 static char buf[PATH_MAX];
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));
223 /* Ensure we have a clean directory pathname */
224 strncpy(buf, parentdir, sizeof(buf));
227 (void)fprintf(stderr, "%s: Unusable directory %s",
228 progname, parentdir);
232 xlog(D_CALL, "Using %s as the state directory", parentdir);
233 strncpy(nsm_base_dirname, parentdir, sizeof(nsm_base_dirname));
238 * nsm_is_default_parentdir - check if parent directory is default
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.
245 nsm_is_default_parentdir(void)
247 return strcmp(nsm_base_dirname, NSM_DEFAULT_STATEDIR) == 0;
251 * nsm_drop_privileges - drop root privileges
252 * @pidfd: file descriptor of a pid file
254 * Returns true if successful, or false if some error occurred.
256 * Set our effective UID and GID to that of our on-disk database.
259 nsm_drop_privileges(const int pidfd)
263 (void)umask(S_IRWXO);
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.
271 if (lstat(nsm_base_dirname, &st) == -1) {
272 xlog(L_ERROR, "Failed to stat %s: %m", nsm_base_dirname);
276 if (st.st_uid == 0) {
277 xlog_warn("Running as root. "
278 "chown %s to choose different user", nsm_base_dirname);
282 if (chdir(nsm_base_dirname) == -1) {
283 xlog(L_ERROR, "Failed to change working directory to %s: %m",
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.
294 if (fchown(pidfd, st.st_uid, st.st_gid) == -1)
295 xlog_warn("Failed to change owner of pidfile: %m");
297 if (setgroups(0, NULL) == -1) {
298 xlog(L_ERROR, "Failed to drop supplementary groups: %m");
305 * setgid(2) first, as setuid(2) may remove privileges needed
306 * to set the group id.
308 if (setgid(st.st_gid) == -1 || setuid(st.st_uid) == -1) {
309 xlog(L_ERROR, "Failed to drop privileges: %m");
313 xlog(D_CALL, "Effective UID, GID: %u, %u", st.st_uid, st.st_gid);
318 * nsm_get_state - retrieve on-disk NSM state number
320 * Returns an odd NSM state number read from disk, or an initial
321 * state number. Zero is returned if some error occurs.
324 nsm_get_state(_Bool update)
329 char *newpath = NULL;
331 path = nsm_make_pathname(NSM_STATE_FILE);
333 xlog(L_ERROR, "Failed to allocate path for " NSM_STATE_FILE);
337 fd = open(path, O_RDONLY);
339 if (errno != ENOENT) {
340 xlog(L_ERROR, "Failed to open %s: %m", path);
344 xlog(L_NOTICE, "Initializing NSM state");
350 result = read(fd, &state, sizeof(state));
351 if (exact_error_check(result, sizeof(state))) {
352 xlog_warn("Failed to read %s: %m", path);
354 xlog(L_NOTICE, "Initializing NSM state");
360 if ((state & 1) == 0)
369 newpath = nsm_make_pathname(NSM_STATE_FILE ".new");
370 if (newpath == NULL) {
372 "Failed to create path for " NSM_STATE_FILE ".new");
377 fd = open(newpath, O_CREAT | O_SYNC | O_WRONLY, 0644);
379 xlog(L_ERROR, "Failed to create %s: %m", newpath);
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);
388 (void)unlink(newpath);
393 if (close(fd) == -1) {
394 xlog(L_ERROR, "Failed to close %s: %m", newpath);
395 (void)unlink(newpath);
400 if (rename(newpath, path) == -1) {
401 xlog(L_ERROR, "Failed to rename %s -> %s: %m",
403 (void)unlink(newpath);
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. */
421 * nsm_update_kernel_state - attempt to post new NSM state to kernel
422 * @state: NSM state number
426 nsm_update_kernel_state(const int state)
432 fd = open(NSM_KERNEL_STATE_FILE, O_WRONLY);
434 xlog(D_GENERAL, "Failed to open " NSM_KERNEL_STATE_FILE ": %m");
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");
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");
449 xlog(L_ERROR, "Failed to close NSM state file "
450 NSM_KERNEL_STATE_FILE ": %m");
454 * nsm_retire_monitored_hosts - back up all hosts from "sm/" to "sm.bak/"
456 * Returns the count of host records that were moved.
458 * Note that if any error occurs during this process, some monitor
459 * records may be left in the "sm" directory.
462 nsm_retire_monitored_hosts(void)
464 unsigned int count = 0;
469 path = nsm_make_pathname(NSM_MONITOR_DIR);
471 xlog(L_ERROR, "Failed to allocate path for " NSM_MONITOR_DIR);
478 xlog_warn("Failed to open " NSM_MONITOR_DIR ": %m");
482 while ((de = readdir(dir)) != NULL) {
485 if (de->d_type != (unsigned char)DT_REG)
487 if (de->d_name[0] == '.')
490 src = nsm_make_record_pathname(NSM_MONITOR_DIR, de->d_name);
492 xlog_warn("Bad monitor file name, skipping");
496 dst = nsm_make_record_pathname(NSM_NOTIFY_DIR, de->d_name);
499 xlog_warn("Bad notify file name, skipping");
503 if (rename(src, dst) == -1)
504 xlog_warn("Failed to rename %s -> %s: %m",
507 xlog(D_GENERAL, "Retired record for mon_name %s",
521 * nsm_priv_to_hex - convert a NSM private cookie to a hex string.
523 * @priv: buffer holding the binary NSM private cookie
524 * @buf: output buffer for NULL terminated hex string
525 * @buflen: size of output buffer
527 * Returns the length of the resulting string or 0 on error
530 nsm_priv_to_hex(const char *priv, char *buf, const size_t buflen)
533 size_t remaining = buflen;
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))
541 remaining -= (size_t)len;
544 return buflen - remaining;
548 * Returns the length in bytes of the created record.
550 __attribute_noinline__
552 nsm_create_monitor_record(char *buf, const size_t buflen,
553 const struct sockaddr *sap, const struct mon *m)
555 const struct sockaddr_in *sin = (const struct sockaddr_in *)sap;
556 size_t hexlen, remaining = buflen;
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))
567 remaining -= (size_t)len;
569 hexlen = nsm_priv_to_hex(m->priv, buf, remaining);
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))
579 remaining -= (size_t)len;
581 return buflen - remaining;
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
590 * Returns true if successful, otherwise false if some error occurs.
593 nsm_insert_monitored_host(const char *hostname, const struct sockaddr *sap,
596 static char buf[LINELEN + 1 + SM_MAXSTRLEN + 2];
598 _Bool result = false;
603 path = nsm_make_record_pathname(NSM_MONITOR_DIR, hostname);
605 xlog(L_ERROR, "Failed to insert: bad monitor hostname '%s'",
610 size = nsm_create_monitor_record(buf, sizeof(buf), sap, m);
612 xlog(L_ERROR, "Failed to insert: record too long");
616 fd = open(path, O_WRONLY | O_CREAT | O_SYNC, S_IRUSR | S_IWUSR);
618 xlog(L_ERROR, "Failed to insert: creating %s: %m", path);
623 len = write(fd, buf, size);
624 if (exact_error_check(len, size)) {
625 xlog_warn("Failed to insert: writing %s: %m", path);
630 if (close(fd) == -1) {
631 xlog(L_ERROR, "Failed to insert: closing %s: %m", path);
641 __attribute_noinline__
643 nsm_parse_line(char *line, struct sockaddr_in *sin, struct mon *m)
649 c = strchr(line, '\n');
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);
661 c = line + RPCARGSLEN;
662 for (i = 0; i < SM_PRIV_SIZE; i++) {
663 if (sscanf(c, "%2x", &tmp) != 1)
665 m->priv[i] = (char)tmp;
670 m->mon_id.mon_name = c;
671 while (*c != '\0' && *c != ' ')
677 m->mon_id.my_id.my_name = c;
683 * Stuff a 'struct mon' with callback data, and call @func.
685 * Returns the count of in-core records created.
688 nsm_read_line(const char *hostname, const time_t timestamp, char *line,
691 struct sockaddr_in sin = {
692 .sin_family = AF_INET,
696 if (!nsm_parse_line(line, &sin, &m))
699 return func(hostname, (struct sockaddr *)(char *)&sin, &m, timestamp);
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.
708 nsm_load_host(const char *directory, const char *filename, nsm_populate_t func)
710 char buf[LINELEN + 1 + SM_MAXSTRLEN + 2];
711 unsigned int result = 0;
716 path = nsm_make_record_pathname(directory, filename);
720 if (stat(path, &stb) == -1) {
721 xlog(L_ERROR, "Failed to stat %s: %m", path);
725 f = fopen(path, "r");
727 xlog(L_ERROR, "Failed to open %s: %m", path);
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);
736 xlog(L_ERROR, "Failed to read monitor data from %s", path);
747 nsm_load_dir(const char *directory, nsm_populate_t func)
749 unsigned int count = 0;
754 path = nsm_make_pathname(directory);
756 xlog(L_ERROR, "Failed to allocate path for directory %s",
764 xlog(L_ERROR, "Failed to open directory %s: %m",
769 while ((de = readdir(dir)) != NULL) {
770 if (de->d_type != (unsigned char)DT_REG)
772 if (de->d_name[0] == '.')
775 count += nsm_load_host(directory, de->d_name, func);
783 * nsm_load_monitor_list - load list of hosts to monitor
784 * @func: callback function to create entry for one host
786 * Returns the count of hosts that were found in the directory.
789 nsm_load_monitor_list(nsm_populate_t func)
791 return nsm_load_dir(NSM_MONITOR_DIR, func);
795 * nsm_load_notify_list - load list of hosts to notify
796 * @func: callback function to create entry for one host
798 * Returns the count of hosts that were found in the directory.
801 nsm_load_notify_list(nsm_populate_t func)
803 return nsm_load_dir(NSM_NOTIFY_DIR, func);
807 nsm_delete_host(const char *directory, const char *hostname)
811 path = nsm_make_record_pathname(directory, hostname);
813 xlog(L_ERROR, "Bad filename, not deleting");
817 if (unlink(path) == -1)
818 xlog(L_ERROR, "Failed to unlink %s: %m", path);
824 * nsm_delete_monitored_host - delete on-disk record for monitored host
825 * @hostname: '\0'-terminated C string containing hostname of record to delete
829 nsm_delete_monitored_host(const char *hostname)
831 nsm_delete_host(NSM_MONITOR_DIR, hostname);
835 * nsm_delete_notified_host - delete on-disk host record after notification
836 * @hostname: '\0'-terminated C string containing hostname of record to delete
840 nsm_delete_notified_host(const char *hostname)
842 nsm_delete_host(NSM_NOTIFY_DIR, hostname);