]> git.decadent.org.uk Git - nfs-utils.git/blob - utils/statd/sm-notify.c
e49c72259e1b9a64231224d90c8e9dc24fb34f1f
[nfs-utils.git] / utils / statd / sm-notify.c
1 /*
2  * Send NSM notify calls to all hosts listed in /var/lib/sm
3  *
4  * Copyright (C) 2004-2006 Olaf Kirch <okir@suse.de>
5  */
6
7 #ifdef HAVE_CONFIG_H
8 #include <config.h>
9 #endif
10
11 #include <err.h>
12 #include <sys/types.h>
13 #include <sys/socket.h>
14 #include <sys/stat.h>
15 #include <sys/poll.h>
16 #include <sys/param.h>
17 #include <sys/syslog.h>
18 #include <arpa/inet.h>
19 #include <dirent.h>
20 #include <time.h>
21 #include <stdio.h>
22 #include <getopt.h>
23 #include <stdlib.h>
24 #include <fcntl.h>
25 #include <unistd.h>
26 #include <string.h>
27 #include <stdarg.h>
28 #include <netdb.h>
29 #include <errno.h>
30 #include <grp.h>
31
32 #include "xlog.h"
33 #include "nsm.h"
34 #include "nfsrpc.h"
35
36 #define NSM_TIMEOUT     2
37 #define NSM_MAX_TIMEOUT 120     /* don't make this too big */
38
39 struct nsm_host {
40         struct nsm_host *       next;
41         char *                  name;
42         struct addrinfo         *ai;
43         time_t                  last_used;
44         time_t                  send_next;
45         unsigned int            timeout;
46         unsigned int            retries;
47         uint32_t                xid;
48 };
49
50 static char             nsm_hostname[256];
51 static int              nsm_state;
52 static int              opt_debug = 0;
53 static _Bool            opt_update_state = true;
54 static unsigned int     opt_max_retry = 15 * 60;
55 static char *           opt_srcaddr = 0;
56 static uint16_t         opt_srcport = 0;
57
58 static void             notify(void);
59 static int              notify_host(int, struct nsm_host *);
60 static void             recv_reply(int);
61 static void             insert_host(struct nsm_host *);
62 static struct nsm_host *find_host(uint32_t);
63 static int              record_pid(void);
64
65 static struct nsm_host *        hosts = NULL;
66
67 static struct addrinfo *smn_lookup(const char *name)
68 {
69         struct addrinfo *ai, hint = {
70 #if HAVE_DECL_AI_ADDRCONFIG
71                 .ai_flags       = AI_ADDRCONFIG,
72 #endif  /* HAVE_DECL_AI_ADDRCONFIG */
73                 .ai_family      = AF_INET,
74                 .ai_protocol    = IPPROTO_UDP,
75         };
76         int error;
77
78         error = getaddrinfo(name, NULL, &hint, &ai);
79         if (error) {
80                 xlog(D_GENERAL, "getaddrinfo(3): %s", gai_strerror(error));
81                 return NULL;
82         }
83
84         return ai;
85 }
86
87 __attribute_malloc__
88 static struct nsm_host *
89 smn_alloc_host(const char *hostname, const time_t timestamp)
90 {
91         struct nsm_host *host;
92
93         host = calloc(1, sizeof(*host));
94         if (host == NULL)
95                 goto out_nomem;
96
97         host->name = strdup(hostname);
98         if (host->name == NULL) {
99                 free(host);
100                 goto out_nomem;
101         }
102
103         host->last_used = timestamp;
104         host->timeout = NSM_TIMEOUT;
105         host->retries = 100;            /* force address retry */
106
107         return host;
108
109 out_nomem:
110         xlog_warn("Unable to allocate memory");
111         return NULL;
112 }
113
114 static void smn_forget_host(struct nsm_host *host)
115 {
116         xlog(D_CALL, "Removing %s from notify list", host->name);
117
118         nsm_delete_notified_host(host->name);
119
120         free(host->name);
121         if (host->ai)
122                 freeaddrinfo(host->ai);
123
124         free(host);
125 }
126
127 static unsigned int
128 smn_get_host(const char *hostname,
129                 __attribute__ ((unused)) const struct sockaddr *sap,
130                 __attribute__ ((unused)) const struct mon *m,
131                 const time_t timestamp)
132 {
133         struct nsm_host *host;
134
135         host = smn_alloc_host(hostname, timestamp);
136         if (host == NULL)
137                 return 0;
138
139         insert_host(host);
140         xlog(D_GENERAL, "Added host %s to notify list", hostname);
141         return 1;
142 }
143
144 int
145 main(int argc, char **argv)
146 {
147         int     c;
148         int     force = 0;
149         char *  progname;
150
151         progname = strrchr(argv[0], '/');
152         if (progname != NULL)
153                 progname++;
154         else
155                 progname = argv[0];
156
157         while ((c = getopt(argc, argv, "dm:np:v:P:f")) != -1) {
158                 switch (c) {
159                 case 'f':
160                         force = 1;
161                         break;
162                 case 'd':
163                         opt_debug++;
164                         break;
165                 case 'm':
166                         opt_max_retry = atoi(optarg) * 60;
167                         break;
168                 case 'n':
169                         opt_update_state = false;
170                         break;
171                 case 'p':
172                         opt_srcport = atoi(optarg);
173                         break;
174                 case 'v':
175                         opt_srcaddr = optarg;
176                         break;
177                 case 'P':
178                         if (!nsm_setup_pathnames(argv[0], optarg))
179                                 exit(1);
180                         break;
181
182                 default:
183                         goto usage;
184                 }
185         }
186
187         if (optind < argc) {
188 usage:          fprintf(stderr,
189                         "Usage: %s -notify [-dfq] [-m max-retry-minutes] [-p srcport]\n"
190                         "            [-P /path/to/state/directory] [-v my_host_name]\n",
191                         progname);
192                 exit(1);
193         }
194
195         xlog_syslog(1);
196         if (opt_debug) {
197                 xlog_stderr(1);
198                 xlog_config(D_ALL, 1);
199         } else
200                 xlog_stderr(0);
201
202         xlog_open(progname);
203         xlog(L_NOTICE, "Version " VERSION " starting");
204
205         if (nsm_is_default_parentdir()) {
206                 if (record_pid() == 0 && force == 0 && opt_update_state) {
207                         /* already run, don't try again */
208                         xlog(L_NOTICE, "Already notifying clients; Exiting!");
209                         exit(0);
210                 }
211         }
212
213         if (opt_srcaddr) {
214                 strncpy(nsm_hostname, opt_srcaddr, sizeof(nsm_hostname)-1);
215         } else
216         if (gethostname(nsm_hostname, sizeof(nsm_hostname)) < 0) {
217                 xlog(L_ERROR, "Failed to obtain name of local host: %m");
218                 exit(1);
219         }
220
221         (void)nsm_retire_monitored_hosts();
222         if (nsm_load_notify_list(smn_get_host) == 0) {
223                 xlog(D_GENERAL, "No hosts to notify; exiting");
224                 return 0;
225         }
226
227         nsm_state = nsm_get_state(opt_update_state);
228         if (nsm_state == 0)
229                 exit(1);
230         nsm_update_kernel_state(nsm_state);
231
232         if (!opt_debug) {
233                 xlog(L_NOTICE, "Backgrounding to notify hosts...\n");
234
235                 if (daemon(0, 0) < 0) {
236                         xlog(L_ERROR, "unable to background: %m");
237                         exit(1);
238                 }
239
240                 close(0);
241                 close(1);
242                 close(2);
243         }
244
245         notify();
246
247         if (hosts) {
248                 struct nsm_host *hp;
249
250                 while ((hp = hosts) != 0) {
251                         hosts = hp->next;
252                         xlog(L_NOTICE, "Unable to notify %s, giving up",
253                                 hp->name);
254                 }
255                 exit(1);
256         }
257
258         exit(0);
259 }
260
261 /*
262  * Notify hosts
263  */
264 static void
265 notify(void)
266 {
267         struct sockaddr_storage address;
268         struct sockaddr *local_addr = (struct sockaddr *)&address;
269         time_t  failtime = 0;
270         int     sock = -1;
271         int retry_cnt = 0;
272
273  retry:
274         sock = socket(AF_INET, SOCK_DGRAM, 0);
275         if (sock < 0) {
276                 xlog(L_ERROR, "Failed to create RPC socket: %m");
277                 exit(1);
278         }
279         fcntl(sock, F_SETFL, O_NONBLOCK);
280
281         memset(&address, 0, sizeof(address));
282         local_addr->sa_family = AF_INET;        /* Default to IPv4 */
283
284         /* Bind source IP if provided on command line */
285         if (opt_srcaddr) {
286                 struct addrinfo *ai = smn_lookup(opt_srcaddr);
287                 if (!ai) {
288                         xlog(L_ERROR,
289                                 "Not a valid hostname or address: \"%s\"",
290                                 opt_srcaddr);
291                         exit(1);
292                 }
293
294                 /* We know it's IPv4 at this point */
295                 memcpy(local_addr, ai->ai_addr, ai->ai_addrlen);
296
297                 freeaddrinfo(ai);
298         }
299
300         /* Use source port if provided on the command line,
301          * otherwise use bindresvport */
302         if (opt_srcport) {
303                 nfs_set_port(local_addr, opt_srcport);
304                 if (bind(sock, local_addr, sizeof(struct sockaddr_in)) < 0) {
305                         xlog(L_ERROR, "Failed to bind RPC socket: %m");
306                         exit(1);
307                 }
308         } else {
309                 struct servent *se;
310                 struct sockaddr_in *sin = (struct sockaddr_in *)local_addr;
311                 (void) bindresvport(sock, sin);
312                 /* try to avoid known ports */
313                 se = getservbyport(sin->sin_port, "udp");
314                 if (se && retry_cnt < 100) {
315                         retry_cnt++;
316                         close(sock);
317                         goto retry;
318                 }
319         }
320
321         if (opt_max_retry)
322                 failtime = time(NULL) + opt_max_retry;
323
324         if (!nsm_drop_privileges(-1))
325                 exit(1);
326
327         while (hosts) {
328                 struct pollfd   pfd;
329                 time_t          now = time(NULL);
330                 unsigned int    sent = 0;
331                 struct nsm_host *hp;
332                 long            wait;
333
334                 if (failtime && now >= failtime)
335                         break;
336
337                 while (hosts && ((wait = hosts->send_next - now) <= 0)) {
338                         /* Never send more than 10 packets at once */
339                         if (sent++ >= 10)
340                                 break;
341
342                         /* Remove queue head */
343                         hp = hosts;
344                         hosts = hp->next;
345
346                         if (notify_host(sock, hp))
347                                 continue;
348
349                         /* Set the timeout for this call, using an
350                            exponential timeout strategy */
351                         wait = hp->timeout;
352                         if ((hp->timeout <<= 1) > NSM_MAX_TIMEOUT)
353                                 hp->timeout = NSM_MAX_TIMEOUT;
354                         hp->send_next = now + wait;
355                         hp->retries++;
356
357                         insert_host(hp);
358                 }
359                 if (hosts == NULL)
360                         return;
361
362                 xlog(D_GENERAL, "Host %s due in %ld seconds",
363                                 hosts->name, wait);
364
365                 pfd.fd = sock;
366                 pfd.events = POLLIN;
367
368                 wait *= 1000;
369                 if (wait < 100)
370                         wait = 100;
371                 if (poll(&pfd, 1, wait) != 1)
372                         continue;
373
374                 recv_reply(sock);
375         }
376 }
377
378 /*
379  * Send notification to a single host
380  */
381 static int
382 notify_host(int sock, struct nsm_host *host)
383 {
384         struct sockaddr *sap;
385         socklen_t salen;
386
387         if (host->ai == NULL) {
388                 host->ai = smn_lookup(host->name);
389                 if (host->ai == NULL) {
390                         xlog_warn("DNS resolution of %s failed; "
391                                 "retrying later", host->name);
392                         return 0;
393                 }
394         }
395
396         /* If we retransmitted 4 times, reset the port to force
397          * a new portmap lookup (in case statd was restarted).
398          * We also rotate through multiple IP addresses at this
399          * point.
400          */
401         if (host->retries >= 4) {
402                 /* don't rotate if there is only one addrinfo */
403                 if (host->ai->ai_next != NULL) {
404                         struct addrinfo *first = host->ai;
405                         struct addrinfo **next = &host->ai;
406
407                         /* remove the first entry from the list */
408                         host->ai = first->ai_next;
409                         first->ai_next = NULL;
410                         /* find the end of the list */
411                         next = &first->ai_next;
412                         while ( *next )
413                                 next = & (*next)->ai_next;
414                         /* put first entry at end */
415                         *next = first;
416                 }
417
418                 nfs_set_port(host->ai->ai_addr, 0);
419                 host->retries = 0;
420         }
421
422         sap = host->ai->ai_addr;
423         salen = host->ai->ai_addrlen;
424
425         if (nfs_get_port(sap) == 0)
426                 host->xid = nsm_xmit_rpcbind(sock, sap, SM_PROG, SM_VERS);
427         else
428                 host->xid = nsm_xmit_notify(sock, sap, salen,
429                                 SM_PROG, nsm_hostname, nsm_state);
430         
431         return 0;
432 }
433
434 /*
435  * Extract the returned port number and set up the SM_NOTIFY call.
436  */
437 static void
438 recv_rpcbind_reply(struct sockaddr *sap, struct nsm_host *host, XDR *xdr)
439 {
440         uint16_t port = nsm_recv_rpcbind(sap->sa_family, xdr);
441
442         host->send_next = time(NULL);
443         host->xid = 0;
444
445         if (port == 0) {
446                 /* No binding for statd... */
447                 xlog(D_GENERAL, "No statd on host %s", host->name);
448                 host->timeout = NSM_MAX_TIMEOUT;
449                 host->send_next += NSM_MAX_TIMEOUT;
450         } else {
451                 nfs_set_port(sap, port);
452                 if (host->timeout >= NSM_MAX_TIMEOUT / 4)
453                         host->timeout = NSM_MAX_TIMEOUT / 4;
454         }
455
456         insert_host(host);
457 }
458
459 /*
460  * Successful NOTIFY call. Server returns void, so nothing
461  * we need to do here.
462  */
463 static void
464 recv_notify_reply(struct nsm_host *host)
465 {
466         xlog(D_GENERAL, "Host %s notified successfully", host->name);
467
468         smn_forget_host(host);
469 }
470
471 /*
472  * Receive reply from remote host
473  */
474 static void
475 recv_reply(int sock)
476 {
477         struct nsm_host *hp;
478         struct sockaddr *sap;
479         char msgbuf[NSM_MAXMSGSIZE];
480         uint32_t        xid;
481         ssize_t         msglen;
482         XDR             xdr;
483
484         memset(msgbuf, 0 , sizeof(msgbuf));
485         msglen = recv(sock, msgbuf, sizeof(msgbuf), 0);
486         if (msglen < 0)
487                 return;
488
489         xlog(D_GENERAL, "Received packet...");
490
491         memset(&xdr, 0, sizeof(xdr));
492         xdrmem_create(&xdr, msgbuf, (unsigned int)msglen, XDR_DECODE);
493         xid = nsm_parse_reply(&xdr);
494         if (xid == 0)
495                 goto out;
496
497         /* Before we look at the data, find the host struct for
498            this reply */
499         if ((hp = find_host(xid)) == NULL)
500                 goto out;
501
502         sap = hp->ai->ai_addr;
503         if (nfs_get_port(sap) == 0)
504                 recv_rpcbind_reply(sap, hp, &xdr);
505         else
506                 recv_notify_reply(hp);
507
508 out:
509         xdr_destroy(&xdr);
510 }
511
512 /*
513  * Insert host into sorted list
514  */
515 static void
516 insert_host(struct nsm_host *host)
517 {
518         struct nsm_host **where, *p;
519
520         where = &hosts;
521         while ((p = *where) != 0) {
522                 /* Sort in ascending order of timeout */
523                 if (host->send_next < p->send_next)
524                         break;
525                 /* If we have the same timeout, put the
526                  * most recently used host first.
527                  * This makes sure that "recent" hosts
528                  * get notified first.
529                  */
530                 if (host->send_next == p->send_next
531                  && host->last_used > p->last_used)
532                         break;
533                 where = &p->next;
534         }
535
536         host->next = *where;
537         *where = host;
538 }
539
540 /*
541  * Find host given the XID
542  */
543 static struct nsm_host *
544 find_host(uint32_t xid)
545 {
546         struct nsm_host **where, *p;
547
548         where = &hosts;
549         while ((p = *where) != 0) {
550                 if (p->xid == xid) {
551                         *where = p->next;
552                         return p;
553                 }
554                 where = &p->next;
555         }
556         return NULL;
557 }
558
559 /*
560  * Record pid in /var/run/sm-notify.pid
561  * This file should remain until a reboot, even if the
562  * program exits.
563  * If file already exists, fail.
564  */
565 static int record_pid(void)
566 {
567         char pid[20];
568         ssize_t len;
569         int fd;
570
571         (void)snprintf(pid, sizeof(pid), "%d\n", (int)getpid());
572         fd = open("/var/run/sm-notify.pid", O_CREAT|O_EXCL|O_WRONLY, 0600);
573         if (fd < 0)
574                 return 0;
575
576         len = write(fd, pid, strlen(pid));
577         if ((len < 0) || ((size_t)len != strlen(pid))) {
578                 xlog_warn("Writing to pid file failed: errno %d (%m)",
579                                 errno);
580         }
581
582         (void)close(fd);
583         return 1;
584 }