/* * Copyright 2009 Oracle. All rights reserved. * * This file is part of nfs-utils. * * nfs-utils is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * nfs-utils is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with nfs-utils. If not, see . */ /* * NSM for Linux. * * Callback information and NSM state is stored in files, usually * under /var/lib/nfs. A database of information contained in local * files stores NLM callback data and what remote peers to notify of * reboots. * * For each monitored remote peer, a text file is created under the * directory specified by NSM_MONITOR_DIR. The name of the file * is a valid DNS hostname. The hostname string must be a valid * ASCII DNS name, and must not contain slash characters, white space, * or '\0' (ie. anything that might have some special meaning in a * path name). * * The contents of each file include seven blank-separated fields of * text, finished with '\n'. The first field contains the network * address of the NLM service to call back. The current implementation * supports using only IPv4 addresses, so the only contents of this * field are a network order IPv4 address expressed in 8 hexadecimal * characters. * * The next four fields are text strings of hexadecimal characters, * representing: * * 2. A 4 byte RPC program number of the NLM service to call back * 3. A 4 byte RPC version number of the NLM service to call back * 4. A 4 byte RPC procedure number of the NLM service to call back * 5. A 16 byte opaque cookie that the NLM service uses to identify * the monitored host * * The sixth field is the monitored host's mon_name, passed to statd * via an SM_MON request. * * The seventh field is the my_name for this peer, which is the * hostname of the local NLM (currently on Linux, the result of * `uname -n`). This can be used as the source address/hostname * when sending SM_NOTIFY requests. * * The NSM protocol does not limit the contents of these strings * in any way except that they must fit into 1024 bytes. Our * implementation requires that these strings not contain * white space or '\0'. */ #ifdef HAVE_CONFIG_H #include #endif #include #ifdef HAVE_SYS_CAPABILITY_H #include #endif #include #include #include #include #include #ifndef S_SPLINT_S #include #endif #include #include #include #include #include #include #include "xlog.h" #include "nsm.h" #define RPCARGSLEN (4 * (8 + 1)) #define LINELEN (RPCARGSLEN + SM_PRIV_SIZE * 2 + 1) #define NSM_KERNEL_STATE_FILE "/proc/sys/fs/nfs/nsm_local_state" static char nsm_base_dirname[PATH_MAX] = NSM_DEFAULT_STATEDIR; #define NSM_MONITOR_DIR "sm" #define NSM_NOTIFY_DIR "sm.bak" #define NSM_STATE_FILE "state" static _Bool error_check(const int len, const size_t buflen) { return (len < 0) || ((size_t)len >= buflen); } static _Bool exact_error_check(const ssize_t len, const size_t buflen) { return (len < 0) || ((size_t)len != buflen); } /* * Returns a dynamically allocated, '\0'-terminated buffer * containing an appropriate pathname, or NULL if an error * occurs. Caller must free the returned result with free(3). */ __attribute__((__malloc__)) static char * nsm_make_record_pathname(const char *directory, const char *hostname) { const char *c; size_t size; char *path; int len; /* * Block hostnames that contain characters that have * meaning to the file system (like '/'), or that can * be confusing on visual inspection (like ' '). */ for (c = hostname; *c != '\0'; c++) if (*c == '/' || isspace((int)*c) != 0) { xlog(D_GENERAL, "Hostname contains invalid characters"); return NULL; } size = strlen(nsm_base_dirname) + strlen(directory) + strlen(hostname) + 3; if (size > PATH_MAX) { xlog(D_GENERAL, "Hostname results in pathname that is too long"); return NULL; } path = malloc(size); if (path == NULL) { xlog(D_GENERAL, "Failed to allocate memory for pathname"); return NULL; } len = snprintf(path, size, "%s/%s/%s", nsm_base_dirname, directory, hostname); if (error_check(len, size)) { xlog(D_GENERAL, "Pathname did not fit in specified buffer"); free(path); return NULL; } return path; } /* * Returns a dynamically allocated, '\0'-terminated buffer * containing an appropriate pathname, or NULL if an error * occurs. Caller must free the returned result with free(3). */ __attribute__((__malloc__)) static char * nsm_make_pathname(const char *directory) { size_t size; char *path; int len; size = strlen(nsm_base_dirname) + strlen(directory) + 2; if (size > PATH_MAX) return NULL; path = malloc(size); if (path == NULL) return NULL; len = snprintf(path, size, "%s/%s", nsm_base_dirname, directory); if (error_check(len, size)) { free(path); return NULL; } return path; } /* * Returns a dynamically allocated, '\0'-terminated buffer * containing an appropriate pathname, or NULL if an error * occurs. Caller must free the returned result with free(3). */ __attribute__((__malloc__)) static char * nsm_make_temp_pathname(const char *pathname) { size_t size; char *path; int len; size = strlen(pathname) + sizeof(".new") + 2; if (size > PATH_MAX) return NULL; path = malloc(size); if (path == NULL) return NULL; len = snprintf(path, size, "%s.new", pathname); if (error_check(len, size)) { free(path); return NULL; } return path; } /* * Use "mktemp, write, rename" to update the contents of a file atomically. * * Returns true if completely successful, or false if some error occurred. */ static _Bool nsm_atomic_write(const char *path, const void *buf, const size_t buflen) { _Bool result = false; ssize_t len; char *temp; int fd; temp = nsm_make_temp_pathname(path); if (temp == NULL) { xlog(L_ERROR, "Failed to create new path for %s", path); goto out; } fd = open(temp, O_CREAT | O_TRUNC | O_SYNC | O_WRONLY, 0644); if (fd == -1) { xlog(L_ERROR, "Failed to create %s: %m", temp); goto out; } len = write(fd, buf, buflen); if (exact_error_check(len, buflen)) { xlog(L_ERROR, "Failed to write %s: %m", temp); (void)close(fd); (void)unlink(temp); goto out; } if (close(fd) == -1) { xlog(L_ERROR, "Failed to close %s: %m", temp); (void)unlink(temp); goto out; } if (rename(temp, path) == -1) { xlog(L_ERROR, "Failed to rename %s -> %s: %m", temp, path); (void)unlink(temp); goto out; } /* Ostensibly, a sync(2) is not needed here because * open(O_CREAT), write(O_SYNC), and rename(2) are * already synchronous with persistent storage, for * any file system we care about. */ result = true; out: free(temp); return result; } /** * nsm_setup_pathnames - set up pathname * @progname: C string containing name of program, for error messages * @parentdir: C string containing pathname to on-disk state, or NULL * * This runs before logging is set up, so error messages are directed * to stderr. * * Returns true and sets up our pathnames, if @parentdir was valid * and usable; otherwise false is returned. */ _Bool nsm_setup_pathnames(const char *progname, const char *parentdir) { static char buf[PATH_MAX]; struct stat st; char *path; /* First: test length of name and whether it exists */ if (lstat(parentdir, &st) == -1) { (void)fprintf(stderr, "%s: Failed to stat %s: %s", progname, parentdir, strerror(errno)); return false; } /* Ensure we have a clean directory pathname */ strncpy(buf, parentdir, sizeof(buf)); path = dirname(buf); if (*path == '.') { (void)fprintf(stderr, "%s: Unusable directory %s", progname, parentdir); return false; } xlog(D_CALL, "Using %s as the state directory", parentdir); strncpy(nsm_base_dirname, parentdir, sizeof(nsm_base_dirname)); return true; } /** * nsm_is_default_parentdir - check if parent directory is default * * Returns true if the active statd parent directory, set by * nsm_change_pathname(), is the same as the built-in default * parent directory; otherwise false is returned. */ _Bool nsm_is_default_parentdir(void) { return strcmp(nsm_base_dirname, NSM_DEFAULT_STATEDIR) == 0; } /* * Clear all capabilities but CAP_NET_BIND_SERVICE. This permits * callers to acquire privileged source ports, but all other root * capabilities are disallowed. * * Returns true if successful, or false if some error occurred. */ #ifdef HAVE_SYS_CAPABILITY_H static _Bool nsm_clear_capabilities(void) { cap_t caps; caps = cap_from_text("cap_net_bind_service=ep"); if (caps == NULL) { xlog(L_ERROR, "Failed to allocate capability: %m"); return false; } if (cap_set_proc(caps) == -1) { xlog(L_ERROR, "Failed to set capability flags: %m"); (void)cap_free(caps); return false; } (void)cap_free(caps); return true; } #define CAP_BOUND_PROCFILE "/proc/sys/kernel/cap-bound" static _Bool prune_bounding_set(void) { #ifdef PR_CAPBSET_DROP int ret; unsigned long i; struct stat st; /* * Prior to kernel 2.6.25, the capabilities bounding set was a global * value. Check to see if /proc/sys/kernel/cap-bound exists and don't * bother to clear the bounding set if it does. */ ret = stat(CAP_BOUND_PROCFILE, &st); if (!ret) { xlog(L_WARNING, "%s exists. Not attempting to clear " "capabilities bounding set.", CAP_BOUND_PROCFILE); return true; } else if (errno != ENOENT) { /* Warn, but attempt to clear the bounding set anyway. */ xlog(L_WARNING, "Unable to stat %s: %m", CAP_BOUND_PROCFILE); } /* prune the bounding set to nothing */ for (i = 0; i <= CAP_LAST_CAP; ++i) { ret = prctl(PR_CAPBSET_DROP, i, 0, 0, 0); if (ret) { xlog(L_ERROR, "Unable to prune capability %lu from " "bounding set: %m", i); return false; } } #endif /* PR_CAPBSET_DROP */ return true; } #else /* !HAVE_SYS_CAPABILITY_H */ static _Bool nsm_clear_capabilities(void) { return true; } static _Bool prune_bounding_set(void) { return true; } #endif /* HAVE_SYS_CAPABILITY_H */ /** * nsm_drop_privileges - drop root privileges * @pidfd: file descriptor of a pid file * * Returns true if successful, or false if some error occurred. * * Set our effective UID and GID to that of our on-disk database. */ _Bool nsm_drop_privileges(const int pidfd) { struct stat st; (void)umask(S_IRWXO); /* * XXX: If we can't stat dirname, or if dirname is owned by * root, we should use "statduser" instead, which is set up * by configure.ac. Nothing in nfs-utils seems to use * "statduser," though. */ if (lstat(nsm_base_dirname, &st) == -1) { xlog(L_ERROR, "Failed to stat %s: %m", nsm_base_dirname); return false; } if (chdir(nsm_base_dirname) == -1) { xlog(L_ERROR, "Failed to change working directory to %s: %m", nsm_base_dirname); return false; } if (!prune_bounding_set()) return false; if (st.st_uid == 0) { xlog_warn("Running as root. " "chown %s to choose different user", nsm_base_dirname); return true; } /* * If the pidfile happens to reside on NFS, dropping privileges * will probably cause us to lose access, even though we are * holding it open. Chown it to prevent this. */ if (pidfd >= 0) if (fchown(pidfd, st.st_uid, st.st_gid) == -1) xlog_warn("Failed to change owner of pidfile: %m"); /* * Don't clear capabilities when dropping root. */ if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) == -1) { xlog(L_ERROR, "prctl(PR_SET_KEEPCAPS) failed: %m"); return false; } if (setgroups(0, NULL) == -1) { xlog(L_ERROR, "Failed to drop supplementary groups: %m"); return false; } /* * ORDER * * setgid(2) first, as setuid(2) may remove privileges needed * to set the group id. */ if (setgid(st.st_gid) == -1 || setuid(st.st_uid) == -1) { xlog(L_ERROR, "Failed to drop privileges: %m"); return false; } xlog(D_CALL, "Effective UID, GID: %u, %u", st.st_uid, st.st_gid); return nsm_clear_capabilities(); } /** * nsm_get_state - retrieve on-disk NSM state number * * Returns an odd NSM state number read from disk, or an initial * state number. Zero is returned if some error occurs. */ int nsm_get_state(_Bool update) { int fd, state = 0; ssize_t result; char *path = NULL; path = nsm_make_pathname(NSM_STATE_FILE); if (path == NULL) { xlog(L_ERROR, "Failed to allocate path for " NSM_STATE_FILE); goto out; } fd = open(path, O_RDONLY); if (fd == -1) { if (errno != ENOENT) { xlog(L_ERROR, "Failed to open %s: %m", path); goto out; } xlog(L_NOTICE, "Initializing NSM state"); state = 1; update = true; goto update; } result = read(fd, &state, sizeof(state)); if (exact_error_check(result, sizeof(state))) { xlog_warn("Failed to read %s: %m", path); xlog(L_NOTICE, "Initializing NSM state"); state = 1; update = true; goto update; } if ((state & 1) == 0) state++; update: (void)close(fd); if (update) { state += 2; if (!nsm_atomic_write(path, &state, sizeof(state))) state = 0; } out: free(path); return state; } /** * nsm_update_kernel_state - attempt to post new NSM state to kernel * @state: NSM state number * */ void nsm_update_kernel_state(const int state) { ssize_t result; char buf[20]; int fd, len; fd = open(NSM_KERNEL_STATE_FILE, O_WRONLY); if (fd == -1) { xlog(D_GENERAL, "Failed to open " NSM_KERNEL_STATE_FILE ": %m"); return; } len = snprintf(buf, sizeof(buf), "%d", state); if (error_check(len, sizeof(buf))) { xlog_warn("Failed to form NSM state number string"); return; } result = write(fd, buf, strlen(buf)); if (exact_error_check(result, strlen(buf))) xlog_warn("Failed to write NSM state number: %m"); if (close(fd) == -1) xlog(L_ERROR, "Failed to close NSM state file " NSM_KERNEL_STATE_FILE ": %m"); } /** * nsm_retire_monitored_hosts - back up all hosts from "sm/" to "sm.bak/" * * Returns the count of host records that were moved. * * Note that if any error occurs during this process, some monitor * records may be left in the "sm" directory. */ unsigned int nsm_retire_monitored_hosts(void) { unsigned int count = 0; struct dirent *de; char *path; DIR *dir; path = nsm_make_pathname(NSM_MONITOR_DIR); if (path == NULL) { xlog(L_ERROR, "Failed to allocate path for " NSM_MONITOR_DIR); return count; } dir = opendir(path); free(path); if (dir == NULL) { xlog_warn("Failed to open " NSM_MONITOR_DIR ": %m"); return count; } while ((de = readdir(dir)) != NULL) { char *src, *dst; struct stat stb; if (de->d_name[0] == '.') continue; src = nsm_make_record_pathname(NSM_MONITOR_DIR, de->d_name); if (src == NULL) { xlog_warn("Bad monitor file name, skipping"); continue; } /* NB: not all file systems fill in d_type correctly */ if (lstat(src, &stb) == -1) { xlog_warn("Bad monitor file %s, skipping: %m", de->d_name); free(src); continue; } if (!S_ISREG(stb.st_mode)) { xlog(D_GENERAL, "Skipping non-regular file %s", de->d_name); free(src); continue; } dst = nsm_make_record_pathname(NSM_NOTIFY_DIR, de->d_name); if (dst == NULL) { free(src); xlog_warn("Bad notify file name, skipping"); continue; } if (rename(src, dst) == -1) xlog_warn("Failed to rename %s -> %s: %m", src, dst); else { xlog(D_GENERAL, "Retired record for mon_name %s", de->d_name); count++; } free(dst); free(src); } (void)closedir(dir); return count; } /* * nsm_priv_to_hex - convert a NSM private cookie to a hex string. * * @priv: buffer holding the binary NSM private cookie * @buf: output buffer for NULL terminated hex string * @buflen: size of output buffer * * Returns the length of the resulting string or 0 on error */ size_t nsm_priv_to_hex(const char *priv, char *buf, const size_t buflen) { int i, len; size_t remaining = buflen; for (i = 0; i < SM_PRIV_SIZE; i++) { len = snprintf(buf, remaining, "%02x", (unsigned int)(0xff & priv[i])); if (error_check(len, remaining)) return 0; buf += len; remaining -= (size_t)len; } return buflen - remaining; } /* * Returns the length in bytes of the created record. */ __attribute__((__noinline__)) static size_t nsm_create_monitor_record(char *buf, const size_t buflen, const struct sockaddr *sap, const struct mon *m) { const struct sockaddr_in *sin = (const struct sockaddr_in *)sap; size_t hexlen, remaining = buflen; int len; len = snprintf(buf, remaining, "%08x %08x %08x %08x ", (unsigned int)sin->sin_addr.s_addr, (unsigned int)m->mon_id.my_id.my_prog, (unsigned int)m->mon_id.my_id.my_vers, (unsigned int)m->mon_id.my_id.my_proc); if (error_check(len, remaining)) return 0; buf += len; remaining -= (size_t)len; hexlen = nsm_priv_to_hex(m->priv, buf, remaining); if (hexlen == 0) return 0; buf += hexlen; remaining -= hexlen; len = snprintf(buf, remaining, " %s %s\n", m->mon_id.mon_name, m->mon_id.my_id.my_name); if (error_check(len, remaining)) return 0; remaining -= (size_t)len; return buflen - remaining; } static _Bool nsm_append_monitored_host(const char *path, const char *line) { _Bool result = false; char *buf = NULL; struct stat stb; size_t buflen; ssize_t len; int fd; if (stat(path, &stb) == -1) { xlog(L_ERROR, "Failed to insert: " "could not stat original file %s: %m", path); goto out; } buflen = (size_t)stb.st_size + strlen(line); buf = malloc(buflen + 1); if (buf == NULL) { xlog(L_ERROR, "Failed to insert: no memory"); goto out; } memset(buf, 0, buflen + 1); fd = open(path, O_RDONLY); if (fd == -1) { xlog(L_ERROR, "Failed to insert: " "could not open original file %s: %m", path); goto out; } len = read(fd, buf, (size_t)stb.st_size); if (exact_error_check(len, (size_t)stb.st_size)) { xlog(L_ERROR, "Failed to insert: " "could not read original file %s: %m", path); (void)close(fd); goto out; } (void)close(fd); strcat(buf, line); if (nsm_atomic_write(path, buf, buflen)) result = true; out: free(buf); return result; } /** * nsm_insert_monitored_host - write callback data for one host to disk * @hostname: C string containing a hostname * @sap: sockaddr containing NLM callback address * @mon: SM_MON arguments to save * * Returns true if successful, otherwise false if some error occurs. */ _Bool nsm_insert_monitored_host(const char *hostname, const struct sockaddr *sap, const struct mon *m) { static char buf[LINELEN + 1 + SM_MAXSTRLEN + 2]; char *path; _Bool result = false; ssize_t len; size_t size; int fd; path = nsm_make_record_pathname(NSM_MONITOR_DIR, hostname); if (path == NULL) { xlog(L_ERROR, "Failed to insert: bad monitor hostname '%s'", hostname); return false; } size = nsm_create_monitor_record(buf, sizeof(buf), sap, m); if (size == 0) { xlog(L_ERROR, "Failed to insert: record too long"); goto out; } /* * If exclusive create fails, we're adding a new line to an * existing file. */ fd = open(path, O_WRONLY | O_CREAT | O_EXCL | O_SYNC, S_IRUSR | S_IWUSR); if (fd == -1) { if (errno != EEXIST) { xlog(L_ERROR, "Failed to insert: creating %s: %m", path); goto out; } result = nsm_append_monitored_host(path, buf); goto out; } result = true; len = write(fd, buf, size); if (exact_error_check(len, size)) { xlog_warn("Failed to insert: writing %s: %m", path); (void)unlink(path); result = false; } if (close(fd) == -1) { xlog(L_ERROR, "Failed to insert: closing %s: %m", path); (void)unlink(path); result = false; } out: free(path); return result; } __attribute__((__noinline__)) static _Bool nsm_parse_line(char *line, struct sockaddr_in *sin, struct mon *m) { unsigned int i, tmp; int count; char *c; c = strchr(line, '\n'); if (c != NULL) *c = '\0'; count = sscanf(line, "%8x %8x %8x %8x ", (unsigned int *)&sin->sin_addr.s_addr, (unsigned int *)&m->mon_id.my_id.my_prog, (unsigned int *)&m->mon_id.my_id.my_vers, (unsigned int *)&m->mon_id.my_id.my_proc); if (count != 4) return false; c = line + RPCARGSLEN; for (i = 0; i < SM_PRIV_SIZE; i++) { if (sscanf(c, "%2x", &tmp) != 1) return false; m->priv[i] = (char)tmp; c += 2; } c++; m->mon_id.mon_name = c; while (*c != '\0' && *c != ' ') c++; if (*c != '\0') *c++ = '\0'; while (*c == ' ') c++; m->mon_id.my_id.my_name = c; return true; } /* * Stuff a 'struct mon' with callback data, and call @func. * * Returns the count of in-core records created. */ static unsigned int nsm_read_line(const char *hostname, const time_t timestamp, char *line, nsm_populate_t func) { struct sockaddr_in sin = { .sin_family = AF_INET, }; struct mon m; if (!nsm_parse_line(line, &sin, &m)) return 0; return func(hostname, (struct sockaddr *)(char *)&sin, &m, timestamp); } /* * Given a filename, reads data from a file under "directory" * and invokes @func so caller can populate their in-core * database with this data. */ static unsigned int nsm_load_host(const char *directory, const char *filename, nsm_populate_t func) { char buf[LINELEN + 1 + SM_MAXSTRLEN + 2]; unsigned int result = 0; struct stat stb; char *path; FILE *f; path = nsm_make_record_pathname(directory, filename); if (path == NULL) goto out_err; if (lstat(path, &stb) == -1) { xlog(L_ERROR, "Failed to stat %s: %m", path); goto out_freepath; } if (!S_ISREG(stb.st_mode)) { xlog(D_GENERAL, "Skipping non-regular file %s", path); goto out_freepath; } f = fopen(path, "r"); if (f == NULL) { xlog(L_ERROR, "Failed to open %s: %m", path); goto out_freepath; } while (fgets(buf, (int)sizeof(buf), f) != NULL) { buf[sizeof(buf) - 1] = '\0'; result += nsm_read_line(filename, stb.st_mtime, buf, func); } if (result == 0) xlog(L_ERROR, "Failed to read monitor data from %s", path); (void)fclose(f); out_freepath: free(path); out_err: return result; } static unsigned int nsm_load_dir(const char *directory, nsm_populate_t func) { unsigned int count = 0; struct dirent *de; char *path; DIR *dir; path = nsm_make_pathname(directory); if (path == NULL) { xlog(L_ERROR, "Failed to allocate path for directory %s", directory); return 0; } dir = opendir(path); free(path); if (dir == NULL) { xlog(L_ERROR, "Failed to open directory %s: %m", directory); return 0; } while ((de = readdir(dir)) != NULL) { if (de->d_name[0] == '.') continue; count += nsm_load_host(directory, de->d_name, func); } (void)closedir(dir); return count; } /** * nsm_load_monitor_list - load list of hosts to monitor * @func: callback function to create entry for one host * * Returns the count of hosts that were found in the directory. */ unsigned int nsm_load_monitor_list(nsm_populate_t func) { return nsm_load_dir(NSM_MONITOR_DIR, func); } /** * nsm_load_notify_list - load list of hosts to notify * @func: callback function to create entry for one host * * Returns the count of hosts that were found in the directory. */ unsigned int nsm_load_notify_list(nsm_populate_t func) { return nsm_load_dir(NSM_NOTIFY_DIR, func); } static void nsm_delete_host(const char *directory, const char *hostname, const char *mon_name, const char *my_name) { char line[LINELEN + 1 + SM_MAXSTRLEN + 2]; char *outbuf = NULL; struct stat stb; char *path, *next; size_t remaining; FILE *f; path = nsm_make_record_pathname(directory, hostname); if (path == NULL) { xlog(L_ERROR, "Bad filename, not deleting"); return; } if (stat(path, &stb) == -1) { xlog(L_ERROR, "Failed to delete: " "could not stat original file %s: %m", path); goto out; } remaining = (size_t)stb.st_size + 1; outbuf = malloc(remaining); if (outbuf == NULL) { xlog(L_ERROR, "Failed to delete: no memory"); goto out; } f = fopen(path, "r"); if (f == NULL) { xlog(L_ERROR, "Failed to delete: " "could not open original file %s: %m", path); goto out; } /* * Walk the records in the file, and copy the non-matching * ones to our output buffer. */ next = outbuf; while (fgets(line, (int)sizeof(line), f) != NULL) { struct sockaddr_in sin; struct mon m; size_t len; if (!nsm_parse_line(line, &sin, &m)) { xlog(L_ERROR, "Failed to delete: " "could not parse original file %s", path); (void)fclose(f); goto out; } if (strcmp(mon_name, m.mon_id.mon_name) == 0 && strcmp(my_name, m.mon_id.my_id.my_name) == 0) continue; /* nsm_parse_line destroys the contents of line[], so * reconstruct the copy in our output buffer. */ len = nsm_create_monitor_record(next, remaining, (struct sockaddr *)(char *)&sin, &m); if (len == 0) { xlog(L_ERROR, "Failed to delete: " "could not construct output record"); (void)fclose(f); goto out; } next += len; remaining -= len; } (void)fclose(f); /* * If nothing was copied when we're done, then unlink the file. * Otherwise, atomically update the contents of the file. */ if (next != outbuf) { if (!nsm_atomic_write(path, outbuf, strlen(outbuf))) xlog(L_ERROR, "Failed to delete: " "could not write new file %s: %m", path); } else { if (unlink(path) == -1) xlog(L_ERROR, "Failed to delete: " "could not unlink file %s: %m", path); } out: free(outbuf); free(path); } /** * nsm_delete_monitored_host - delete on-disk record for monitored host * @hostname: '\0'-terminated C string containing hostname of record to delete * @mon_name: '\0'-terminated C string containing monname of record to delete * @my_name: '\0'-terminated C string containing myname of record to delete * */ void nsm_delete_monitored_host(const char *hostname, const char *mon_name, const char *my_name) { nsm_delete_host(NSM_MONITOR_DIR, hostname, mon_name, my_name); } /** * nsm_delete_notified_host - delete on-disk host record after notification * @hostname: '\0'-terminated C string containing hostname of record to delete * @mon_name: '\0'-terminated C string containing monname of record to delete * @my_name: '\0'-terminated C string containing myname of record to delete * */ void nsm_delete_notified_host(const char *hostname, const char *mon_name, const char *my_name) { nsm_delete_host(NSM_NOTIFY_DIR, hostname, mon_name, my_name); }