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