diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 06:03:02 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 06:03:02 +0000 |
commit | 4897093455a2bf08f3db3a1132cc2f6f5484d77c (patch) | |
tree | 9e6373544263f003139431fb4b08f9766e1ed530 /utils/statd/sm-notify.c | |
parent | Initial commit. (diff) | |
download | nfs-utils-4897093455a2bf08f3db3a1132cc2f6f5484d77c.tar.xz nfs-utils-4897093455a2bf08f3db3a1132cc2f6f5484d77c.zip |
Adding upstream version 1:2.6.4.upstream/1%2.6.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'utils/statd/sm-notify.c')
-rw-r--r-- | utils/statd/sm-notify.c | 928 |
1 files changed, 928 insertions, 0 deletions
diff --git a/utils/statd/sm-notify.c b/utils/statd/sm-notify.c new file mode 100644 index 0000000..ed82b8f --- /dev/null +++ b/utils/statd/sm-notify.c @@ -0,0 +1,928 @@ +/* + * Send NSM notify calls to all hosts listed in /var/lib/sm + * + * Copyright (C) 2004-2006 Olaf Kirch <okir@suse.de> + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <err.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <poll.h> +#include <sys/param.h> +#include <sys/syslog.h> +#include <arpa/inet.h> +#include <dirent.h> +#include <time.h> +#include <stdio.h> +#include <getopt.h> +#include <stdlib.h> +#include <fcntl.h> +#include <unistd.h> +#include <string.h> +#include <stdarg.h> +#include <netdb.h> +#include <errno.h> +#include <grp.h> +#include <netinet/in.h> +#include <arpa/nameser.h> +#include <resolv.h> + +#include "conffile.h" +#include "sockaddr.h" +#include "xlog.h" +#include "nsm.h" +#include "nfslib.h" +#include "nfsrpc.h" + +/* glibc before 2.3.4 */ +#ifndef AI_NUMERICSERV +#define AI_NUMERICSERV 0 +#endif + +#define NSM_TIMEOUT 2 +#define NSM_MAX_TIMEOUT 120 /* don't make this too big */ + +#define NLM_END_GRACE_FILE "/proc/fs/lockd/nlm_end_grace" + +int lift_grace = 1; +int force = 0; + +struct nsm_host { + struct nsm_host * next; + char * name; + const char * mon_name; + const char * my_name; + char * notify_arg; + struct addrinfo *ai; + time_t last_used; + time_t send_next; + unsigned int timeout; + unsigned int retries; + uint32_t xid; +}; + +static char nsm_hostname[SM_MAXSTRLEN + 1]; +static int nsm_state; +static int nsm_family = AF_INET; +static int opt_debug = 0; +static _Bool opt_update_state = true; +static unsigned int opt_max_retry = 15 * 60; +static char * opt_srcaddr = NULL; +static char * opt_srcport = NULL; + +static void notify(const int sock); +static int notify_host(int, struct nsm_host *); +static void recv_reply(int); +static void insert_host(struct nsm_host *); +static struct nsm_host *find_host(uint32_t); +static int record_pid(void); + +static struct nsm_host * hosts = NULL; + +__attribute__((__malloc__)) +static struct addrinfo * +smn_lookup(const char *name) +{ + struct addrinfo *ai = NULL; + struct addrinfo hint = { + .ai_family = (nsm_family == AF_INET ? AF_INET: AF_UNSPEC), + .ai_protocol = (int)IPPROTO_UDP, + }; + int error; + + res_init(); + error = getaddrinfo(name, NULL, &hint, &ai); + if (error != 0) { + xlog(D_GENERAL, "getaddrinfo(3): %s", gai_strerror(error)); + return NULL; + } + + return ai; +} + +#ifdef HAVE_GETNAMEINFO +static char * +smn_get_hostname(const struct sockaddr *sap, const socklen_t salen, + const char *name) +{ + char buf[NI_MAXHOST]; + int error; + + error = getnameinfo(sap, salen, buf, sizeof(buf), NULL, 0, NI_NAMEREQD); + if (error != 0) { + xlog(L_ERROR, "my_name '%s' is unusable: %s", + name, gai_strerror(error)); + return NULL; + } + return strdup(buf); +} +#else /* !HAVE_GETNAMEINFO */ +static char * +smn_get_hostname(const struct sockaddr *sap, + __attribute__ ((unused)) const socklen_t salen, + const char *name) +{ + const struct sockaddr_in *sin = (const struct sockaddr_in *)(char *)sap; + const struct in_addr *addr = &sin->sin_addr; + struct hostent *hp; + + if (sap->sa_family != AF_INET) { + xlog(L_ERROR, "my_name '%s' is unusable: Bad address family", + name); + return NULL; + } + + hp = gethostbyaddr(addr, (socklen_t)sizeof(addr), AF_INET); + if (hp == NULL) { + xlog(L_ERROR, "my_name '%s' is unusable: %s", + name, hstrerror(h_errno)); + return NULL; + } + return strdup(hp->h_name); +} +#endif /* !HAVE_GETNAMEINFO */ + +/* + * Presentation addresses are converted to their canonical hostnames. + * If the IP address does not map to a hostname, it is an error: + * we never send a presentation address as the argument of SM_NOTIFY. + * + * If "name" is not a presentation address, it is left alone. This + * allows the administrator some flexibility if DNS isn't configured + * exactly how sm-notify prefers it. + * + * Returns NUL-terminated C string containing the result, or NULL + * if the canonical name doesn't exist or cannot be determined. + * The caller must free the result with free(3). + */ +__attribute__((__malloc__)) +static char * +smn_verify_my_name(const char *name) +{ + struct addrinfo *ai = NULL; + struct addrinfo hint = { +#ifdef IPV6_SUPPORTED + .ai_family = AF_UNSPEC, +#else /* !IPV6_SUPPORTED */ + .ai_family = AF_INET, +#endif /* !IPV6_SUPPORTED */ + .ai_flags = AI_NUMERICHOST, + }; + char *retval; + int error; + + error = getaddrinfo(name, NULL, &hint, &ai); + switch (error) { + case 0: + /* @name was a presentation address */ + retval = smn_get_hostname(ai->ai_addr, ai->ai_addrlen, name); + nfs_freeaddrinfo(ai); + if (retval == NULL) + return NULL; + break; + case EAI_NONAME: + /* @name was not a presentation address */ + retval = strdup(name); + break; + default: + xlog(L_ERROR, "my_name '%s' is unusable: %s", + name, gai_strerror(error)); + return NULL; + } + + xlog(D_GENERAL, "Canonical name for my_name '%s': %s", + name, retval); + return retval; +} + +__attribute__((__malloc__)) +static struct nsm_host * +smn_alloc_host(const char *hostname, const char *mon_name, + const char *my_name, const time_t timestamp) +{ + struct nsm_host *host; + + host = calloc(1, sizeof(*host)); + if (host == NULL) + goto out_nomem; + + /* + * mon_name and my_name are preserved so sm-notify can + * find the right monitor record to remove when it is + * done processing this host. + */ + host->name = strdup(hostname); + host->mon_name = (const char *)strdup(mon_name); + host->my_name = (const char *)strdup(my_name); + host->notify_arg = strdup(opt_srcaddr != NULL ? + nsm_hostname : my_name); + if (host->name == NULL || + host->mon_name == NULL || + host->my_name == NULL || + host->notify_arg == NULL) { + free(host->notify_arg); + free((void *)host->my_name); + free((void *)host->mon_name); + free(host->name); + free(host); + goto out_nomem; + } + + host->last_used = timestamp; + host->timeout = NSM_TIMEOUT; + host->retries = 100; /* force address retry */ + + return host; + +out_nomem: + xlog_warn("Unable to allocate memory"); + return NULL; +} + +static void smn_forget_host(struct nsm_host *host) +{ + xlog(D_CALL, "Removing %s (%s, %s) from notify list", + host->name, host->mon_name, host->my_name); + + nsm_delete_notified_host(host->name, host->mon_name, host->my_name); + + free(host->notify_arg); + free((void *)host->my_name); + free((void *)host->mon_name); + free(host->name); + nfs_freeaddrinfo(host->ai); + + free(host); +} + +static unsigned int +smn_get_host(const char *hostname, + __attribute__ ((unused)) const struct sockaddr *sap, + const struct mon *m, const time_t timestamp) +{ + struct nsm_host *host; + + host = smn_alloc_host(hostname, + m->mon_id.mon_name, m->mon_id.my_id.my_name, timestamp); + if (host == NULL) + return 0; + + insert_host(host); + return 1; +} + +#ifdef IPV6_SUPPORTED +static int smn_socket(void) +{ + int sock; + + /* + * Use an AF_INET socket if IPv6 is disabled on the + * local system. + */ + sock = socket(AF_INET6, SOCK_DGRAM, 0); + if (sock == -1) { + if (errno != EAFNOSUPPORT) { + xlog(L_ERROR, "Failed to create RPC socket: %m"); + return -1; + } + sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock < 0) { + xlog(L_ERROR, "Failed to create RPC socket: %m"); + return -1; + } + } else + nsm_family = AF_INET6; + + if (fcntl(sock, F_SETFL, O_NONBLOCK) == -1) { + xlog(L_ERROR, "fcntl(3) on RPC socket failed: %m"); + goto out_close; + } + + /* + * TI-RPC over IPv6 (udp6/tcp6) does not handle IPv4. However, + * since sm-notify open-codes all of its RPC support, it can + * use a single socket and let the local network stack provide + * the correct mapping between address families automatically. + * This is the same thing that is done in the kernel. + */ + if (nsm_family == AF_INET6) { + const int zero = 0; + socklen_t zerolen = (socklen_t)sizeof(zero); + + if (setsockopt(sock, SOL_IPV6, IPV6_V6ONLY, + (char *)&zero, zerolen) == -1) { + xlog(L_ERROR, "setsockopt(3) on RPC socket failed: %m"); + goto out_close; + } + } + + return sock; + +out_close: + (void)close(sock); + return -1; +} +#else /* !IPV6_SUPPORTED */ +static int smn_socket(void) +{ + int sock; + + sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock == -1) { + xlog(L_ERROR, "Failed to create RPC socket: %m"); + return -1; + } + + if (fcntl(sock, F_SETFL, O_NONBLOCK) == -1) { + xlog(L_ERROR, "fcntl(3) on RPC socket failed: %m"); + (void)close(sock); + return -1; + } + + return sock; +} +#endif /* !IPV6_SUPPORTED */ + +/* + * If admin specified a source address or srcport, then convert those + * to a sockaddr and return it. Otherwise, return an ANYADDR address. + */ +__attribute__((__malloc__)) +static struct addrinfo * +smn_bind_address(const char *srcaddr, const char *srcport) +{ + struct addrinfo *ai = NULL; + struct addrinfo hint = { + .ai_flags = AI_NUMERICSERV | AI_V4MAPPED, + .ai_family = nsm_family, + .ai_protocol = (int)IPPROTO_UDP, + }; + int error; + + if (srcaddr == NULL) + hint.ai_flags |= AI_PASSIVE; + + /* Do not allow "node" and "service" parameters both to be NULL */ + if (srcport == NULL) + error = getaddrinfo(srcaddr, "", &hint, &ai); + else + error = getaddrinfo(srcaddr, srcport, &hint, &ai); + if (error != 0) { + xlog(L_ERROR, + "Invalid bind address or port for RPC socket: %s", + gai_strerror(error)); + return NULL; + } + + return ai; +} + +#ifdef HAVE_LIBTIRPC +static int +smn_bindresvport(int sock, struct sockaddr *sap) +{ + return bindresvport_sa(sock, sap); +} + +#else /* !HAVE_LIBTIRPC */ +static int +smn_bindresvport(int sock, struct sockaddr *sap) +{ + if (sap->sa_family != AF_INET) { + errno = EAFNOSUPPORT; + return -1; + } + + return bindresvport(sock, (struct sockaddr_in *)(char *)sap); +} +#endif /* !HAVE_LIBTIRPC */ + +/* + * Prepare a socket for sending RPC requests + * + * Returns a bound datagram socket file descriptor, or -1 if + * an error occurs. + */ +static int +smn_create_socket(const char *srcaddr, const char *srcport) +{ + int sock, retry_cnt = 0; + struct addrinfo *ai; + +retry: + sock = smn_socket(); + if (sock == -1) + return -1; + + ai = smn_bind_address(srcaddr, srcport); + if (ai == NULL) { + (void)close(sock); + return -1; + } + + /* Use source port if provided on the command line, + * otherwise use bindresvport */ + if (srcport) { + if (bind(sock, ai->ai_addr, ai->ai_addrlen) == -1) { + xlog(L_ERROR, "Failed to bind RPC socket: %m"); + nfs_freeaddrinfo(ai); + (void)close(sock); + return -1; + } + } else { + struct servent *se; + + if (smn_bindresvport(sock, ai->ai_addr) == -1) { + xlog(L_ERROR, + "bindresvport on RPC socket failed: %m"); + nfs_freeaddrinfo(ai); + (void)close(sock); + return -1; + } + + /* try to avoid known ports */ + se = getservbyport((int)nfs_get_port(ai->ai_addr), "udp"); + if (se != NULL && retry_cnt < 100) { + retry_cnt++; + nfs_freeaddrinfo(ai); + (void)close(sock); + goto retry; + } + } + + nfs_freeaddrinfo(ai); + return sock; +} + +/* Inform the kernel that it's OK to lift lockd's grace period */ +static void +nsm_lift_grace_period(void) +{ + int fd; + + fd = open(NLM_END_GRACE_FILE, O_WRONLY); + if (fd < 0) { + /* Don't warn if file isn't present */ + if (errno != ENOENT) + xlog(L_WARNING, "Unable to open %s: %m", + NLM_END_GRACE_FILE); + return; + } + + if (write(fd, "Y", 1) < 0) + xlog(L_WARNING, "Unable to write to %s: %m", NLM_END_GRACE_FILE); + + close(fd); + return; +} +inline static void +read_smnotify_conf(char **argv) +{ + char *s; + + conf_init_file(NFS_CONFFILE); + xlog_set_debug("sm-notify"); + opt_max_retry = conf_get_num("sm-notify", "retry-time", opt_max_retry / 60) * 60; + opt_srcport = conf_get_str("sm-notify", "outgoing-port"); + opt_srcaddr = conf_get_str("sm-notify", "outgoing-addr"); + lift_grace = conf_get_bool("sm-notify", "lift-grace", lift_grace); + + s = conf_get_str("statd", "state-directory-path"); + if (s && !nsm_setup_pathnames(argv[0], s)) + exit(1); + opt_update_state = conf_get_bool("sm-notify", "update-state", opt_update_state); + force = conf_get_bool("sm-notify", "force", force); +} + +int +main(int argc, char **argv) +{ + int c, sock; + char * progname; + + progname = strrchr(argv[0], '/'); + if (progname != NULL) + progname++; + else + progname = argv[0]; + + /* Read in config setting */ + read_smnotify_conf(argv); + + while ((c = getopt(argc, argv, "dm:np:v:P:f")) != -1) { + switch (c) { + case 'f': + force = 1; + break; + case 'd': + opt_debug++; + break; + case 'm': + opt_max_retry = atoi(optarg) * 60; + break; + case 'n': + opt_update_state = false; + break; + case 'p': + opt_srcport = optarg; + break; + case 'v': + opt_srcaddr = optarg; + break; + case 'P': + if (!nsm_setup_pathnames(argv[0], optarg)) + exit(1); + break; + + default: + goto usage; + } + } + + if (optind < argc) { +usage: fprintf(stderr, + "Usage: %s -notify [-dfq] [-m max-retry-minutes] [-p srcport]\n" + " [-P /path/to/state/directory] [-v my_host_name]\n", + progname); + exit(1); + } + + if (opt_debug) { + xlog_syslog(0); + xlog_stderr(1); + xlog_config(D_ALL, 1); + } else { + xlog_syslog(1); + xlog_stderr(0); + } + + xlog_open(progname); + xlog(L_NOTICE, "Version " VERSION " starting"); + + if (nsm_is_default_parentdir()) { + if (record_pid() == 0 && force == 0 && opt_update_state) { + /* already run, don't try again */ + xlog(L_NOTICE, "Already notifying clients; Exiting!"); + exit(0); + } + } + + if (opt_srcaddr != NULL) { + char *name; + + name = smn_verify_my_name(opt_srcaddr); + if (name == NULL) + exit(1); + + strncpy(nsm_hostname, name, sizeof(nsm_hostname)-1); + free(name); + } + + (void)nsm_retire_monitored_hosts(); + if (nsm_load_notify_list(smn_get_host) == 0) { + xlog(D_GENERAL, "No hosts to notify; exiting"); + if (lift_grace) + nsm_lift_grace_period(); + return 0; + } + + nsm_state = nsm_get_state(opt_update_state); + if (nsm_state == 0) + exit(1); + nsm_update_kernel_state(nsm_state); + + if (!opt_debug) { + xlog(L_NOTICE, "Backgrounding to notify hosts...\n"); + + if (daemon(0, 0) < 0) { + xlog(L_ERROR, "unable to background: %m"); + exit(1); + } + + close(0); + close(1); + close(2); + } + + sock = smn_create_socket(opt_srcaddr, opt_srcport); + if (sock == -1) + exit(1); + + if (!nsm_drop_privileges(-1)) + exit(1); + + notify(sock); + + if (hosts) { + struct nsm_host *hp; + + while ((hp = hosts) != 0) { + hosts = hp->next; + xlog(L_NOTICE, "Unable to notify %s, giving up", + hp->name); + } + exit(1); + } + + exit(0); +} + +/* + * Notify hosts + */ +static void +notify(const int sock) +{ + time_t failtime = 0; + + if (opt_max_retry) + failtime = time(NULL) + opt_max_retry; + + while (hosts) { + struct pollfd pfd; + time_t now = time(NULL); + unsigned int sent = 0; + struct nsm_host *hp; + long wait; + + if (failtime && now >= failtime) + break; + + while (hosts && ((wait = hosts->send_next - now) <= 0)) { + /* Never send more than 10 packets at once */ + if (sent++ >= 10) + break; + + /* Remove queue head */ + hp = hosts; + hosts = hp->next; + + if (notify_host(sock, hp)) + continue; + + /* Set the timeout for this call, using an + exponential timeout strategy */ + wait = hp->timeout; + if ((hp->timeout <<= 1) > NSM_MAX_TIMEOUT) + hp->timeout = NSM_MAX_TIMEOUT; + hp->send_next = now + wait; + hp->retries++; + + insert_host(hp); + } + if (hosts == NULL) + return; + + xlog(D_GENERAL, "Host %s due in %ld seconds", + hosts->name, wait); + + pfd.fd = sock; + pfd.events = POLLIN; + + wait *= 1000; + if (wait < 100) + wait = 100; + if (poll(&pfd, 1, wait) != 1) + continue; + + recv_reply(sock); + } +} + +/* + * Send notification to a single host + */ +static int +notify_host(int sock, struct nsm_host *host) +{ + struct sockaddr *sap; + socklen_t salen; + + if (host->ai == NULL) { + host->ai = smn_lookup(host->name); + if (host->ai == NULL) { + xlog_warn("DNS resolution of %s failed; " + "retrying later", host->name); + return 0; + } + } + + /* If we retransmitted 4 times, reset the port to force + * a new portmap lookup (in case statd was restarted). + * We also rotate through multiple IP addresses at this + * point. + */ + if (host->retries >= 4) { + /* don't rotate if there is only one addrinfo */ + if (host->ai->ai_next != NULL) { + struct addrinfo *first = host->ai; + struct addrinfo **next = &host->ai; + + /* remove the first entry from the list */ + host->ai = first->ai_next; + first->ai_next = NULL; + /* find the end of the list */ + next = &first->ai_next; + while ( *next ) + next = & (*next)->ai_next; + /* put first entry at end */ + *next = first; + } + + nfs_set_port(host->ai->ai_addr, 0); + host->retries = 0; + } + + sap = host->ai->ai_addr; + salen = host->ai->ai_addrlen; + + if (nfs_get_port(sap) == 0) + host->xid = nsm_xmit_rpcbind(sock, sap, SM_PROG, SM_VERS); + else + host->xid = nsm_xmit_notify(sock, sap, salen, + SM_PROG, host->notify_arg, nsm_state); + + return 0; +} + +static void +smn_defer(struct nsm_host *host) +{ + host->xid = 0; + host->send_next = time(NULL) + NSM_MAX_TIMEOUT; + host->timeout = NSM_MAX_TIMEOUT; + insert_host(host); +} + +static void +smn_schedule(struct nsm_host *host) +{ + host->retries = 0; + host->xid = 0; + host->send_next = time(NULL); + host->timeout = NSM_TIMEOUT; + insert_host(host); +} + +/* + * Extract the returned port number and set up the SM_NOTIFY call. + */ +static void +recv_rpcbind_reply(struct sockaddr *sap, struct nsm_host *host, XDR *xdr) +{ + uint16_t port = nsm_recv_rpcbind(sap->sa_family, xdr); + + if (port == 0) { + /* No binding for statd... */ + xlog(D_GENERAL, "No statd on host %s", host->name); + smn_defer(host); + } else { + xlog(D_GENERAL, "Processing rpcbind reply for %s (port %u)", + host->name, port); + nfs_set_port(sap, port); + smn_schedule(host); + } +} + +/* + * Successful NOTIFY call. Server returns void. + * + * Try sending another SM_NOTIFY with an unqualified "my_name" + * argument. Reuse the port number. If "my_name" is already + * unqualified, we're done. + */ +static void +recv_notify_reply(struct nsm_host *host) +{ + char *dot = strchr(host->notify_arg, '.'); + + if (dot != NULL) { + *dot = '\0'; + smn_schedule(host); + } else { + xlog(D_GENERAL, "Host %s notified successfully", host->name); + smn_forget_host(host); + } +} + +/* + * Receive reply from remote host + */ +static void +recv_reply(int sock) +{ + struct nsm_host *hp; + struct sockaddr *sap; + char msgbuf[NSM_MAXMSGSIZE]; + uint32_t xid; + ssize_t msglen; + XDR xdr; + + memset(msgbuf, 0 , sizeof(msgbuf)); + msglen = recv(sock, msgbuf, sizeof(msgbuf), 0); + if (msglen < 0) + return; + + xlog(D_GENERAL, "Received packet..."); + + memset(&xdr, 0, sizeof(xdr)); + xdrmem_create(&xdr, msgbuf, (unsigned int)msglen, XDR_DECODE); + xid = nsm_parse_reply(&xdr); + if (xid == 0) + goto out; + + /* Before we look at the data, find the host struct for + this reply */ + if ((hp = find_host(xid)) == NULL) + goto out; + + sap = hp->ai->ai_addr; + if (nfs_get_port(sap) == 0) + recv_rpcbind_reply(sap, hp, &xdr); + else + recv_notify_reply(hp); + +out: + xdr_destroy(&xdr); +} + +/* + * Insert host into notification list, sorted by next send time + */ +static void +insert_host(struct nsm_host *host) +{ + struct nsm_host **where, *p; + + where = &hosts; + while ((p = *where) != 0) { + /* Sort in ascending order of timeout */ + if (host->send_next < p->send_next) + break; + /* If we have the same timeout, put the + * most recently used host first. + * This makes sure that "recent" hosts + * get notified first. + */ + if (host->send_next == p->send_next + && host->last_used > p->last_used) + break; + where = &p->next; + } + + host->next = *where; + *where = host; + xlog(D_GENERAL, "Added host %s to notify list", host->name); +} + +/* + * Find host given the XID + */ +static struct nsm_host * +find_host(uint32_t xid) +{ + struct nsm_host **where, *p; + + where = &hosts; + while ((p = *where) != 0) { + if (p->xid == xid) { + *where = p->next; + return p; + } + where = &p->next; + } + return NULL; +} + +/* + * Record pid in /run/sm-notify.pid + * This file should remain until a reboot, even if the + * program exits. + * If file already exists, fail. + */ +static int record_pid(void) +{ + char pid[20]; + ssize_t len; + int fd; + + (void)snprintf(pid, sizeof(pid), "%d\n", (int)getpid()); + fd = open("/run/sm-notify.pid", O_CREAT|O_EXCL|O_WRONLY, 0600); + if (fd < 0) + return 0; + + len = write(fd, pid, strlen(pid)); + if ((len < 0) || ((size_t)len != strlen(pid))) { + xlog_warn("Writing to pid file failed: errno %d (%m)", + errno); + } + + (void)close(fd); + return 1; +} |