xref: /illumos-gate/usr/src/cmd/fs.d/nfs/mountd/rmtab.c (revision 7c478bd95313f5f23a4c958a745db2134aa03244)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * rmtab.c
24  *
25  * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
26  * Use is subject to license terms.
27  */
28 
29 #pragma ident	"%Z%%M%	%I%	%E% SMI"
30 
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <ctype.h>
34 #include <sys/types.h>
35 #include <string.h>
36 #include <sys/param.h>
37 #include <sys/stat.h>
38 #include <sys/file.h>
39 #include <sys/time.h>
40 #include <errno.h>
41 #include <rpcsvc/mount.h>
42 #include <sys/pathconf.h>
43 #include <sys/systeminfo.h>
44 #include <sys/utsname.h>
45 #include <signal.h>
46 #include <locale.h>
47 #include <unistd.h>
48 #include <thread.h>
49 #include <syslog.h>
50 #include <sys/socket.h>
51 #include <netinet/in.h>
52 #include <arpa/inet.h>
53 #include "../lib/sharetab.h"
54 #include "hashset.h"
55 #include "mountd.h"
56 
57 static char RMTAB[] = "/etc/rmtab";
58 static FILE *rmtabf = NULL;
59 
60 /*
61  * There is nothing magic about the value selected here. Too low,
62  * and mountd might spend too much time rewriting the rmtab file.
63  * Too high, it won't do it frequently enough.
64  */
65 static int rmtab_del_thresh = 250;
66 
67 #define	RMTAB_TOOMANY_DELETED()	\
68 	((rmtab_deleted > rmtab_del_thresh) && (rmtab_deleted > rmtab_inuse))
69 
70 /*
71  * mountd's version of a "struct mountlist". It is the same except
72  * for the added ml_pos field.
73  */
74 struct mntentry {
75 	char  *m_host;
76 	char  *m_path;
77 	long   m_pos;
78 };
79 
80 static HASHSET mntlist;
81 
82 static int mntentry_equal(const void *, const void *);
83 static uint32_t mntentry_hash(const void *);
84 static int mntlist_contains(char *, char *);
85 static void rmtab_delete(long);
86 static long rmtab_insert(char *, char *);
87 static void rmtab_rewrite(void);
88 static void rmtab_parse(char *buf);
89 static bool_t xdr_mntlistencode(XDR * xdrs, HASHSET * mntlist);
90 
91 #define	exstrdup(s) \
92 	strcpy(exmalloc(strlen(s)+1), s)
93 
94 
95 static int rmtab_inuse;
96 static int rmtab_deleted;
97 
98 static rwlock_t rmtab_lock;	/* lock to protect rmtab list */
99 
100 
101 /*
102  * Check whether the given client/path combination
103  * already appears in the mount list.
104  */
105 
106 static int
107 mntlist_contains(char *host, char *path)
108 {
109 	struct mntentry m;
110 
111 	m.m_host = host;
112 	m.m_path = path;
113 
114 	return (h_get(mntlist, &m) != NULL);
115 }
116 
117 
118 /*
119  *  Add an entry to the mount list.
120  *  First check whether it's there already - the client
121  *  may have crashed and be rebooting.
122  */
123 
124 static void
125 mntlist_insert(char *host, char *path)
126 {
127 	if (!mntlist_contains(host, path)) {
128 		struct mntentry *m;
129 
130 		m = exmalloc(sizeof (struct mntentry));
131 
132 		m->m_host = exstrdup(host);
133 		m->m_path = exstrdup(path);
134 		m->m_pos = rmtab_insert(host, path);
135 		(void) h_put(mntlist, m);
136 	}
137 }
138 
139 void
140 mntlist_new(char *host, char *path)
141 {
142 	(void) rw_wrlock(&rmtab_lock);
143 	mntlist_insert(host, path);
144 	(void) rw_unlock(&rmtab_lock);
145 }
146 
147 /*
148  * Delete an entry from the mount list.
149  */
150 
151 void
152 mntlist_delete(char *host, char *path)
153 {
154 	struct mntentry *m, mm;
155 
156 	mm.m_host = host;
157 	mm.m_path = path;
158 
159 	(void) rw_wrlock(&rmtab_lock);
160 
161 	if (m = (struct mntentry *)h_get(mntlist, &mm)) {
162 		rmtab_delete(m->m_pos);
163 
164 		(void) h_delete(mntlist, m);
165 
166 		free(m->m_path);
167 		free(m->m_host);
168 		free(m);
169 
170 		if (RMTAB_TOOMANY_DELETED())
171 			rmtab_rewrite();
172 	}
173 	(void) rw_unlock(&rmtab_lock);
174 }
175 
176 /*
177  * Delete all entries for a host from the mount list
178  */
179 
180 void
181 mntlist_delete_all(char *host)
182 {
183 	HASHSET_ITERATOR iterator;
184 	struct mntentry *m;
185 
186 	(void) rw_wrlock(&rmtab_lock);
187 
188 	iterator = h_iterator(mntlist);
189 
190 	while (m = (struct mntentry *)h_next(iterator)) {
191 		if (strcasecmp(m->m_host, host))
192 			continue;
193 
194 		rmtab_delete(m->m_pos);
195 
196 		(void) h_delete(mntlist, m);
197 
198 		free(m->m_path);
199 		free(m->m_host);
200 		free(m);
201 	}
202 
203 	if (RMTAB_TOOMANY_DELETED())
204 		rmtab_rewrite();
205 
206 	(void) rw_unlock(&rmtab_lock);
207 
208 	if (iterator != NULL)
209 		free(iterator);
210 }
211 
212 /*
213  * Equivalent to xdr_mountlist from librpcsvc but for HASHSET
214  * rather that for a linked list. It is used only to encode data
215  * from HASHSET before sending it over the wire.
216  */
217 
218 static bool_t
219 xdr_mntlistencode(XDR *xdrs, HASHSET *mntlist)
220 {
221 	HASHSET_ITERATOR iterator = h_iterator(*mntlist);
222 
223 	for (;;) {
224 		struct mntentry *m = (struct mntentry *)h_next(iterator);
225 		bool_t more_data = (m != NULL);
226 
227 		if (!xdr_bool(xdrs, &more_data)) {
228 			if (iterator != NULL)
229 				free(iterator);
230 			return (FALSE);
231 		}
232 
233 		if (!more_data)
234 			break;
235 
236 		if ((!xdr_name(xdrs, &m->m_host)) ||
237 			(!xdr_dirpath(xdrs, &m->m_path))) {
238 			if (iterator != NULL)
239 				free(iterator);
240 			return (FALSE);
241 		}
242 
243 	}
244 
245 	if (iterator != NULL)
246 		free(iterator);
247 
248 	return (TRUE);
249 }
250 
251 void
252 mntlist_send(SVCXPRT *transp)
253 {
254 	(void) rw_rdlock(&rmtab_lock);
255 
256 	errno = 0;
257 	if (!svc_sendreply(transp, xdr_mntlistencode, (char *)&mntlist))
258 		log_cant_reply(transp);
259 
260 	(void) rw_unlock(&rmtab_lock);
261 }
262 
263 /*
264  * Compute a hash for an mntlist entry.
265  */
266 #define	SPACE		(uchar_t)0x20
267 #define	HASH_BITS	3	/* Num. of bits to shift the hash  */
268 #define	HASH_MAXHOST	6	/* Max. number of characters from a hostname */
269 #define	HASH_MAXPATH	3	/* Max. number of characters from a pathname */
270 
271 /*
272  * Compute a 32 bit hash value for an mntlist entry.
273  * We consider only first HASH_MAXHOST characters from the host part.
274  * We skip the first character in the path part (usually /), and we
275  * consider at most HASH_MAXPATH following characters.
276  * We shift the hash value by HASH_BITS after each character.
277  */
278 
279 static uint32_t
280 mntentry_hash(const void *p)
281 {
282 	struct mntentry *m = (struct mntentry *)p;
283 	uchar_t *s;
284 	uint_t i, sum = 0;
285 
286 	for (i = 0, s = (uchar_t *)m->m_host; *s && i < HASH_MAXHOST; i++) {
287 		uchar_t ls = tolower(*s);
288 		sum <<= HASH_BITS;
289 		sum += ls - SPACE;
290 		*s++;
291 	}
292 
293 	/*
294 	 * The first character is usually '/'.
295 	 * Start with the next character.
296 	 */
297 	for (i = 0, s = (uchar_t *)m->m_path+1; *s && i < HASH_MAXPATH; i++) {
298 		sum <<= HASH_BITS;
299 		sum += *s++ - SPACE;
300 	}
301 
302 	return (sum);
303 }
304 
305 /*
306  * Compare mntlist entries.
307  * The comparison ignores a value of m_pos.
308  */
309 
310 static int
311 mntentry_equal(const void *p1, const void *p2)
312 {
313 	struct mntentry *m1 = (struct mntentry *)p1;
314 	struct mntentry *m2 = (struct mntentry *)p2;
315 
316 	return ((strcasecmp(m1->m_host, m2->m_host) ||
317 		strcmp(m1->m_path, m2->m_path)) ? 0 : 1);
318 }
319 
320 /*
321  * Rewrite /etc/rmtab with a current content of mntlist.
322  */
323 static void
324 rmtab_rewrite()
325 {
326 	if (rmtabf)
327 		(void) fclose(rmtabf);
328 
329 	/* Rewrite the file. */
330 	if (rmtabf = fopen(RMTAB, "w+")) {
331 		HASHSET_ITERATOR iterator;
332 		struct mntentry *m;
333 
334 		(void) fchmod(fileno(rmtabf),
335 		    (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH));
336 		rmtab_inuse = rmtab_deleted = 0;
337 
338 		iterator = h_iterator(mntlist);
339 
340 		while (m = (struct mntentry *)h_next(iterator))
341 			m->m_pos = rmtab_insert(m->m_host, m->m_path);
342 		if (iterator != NULL)
343 			free(iterator);
344 	}
345 }
346 
347 /*
348  * Parse the content of /etc/rmtab and insert the entries into mntlist.
349  * The buffer s should be ended with a NUL char.
350  */
351 
352 static void
353 rmtab_parse(char *s)
354 {
355 	char  *host;
356 	char  *path;
357 	char  *tmp;
358 	struct in6_addr ipv6addr;
359 
360 host_part:
361 	if (*s == '#')
362 		goto skip_rest;
363 
364 	host = s;
365 	for (;;) {
366 		switch (*s++) {
367 		case '\0':
368 			return;
369 		case '\n':
370 			goto host_part;
371 		case ':':
372 			s[-1] = '\0';
373 			goto path_part;
374 		case '[':
375 			tmp = strchr(s, ']');
376 			if (tmp) {
377 				*tmp = '\0';
378 				if (inet_pton(AF_INET6, s, &ipv6addr) > 0) {
379 					host = s;
380 					s = ++tmp;
381 				} else
382 					*tmp = ']';
383 			}
384 		default:
385 			continue;
386 		}
387 	}
388 
389 path_part:
390 	path = s;
391 	for (;;) {
392 		switch (*s++) {
393 		case '\n':
394 			s[-1] = '\0';
395 			if (*host && *path)
396 				mntlist_insert(host, path);
397 			goto host_part;
398 		case '\0':
399 			if (*host && *path)
400 				mntlist_insert(host, path);
401 			return;
402 		default:
403 			continue;
404 		}
405 	}
406 
407 skip_rest:
408 	for (;;) {
409 		switch (*++s) {
410 		case '\n':
411 			goto host_part;
412 		case '\0':
413 			return;
414 		default:
415 			continue;
416 		}
417 	}
418 }
419 
420 /*
421  * Read in contents of rmtab.
422  * Call rmtab_parse to parse the file and store entries in mntlist.
423  * Rewrites the file to get rid of unused entries.
424  */
425 
426 #define	RMTAB_LOADLEN	(16*2024)	/* Max bytes to read at a time */
427 
428 void
429 rmtab_load()
430 {
431 	FILE *fp;
432 
433 	(void) rwlock_init(&rmtab_lock, USYNC_THREAD, NULL);
434 
435 	/*
436 	 * Don't need to lock the list at this point
437 	 * because there's only a single thread running.
438 	 */
439 	mntlist = h_create(mntentry_hash, mntentry_equal, 101, 0.75);
440 
441 	if (fp = fopen(RMTAB, "r")) {
442 		char buf[RMTAB_LOADLEN+1];
443 		size_t len;
444 
445 		/*
446 		 * Read at most RMTAB_LOADLEN bytes from /etc/rmtab.
447 		 * - if fread returns RMTAB_LOADLEN we can be in the middle
448 		 *   of a line so change the last newline character into NUL
449 		 *   and seek back to the next character after newline.
450 		 * - otherwise set NUL behind the last character read.
451 		 */
452 		while ((len = fread(buf, 1, RMTAB_LOADLEN, fp)) > 0) {
453 			if (len == RMTAB_LOADLEN) {
454 				int i;
455 
456 				for (i = 1; i < len; i++) {
457 					if (buf[len-i] == '\n') {
458 						buf[len-i] = '\0';
459 						(void) fseek(fp, -i+1,
460 							    SEEK_CUR);
461 						goto parse;
462 					}
463 				}
464 			}
465 
466 			/* Put a NUL character at the end of buffer */
467 			buf[len] = '\0';
468 	parse:
469 			rmtab_parse(buf);
470 		}
471 		(void) fclose(fp);
472 	}
473 	rmtab_rewrite();
474 }
475 
476 /*
477  * Write an entry at the current location in rmtab
478  * for the given client and path.
479  *
480  * Returns the starting position of the entry
481  * or -1 if there was an error.
482  */
483 
484 long
485 rmtab_insert(char *host, char *path)
486 {
487 	long   pos;
488 	struct in6_addr ipv6addr;
489 
490 	if (rmtabf == NULL || fseek(rmtabf, 0L, 2) == -1) {
491 		return (-1);
492 	}
493 	pos = ftell(rmtabf);
494 
495 	/*
496 	 * Check if host is an IPv6 literal
497 	 */
498 
499 	if (inet_pton(AF_INET6, host, &ipv6addr) > 0) {
500 		if (fprintf(rmtabf, "[%s]:%s\n", host, path) == EOF) {
501 			return (-1);
502 		}
503 	} else {
504 		if (fprintf(rmtabf, "%s:%s\n", host, path) == EOF) {
505 			return (-1);
506 		}
507 	}
508 	if (fflush(rmtabf) == EOF) {
509 		return (-1);
510 	}
511 	rmtab_inuse++;
512 	return (pos);
513 }
514 
515 /*
516  * Mark as unused the rmtab entry at the given offset in the file.
517  */
518 
519 void
520 rmtab_delete(long pos)
521 {
522 	if (rmtabf != NULL && pos != -1 && fseek(rmtabf, pos, 0) == 0) {
523 		(void) fprintf(rmtabf, "#");
524 		(void) fflush(rmtabf);
525 
526 		rmtab_inuse--;
527 		rmtab_deleted++;
528 	}
529 }
530