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