summaryrefslogtreecommitdiffstats
path: root/usr.sbin/rpc.statd/file.c
blob: aeb1fc09651d235619599b197466e58946315dc8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
/*
 * Copyright (c) 1995
 *	A.R. Gordon (andrew.gordon@net-tel.co.uk).  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed for the FreeBSD project
 * 4. Neither the name of the author nor the names of any co-contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY ANDREW GORDON AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 */

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/mman.h>		/* For mmap()				*/
#include <rpc/rpc.h>
#include <syslog.h>

#include "statd.h"

FileLayout *status_info;	/* Pointer to the mmap()ed status file	*/
static int status_fd;		/* File descriptor for the open file	*/
static off_t status_file_len;	/* Current on-disc length of file	*/

/* sync_file --------------------------------------------------------------- */
/*
   Purpose:	Packaged call of msync() to flush changes to mmap()ed file
   Returns:	Nothing.  Errors to syslog.
*/

void sync_file(void)
{
  if (msync((void *)status_info, 0, 0) < 0)
  {
    syslog(LOG_ERR, "msync() failed: %s", strerror(errno));
  }
}

/* find_host -------------------------------------------------------------- */
/*
   Purpose:	Find the entry in the status file for a given host
   Returns:	Pointer to that entry in the mmap() region, or NULL.
   Notes:	Also creates entries if requested.
		Failure to create also returns NULL.
*/

HostInfo *find_host(char *hostname, int create)
{
  HostInfo *hp;
  HostInfo *spare_slot = NULL;
  HostInfo *result = NULL;
  int i;

  for (i = 0, hp = status_info->hosts; i < status_info->noOfHosts; i++, hp++)
  {
    if (!strncasecmp(hostname, hp->hostname, SM_MAXSTRLEN))
    {
      result = hp;
      break;
    }
    if (!spare_slot && !hp->monList && !hp->notifyReqd)
      spare_slot = hp;
  }

  /* Return if entry found, or if not asked to create one.		*/
  if (result || !create) return (result);

  /* Now create an entry, using the spare slot if one was found or	*/
  /* adding to the end of the list otherwise, extending file if reqd	*/
  if (!spare_slot)
  {
    off_t desired_size;
    spare_slot = &status_info->hosts[status_info->noOfHosts];
    desired_size = ((char*)spare_slot - (char*)status_info) + sizeof(HostInfo);
    if (desired_size > status_file_len)
    {
      /* Extend file by writing 1 byte of junk at the desired end pos	*/
      lseek(status_fd, desired_size - 1, SEEK_SET);
      i = write(status_fd, &i, 1);
      if (i < 1)
      {
	syslog(LOG_ERR, "Unable to extend status file");
	return (NULL);
      }
      status_file_len = desired_size;
    }
    status_info->noOfHosts++;
  }

  /* Initialise the spare slot that has been found/created		*/
  /* Note that we do not msync(), since the caller is presumed to be	*/
  /* about to modify the entry further					*/
  memset(spare_slot, 0, sizeof(HostInfo));
  strncpy(spare_slot->hostname, hostname, SM_MAXSTRLEN);
  return (spare_slot);
}

/* init_file -------------------------------------------------------------- */
/*
   Purpose:	Open file, create if necessary, initialise it.
   Returns:	Nothing - exits on error
   Notes:	Called before process becomes daemon, hence logs to
		stderr rather than syslog.
		Opens the file, then mmap()s it for ease of access.
		Also performs initial clean-up of the file, zeroing
		monitor list pointers, setting the notifyReqd flag in
		all hosts that had a monitor list, and incrementing
		the state number to the next even value.
*/

void init_file(char *filename)
{
  int new_file = FALSE;
  char buf[HEADER_LEN];
  int i;

  /* try to open existing file - if not present, create one		*/
  status_fd = open(filename, O_RDWR);
  if ((status_fd < 0) && (errno == ENOENT))
  {
    status_fd = open(filename, O_RDWR | O_CREAT, 0644);
    new_file = TRUE;
  }
  if (status_fd < 0)
  {
    perror("rpc.statd");
    fprintf(stderr, "Unable to open status file %s\n", filename);
    exit(1);
  }

  /* File now open.  mmap() it, with a generous size to allow for	*/
  /* later growth, where we will extend the file but not re-map it.	*/
  status_info = (FileLayout *)
    mmap(NULL, 0x10000000, PROT_READ | PROT_WRITE, MAP_SHARED, status_fd, 0);

  if (status_info == (FileLayout *) -1)
  {
    perror("rpc.statd");
    fprintf(stderr, "Unable to mmap() status file\n");
  }

  status_file_len = lseek(status_fd, 0L, SEEK_END);

  /* If the file was not newly created, validate the contents, and if	*/
  /* defective, re-create from scratch.					*/
  if (!new_file)
  {
    if ((status_file_len < HEADER_LEN) || (status_file_len
      < (HEADER_LEN + sizeof(HostInfo) * status_info->noOfHosts)) )
    {
      fprintf(stderr, "rpc.statd: status file is corrupt\n");
      new_file = TRUE;
    }
  }

  /* Initialisation of a new, empty file.				*/
  if (new_file)
  {
    memset(buf, 0, sizeof(buf));
    lseek(status_fd, 0L, SEEK_SET);
    write(status_fd, buf, HEADER_LEN);
    status_file_len = HEADER_LEN;
  }
  else
  {
    /* Clean-up of existing file - monitored hosts will have a pointer	*/
    /* to a list of clients, which refers to memory in the previous	*/
    /* incarnation of the program and so are meaningless now.  These	*/
    /* pointers are zeroed and the fact that the host was previously	*/
    /* monitored is recorded by setting the notifyReqd flag, which will	*/
    /* in due course cause a SM_NOTIFY to be sent.			*/
    /* Note that if we crash twice in quick succession, some hosts may	*/
    /* already have notifyReqd set, where we didn't manage to notify	*/
    /* them before the second crash occurred.				*/
    for (i = 0; i < status_info->noOfHosts; i++)
    {
      HostInfo *this_host = &status_info->hosts[i];

      if (this_host->monList)
      {
	this_host->notifyReqd = TRUE;
	this_host->monList = NULL;
      }
    }
    /* Select the next higher even number for the state counter		*/
    status_info->ourState = (status_info->ourState + 2) & 0xfffffffe;
/*???????******/ status_info->ourState++;
  }
}

/* xdr_stat_chge ----------------------------------------------------------- */
/*
   Purpose:	XDR-encode structure of type stat_chge
   Returns:	TRUE if successful
   Notes:	This function is missing from librpcsvc, because the
		sm_inter.x distributed by Sun omits the SM_NOTIFY
		procedure used between co-operating statd's
*/

bool_t xdr_stat_chge(XDR *xdrs, stat_chge *objp)
{
  if (!xdr_string(xdrs, &objp->mon_name, SM_MAXSTRLEN))
  {
    return (FALSE);
  }
  if (!xdr_int(xdrs, &objp->state))
  {
    return (FALSE);
  }
  return (TRUE);
}


/* notify_one_host --------------------------------------------------------- */
/*
   Purpose:	Perform SM_NOTIFY procedure at specified host
   Returns:	TRUE if success, FALSE if failed.
*/

static int notify_one_host(char *hostname)
{
  struct timeval timeout = { 20, 0 };	/* 20 secs timeout		*/
  CLIENT *cli;
  char dummy; 
  stat_chge arg;
  char our_hostname[SM_MAXSTRLEN+1];

  gethostname(our_hostname, sizeof(our_hostname));
  our_hostname[SM_MAXSTRLEN] = '\0';
  arg.mon_name = our_hostname;
  arg.state = status_info->ourState;

  if (debug) syslog (LOG_DEBUG, "Sending SM_NOTIFY to host %s from %s", hostname, our_hostname);

  cli = clnt_create(hostname, SM_PROG, SM_VERS, "udp");
  if (!cli)
  {
    syslog(LOG_ERR, "Failed to contact host %s%s", hostname,
      clnt_spcreateerror(""));
    return (FALSE);
  }

  if (clnt_call(cli, SM_NOTIFY, xdr_stat_chge, &arg, xdr_void, &dummy, timeout)
    != RPC_SUCCESS)
  {
    syslog(LOG_ERR, "Failed to contact rpc.statd at host %s", hostname);
    clnt_destroy(cli);
    return (FALSE);
  }

  clnt_destroy(cli);
  return (TRUE);
}

/* notify_hosts ------------------------------------------------------------ */
/*
   Purpose:	Send SM_NOTIFY to all hosts marked as requiring it
   Returns:	Nothing, immediately - forks a process to do the work.
   Notes:	Does nothing if there are no monitored hosts.
		Called after all the initialisation has been done - 
		logs to syslog.
*/

void notify_hosts(void)
{
  int i;
  int attempts;
  int work_to_do = FALSE;
  HostInfo *hp;
  pid_t pid;

  /* First check if there is in fact any work to do.			*/
  for (i = status_info->noOfHosts, hp = status_info->hosts; i ; i--, hp++)
  {
    if (hp->notifyReqd)
    {
      work_to_do = TRUE;
      break;
    }
  }

  if (!work_to_do) return;	/* No work found			*/

  pid = fork();
  if (pid == -1)
  {
    syslog(LOG_ERR, "Unable to fork notify process - %s", strerror(errno));
    return;
  }
  if (pid) return;

  /* Here in the child process.  We continue until all the hosts marked	*/
  /* as requiring notification have been duly notified.			*/
  /* If one of the initial attempts fails, we sleep for a while and	*/
  /* have another go.  This is necessary because when we have crashed,	*/
  /* (eg. a power outage) it is quite possible that we won't be able to	*/
  /* contact all monitored hosts immediately on restart, either because	*/
  /* they crashed too and take longer to come up (in which case the	*/
  /* notification isn't really required), or more importantly if some	*/
  /* router etc. needed to reach the monitored host has not come back	*/
  /* up yet.  In this case, we will be a bit late in re-establishing	*/
  /* locks (after the grace period) but that is the best we can do.	*/
  /* We try 10 times at 5 sec intervals, 10 more times at 1 minute	*/
  /* intervals, then 24 more times at hourly intervals, finally		*/
  /* giving up altogether if the host hasn't come back to life after	*/
  /* 24 hours.								*/

  for (attempts = 0; attempts < 44; attempts++)
  {
    work_to_do = FALSE;		/* Unless anything fails		*/
    for (i = status_info->noOfHosts, hp = status_info->hosts; i ; i--, hp++)
    {
      if (hp->notifyReqd)
      {
        if (notify_one_host(hp->hostname))
	{
	  hp->notifyReqd = FALSE;
          sync_file();
	}
	else work_to_do = TRUE;
      }
    }
    if (!work_to_do) break;
    if (attempts < 10) sleep(5);
    else if (attempts < 20) sleep(60);
    else sleep(60*60);
  }
  exit(0);
}


OpenPOWER on IntegriCloud