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