xref: /illumos-gate/usr/src/cmd/fs.d/autofs/autod_readdir.c (revision 7a6d80f1660abd4755c68cbd094d4a914681d26e)
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  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 /*
27  *	autod_readdir.c
28  */
29 
30 #include <stdio.h>
31 #include <ctype.h>
32 #include <string.h>
33 #include <syslog.h>
34 #include <sys/types.h>
35 #include <sys/param.h>
36 #include <errno.h>
37 #include <pwd.h>
38 #include <locale.h>
39 #include <stdlib.h>
40 #include <unistd.h>
41 #include <assert.h>
42 #include <fcntl.h>
43 #include "automount.h"
44 
45 static void build_dir_entry_list(struct autofs_rddir_cache *rdcp,
46 				struct dir_entry *list);
47 static int autofs_rddir_cache_enter(char *map, ulong_t bucket_size,
48 				struct autofs_rddir_cache **rdcpp);
49 int autofs_rddir_cache_lookup(char *map, struct autofs_rddir_cache **rdcpp);
50 static int autofs_rddir_cache_delete(struct autofs_rddir_cache *rdcp);
51 static int create_dirents(struct autofs_rddir_cache *rdcp, ulong_t offset,
52 				autofs_rddirres *res);
53 struct dir_entry *rddir_entry_lookup(char *name, struct dir_entry *list);
54 static void free_offset_tbl(struct off_tbl *head);
55 static void free_dir_list(struct dir_entry *head);
56 
57 #define	OFFSET_BUCKET_SIZE	100
58 
59 rwlock_t autofs_rddir_cache_lock;		/* readdir cache lock */
60 struct autofs_rddir_cache *rddir_head;		/* readdir cache head */
61 
62 int
63 do_readdir(autofs_rddirargs *rda, autofs_rddirres *rd)
64 {
65 	struct dir_entry *list = NULL, *l;
66 	struct autofs_rddir_cache *rdcp = NULL;
67 	int error;
68 	int cache_time = RDDIR_CACHE_TIME;
69 
70 	if (automountd_nobrowse) {
71 		/*
72 		 * Browsability was disabled return an empty list.
73 		 */
74 		rd->rd_status = AUTOFS_OK;
75 		rd->rd_rddir.rddir_size = 0;
76 		rd->rd_rddir.rddir_eof = 1;
77 		rd->rd_rddir.rddir_entries = NULL;
78 
79 		return (0);
80 	}
81 
82 	rw_rdlock(&autofs_rddir_cache_lock);
83 	error = autofs_rddir_cache_lookup(rda->rda_map, &rdcp);
84 	if (error) {
85 		rw_unlock(&autofs_rddir_cache_lock);
86 		rw_wrlock(&autofs_rddir_cache_lock);
87 		error = autofs_rddir_cache_lookup(rda->rda_map, &rdcp);
88 		if (error) {
89 			if (trace > 2)
90 				trace_prt(1,
91 				"map %s not found, adding...\n", rda->rda_map);
92 			/*
93 			 * entry doesn't exist, add it.
94 			 */
95 			error = autofs_rddir_cache_enter(rda->rda_map,
96 					OFFSET_BUCKET_SIZE, &rdcp);
97 		}
98 	}
99 	rw_unlock(&autofs_rddir_cache_lock);
100 
101 	if (error)
102 		return (error);
103 
104 	assert(rdcp != NULL);
105 	assert(rdcp->in_use);
106 
107 	if (!rdcp->full) {
108 		rw_wrlock(&rdcp->rwlock);
109 		if (!rdcp->full) {
110 			/*
111 			 * cache entry hasn't been filled up, do it now.
112 			 */
113 			char *stack[STACKSIZ];
114 			char **stkptr;
115 
116 			/*
117 			 * Initialize the stack of open files
118 			 * for this thread
119 			 */
120 			stack_op(INIT, NULL, stack, &stkptr);
121 			(void) getmapkeys(rda->rda_map, &list, &error,
122 			    &cache_time, stack, &stkptr, rda->uid);
123 			if (!error)
124 				build_dir_entry_list(rdcp, list);
125 			else if (list) {
126 				free_dir_list(list);
127 				list = NULL;
128 			}
129 		}
130 	} else
131 		rw_rdlock(&rdcp->rwlock);
132 
133 	rd->rd_bufsize = rda->rda_count;
134 	if (!error) {
135 		error = create_dirents(rdcp, rda->rda_offset, rd);
136 		if (error) {
137 			if (rdcp->offtp) {
138 				free_offset_tbl(rdcp->offtp);
139 				rdcp->offtp = NULL;
140 			}
141 			if (rdcp->entp) {
142 				free_dir_list(rdcp->entp);
143 				rdcp->entp = NULL;
144 			}
145 			rdcp->full = 0;
146 			list = NULL;
147 		}
148 	}
149 
150 	if (trace > 2) {
151 		/*
152 		 * print this list only once
153 		 */
154 		for (l = list; l != NULL; l = l->next)
155 			trace_prt(0, "%s\n", l->name);
156 		trace_prt(0, "\n");
157 	}
158 
159 	if (!error) {
160 		rd->rd_status = AUTOFS_OK;
161 		if (cache_time) {
162 			/*
163 			 * keep list of entries for up to
164 			 * 'cache_time' seconds
165 			 */
166 			rdcp->ttl = time((time_t *)NULL) + cache_time;
167 		} else {
168 			/*
169 			 * the underlying name service indicated not
170 			 * to cache contents.
171 			 */
172 			if (rdcp->offtp) {
173 				free_offset_tbl(rdcp->offtp);
174 				rdcp->offtp = NULL;
175 			}
176 			if (rdcp->entp) {
177 				free_dir_list(rdcp->entp);
178 				rdcp->entp = NULL;
179 			}
180 			rdcp->full = 0;
181 		}
182 	} else {
183 		/*
184 		 * return an empty list
185 		 */
186 		rd->rd_rddir.rddir_size = 0;
187 		rd->rd_rddir.rddir_eof = 1;
188 		rd->rd_rddir.rddir_entries = NULL;
189 
190 		/*
191 		 * Invalidate cache and set error
192 		 */
193 		switch (error) {
194 		case ENOENT:
195 			rd->rd_status = AUTOFS_NOENT;
196 			break;
197 		case ENOMEM:
198 			rd->rd_status = AUTOFS_NOMEM;
199 			break;
200 		default:
201 			rd->rd_status = AUTOFS_ECOMM;
202 		}
203 	}
204 	rw_unlock(&rdcp->rwlock);
205 
206 	mutex_lock(&rdcp->lock);
207 	rdcp->in_use--;
208 	mutex_unlock(&rdcp->lock);
209 
210 	assert(rdcp->in_use >= 0);
211 
212 	return (error);
213 }
214 
215 #define	roundtoint(x)	(((x) + sizeof (int) - 1) & ~(sizeof (int) - 1))
216 #define	DIRENT64_RECLEN(namelen)	\
217 	(((int)(((dirent64_t *)0)->d_name) + 1 + (namelen) + 7) & ~ 7)
218 
219 static int
220 create_dirents(
221 	struct autofs_rddir_cache *rdcp,
222 	ulong_t offset,
223 	autofs_rddirres *res)
224 {
225 	uint_t total_bytes_wanted;
226 	int bufsize;
227 	ushort_t this_reclen;
228 	int outcount = 0;
229 	int namelen;
230 	struct dir_entry *list = NULL, *l, *nl;
231 	struct dirent64 *dp;
232 	char *outbuf;
233 	struct off_tbl *offtp, *next = NULL;
234 	int this_bucket = 0;
235 	int error = 0;
236 	int x = 0, y = 0;
237 
238 	assert(RW_LOCK_HELD(&rdcp->rwlock));
239 	for (offtp = rdcp->offtp; offtp != NULL; offtp = next) {
240 		x++;
241 		next = offtp->next;
242 		this_bucket = (next == NULL);
243 		if (!this_bucket)
244 			this_bucket = (offset < next->offset);
245 		if (this_bucket) {
246 			/*
247 			 * has to be in this bucket
248 			 */
249 			assert(offset >= offtp->offset);
250 			list = offtp->first;
251 			break;
252 		}
253 		/*
254 		 * loop to look in next bucket
255 		 */
256 	}
257 
258 	for (l = list; l != NULL && l->offset < offset; l = l->next)
259 		y++;
260 
261 	if (l == NULL) {
262 		/*
263 		 * reached end of directory
264 		 */
265 		error = 0;
266 		goto empty;
267 	}
268 
269 	if (trace > 2)
270 		trace_prt(1, "%s: offset searches (%d, %d)\n", rdcp->map, x, y);
271 
272 	total_bytes_wanted = res->rd_bufsize;
273 	bufsize = total_bytes_wanted + sizeof (struct dirent64);
274 	outbuf = malloc(bufsize);
275 	if (outbuf == NULL) {
276 		syslog(LOG_ERR, "memory allocation error\n");
277 		error = ENOMEM;
278 		goto empty;
279 	}
280 	memset(outbuf, 0, bufsize);
281 	/* LINTED pointer alignment */
282 	dp = (struct dirent64 *)outbuf;
283 
284 	while (l) {
285 		nl = l->next;
286 		namelen = strlen(l->name);
287 		this_reclen = DIRENT64_RECLEN(namelen);
288 		if (outcount + this_reclen > total_bytes_wanted) {
289 			break;
290 		}
291 		dp->d_ino = (ino64_t)l->nodeid;
292 		if (nl) {
293 			/*
294 			 * get the next elements offset
295 			 */
296 			dp->d_off = (off64_t)nl->offset;
297 		} else {
298 			/*
299 			 * This is the last element
300 			 * make offset one plus the current.
301 			 */
302 			dp->d_off = (off64_t)l->offset + 1;
303 		}
304 		(void) strcpy(dp->d_name, l->name);
305 		dp->d_reclen = (ushort_t)this_reclen;
306 		outcount += dp->d_reclen;
307 		dp = (struct dirent64 *)((int)dp + dp->d_reclen);
308 		assert(outcount <= total_bytes_wanted);
309 		l = l->next;
310 	}
311 
312 	res->rd_rddir.rddir_size = (long)outcount;
313 	if (outcount > 0) {
314 		/*
315 		 * have some entries
316 		 */
317 		res->rd_rddir.rddir_eof = (l == NULL);
318 		/* LINTED pointer alignment */
319 		res->rd_rddir.rddir_entries = (struct dirent64 *)outbuf;
320 		error = 0;
321 	} else {
322 		/*
323 		 * total_bytes_wanted is not large enough for one
324 		 * directory entry
325 		 */
326 		res->rd_rddir.rddir_eof = 0;
327 		res->rd_rddir.rddir_entries = NULL;
328 		free(outbuf);
329 		error = EIO;
330 	}
331 	return (error);
332 
333 empty:
334 	res->rd_rddir.rddir_size = 0L;
335 	res->rd_rddir.rddir_eof = TRUE;
336 	res->rd_rddir.rddir_entries = NULL;
337 	return (error);
338 }
339 
340 
341 /*
342  * add new entry to cache for 'map'
343  */
344 static int
345 autofs_rddir_cache_enter(
346 	char *map,
347 	ulong_t bucket_size,
348 	struct autofs_rddir_cache **rdcpp)
349 {
350 	struct autofs_rddir_cache *p;
351 	assert(RW_LOCK_HELD(&autofs_rddir_cache_lock));
352 
353 	/*
354 	 * Add to front of the list at this time
355 	 */
356 	p = (struct autofs_rddir_cache *)malloc(sizeof (*p));
357 	if (p == NULL) {
358 		syslog(LOG_ERR,
359 			"autofs_rddir_cache_enter: memory allocation failed\n");
360 		return (ENOMEM);
361 	}
362 	memset((char *)p, 0, sizeof (*p));
363 
364 	p->map = malloc(strlen(map) + 1);
365 	if (p->map == NULL) {
366 		syslog(LOG_ERR,
367 			"autofs_rddir_cache_enter: memory allocation failed\n");
368 		free(p);
369 		return (ENOMEM);
370 	}
371 	strcpy(p->map, map);
372 
373 	p->bucket_size = bucket_size;
374 	/*
375 	 * no need to grab mutex lock since I haven't yet made the
376 	 * node visible to the list
377 	 */
378 	p->in_use = 1;
379 	(void) rwlock_init(&p->rwlock, USYNC_THREAD, NULL);
380 	(void) mutex_init(&p->lock, USYNC_THREAD, NULL);
381 
382 	if (rddir_head == NULL)
383 		rddir_head = p;
384 	else {
385 		p->next = rddir_head;
386 		rddir_head = p;
387 	}
388 	*rdcpp = p;
389 
390 	return (0);
391 }
392 
393 /*
394  * find 'map' in readdir cache
395  */
396 int
397 autofs_rddir_cache_lookup(char *map, struct autofs_rddir_cache **rdcpp)
398 {
399 	struct autofs_rddir_cache *p;
400 
401 	assert(RW_LOCK_HELD(&autofs_rddir_cache_lock));
402 	for (p = rddir_head; p != NULL; p = p->next) {
403 		if (strcmp(p->map, map) == 0) {
404 			/*
405 			 * found matching entry
406 			 */
407 			*rdcpp = p;
408 			mutex_lock(&p->lock);
409 			p->in_use++;
410 			mutex_unlock(&p->lock);
411 			return (0);
412 		}
413 	}
414 	/*
415 	 * didn't find entry
416 	 */
417 	return (ENOENT);
418 }
419 
420 /*
421  * free the offset table
422  */
423 static void
424 free_offset_tbl(struct off_tbl *head)
425 {
426 	struct off_tbl *p, *next = NULL;
427 
428 	for (p = head; p != NULL; p = next) {
429 		next = p->next;
430 		free(p);
431 	}
432 }
433 
434 /*
435  * free the directory entries
436  */
437 static void
438 free_dir_list(struct dir_entry *head)
439 {
440 	struct dir_entry *p, *next = NULL;
441 
442 	for (p = head; p != NULL; p = next) {
443 		next = p->next;
444 		assert(p->name);
445 		free(p->name);
446 		free(p);
447 	}
448 }
449 
450 static void
451 autofs_rddir_cache_entry_free(struct autofs_rddir_cache *p)
452 {
453 	assert(RW_LOCK_HELD(&autofs_rddir_cache_lock));
454 	assert(!p->in_use);
455 	if (p->map)
456 		free(p->map);
457 	if (p->offtp)
458 		free_offset_tbl(p->offtp);
459 	if (p->entp)
460 		free_dir_list(p->entp);
461 	free(p);
462 }
463 
464 /*
465  * Remove entry from the rddircache
466  * the caller must own the autofs_rddir_cache_lock.
467  */
468 static int
469 autofs_rddir_cache_delete(struct autofs_rddir_cache *rdcp)
470 {
471 	struct autofs_rddir_cache *p, *prev;
472 
473 	assert(RW_LOCK_HELD(&autofs_rddir_cache_lock));
474 	/*
475 	 * Search cache for entry
476 	 */
477 	prev = NULL;
478 	for (p = rddir_head; p != NULL; p = p->next) {
479 		if (p == rdcp) {
480 			/*
481 			 * entry found, remove from list if not in use
482 			 */
483 			if (p->in_use)
484 				return (EBUSY);
485 			if (prev)
486 				prev->next = p->next;
487 			else
488 				rddir_head = p->next;
489 			autofs_rddir_cache_entry_free(p);
490 			return (0);
491 		}
492 		prev = p;
493 	}
494 	syslog(LOG_ERR, "Couldn't find entry %x in cache\n", p);
495 	return (ENOENT);
496 }
497 
498 /*
499  * Return entry that matches name, NULL otherwise.
500  * Assumes the readers lock for this list has been grabed.
501  */
502 struct dir_entry *
503 rddir_entry_lookup(char *name, struct dir_entry *list)
504 {
505 	return (btree_lookup(list, name));
506 }
507 
508 static void
509 build_dir_entry_list(struct autofs_rddir_cache *rdcp, struct dir_entry *list)
510 {
511 	struct dir_entry *p;
512 	ulong_t offset = AUTOFS_DAEMONCOOKIE, offset_list = AUTOFS_DAEMONCOOKIE;
513 	struct off_tbl *offtp, *last = NULL;
514 	ino_t inonum = 4;
515 
516 	assert(RW_LOCK_HELD(&rdcp->rwlock));
517 	assert(rdcp->entp == NULL);
518 	rdcp->entp = list;
519 	for (p = list; p != NULL; p = p->next) {
520 		p->nodeid = inonum;
521 		p->offset = offset;
522 		if (offset >= offset_list) {
523 			/*
524 			 * add node to index table
525 			 */
526 			offtp = (struct off_tbl *)
527 				malloc(sizeof (struct off_tbl));
528 			if (offtp != NULL) {
529 				offtp->offset = offset;
530 				offtp->first = p;
531 				offtp->next = NULL;
532 				offset_list += rdcp->bucket_size;
533 			} else {
534 				syslog(LOG_ERR,
535 "WARNING: build_dir_entry_list: could not add offset to index table\n");
536 				continue;
537 			}
538 			/*
539 			 * add to cache
540 			 */
541 			if (rdcp->offtp == NULL)
542 				rdcp->offtp = offtp;
543 			else
544 				last->next = offtp;
545 			last = offtp;
546 		}
547 		offset++;
548 		inonum += 2;		/* use even numbers in daemon */
549 	}
550 	rdcp->full = 1;
551 }
552 
553 mutex_t cleanup_lock;
554 cond_t cleanup_start_cv;
555 cond_t cleanup_done_cv;
556 
557 /*
558  * cache cleanup thread starting point
559  */
560 void
561 cache_cleanup(void)
562 {
563 	timestruc_t reltime;
564 	struct autofs_rddir_cache *p, *next = NULL;
565 	int error;
566 
567 	mutex_init(&cleanup_lock, USYNC_THREAD, NULL);
568 	cond_init(&cleanup_start_cv, USYNC_THREAD, NULL);
569 	cond_init(&cleanup_done_cv, USYNC_THREAD, NULL);
570 
571 	mutex_lock(&cleanup_lock);
572 	for (;;) {
573 		reltime.tv_sec = RDDIR_CACHE_TIME/2;
574 		reltime.tv_nsec = 0;
575 
576 		/*
577 		 * delay RDDIR_CACHE_TIME seconds, or until some other thread
578 		 * requests that I cleanup the caches
579 		 */
580 		if (error = cond_reltimedwait(
581 		    &cleanup_start_cv, &cleanup_lock, &reltime)) {
582 			if (error != ETIME) {
583 				if (trace > 1)
584 					trace_prt(1,
585 					"cleanup thread wakeup (%d)\n", error);
586 				continue;
587 			}
588 		}
589 		mutex_unlock(&cleanup_lock);
590 
591 		/*
592 		 * Perform the cache cleanup
593 		 */
594 		rw_wrlock(&autofs_rddir_cache_lock);
595 		for (p = rddir_head; p != NULL; p = next) {
596 			next = p->next;
597 			if (p->in_use > 0) {
598 				/*
599 				 * cache entry busy, skip it
600 				 */
601 				if (trace > 1) {
602 					trace_prt(1,
603 					"%s cache in use\n", p->map);
604 				}
605 				continue;
606 			}
607 			/*
608 			 * Cache entry is not in use, and nobody can grab a
609 			 * new reference since I'm holding the
610 			 * autofs_rddir_cache_lock
611 			 */
612 
613 			/*
614 			 * error will be zero if some thread signaled us asking
615 			 * that the caches be freed. In such case, free caches
616 			 * even if they're still valid and nobody is referencing
617 			 * them at this time. Otherwise, free caches only
618 			 * if their time to live (ttl) has expired.
619 			 */
620 			if (error == ETIME && (p->ttl > time((time_t *)NULL))) {
621 				/*
622 				 * Scheduled cache cleanup, if cache is still
623 				 * valid don't free.
624 				 */
625 				if (trace > 1) {
626 					trace_prt(1,
627 					"%s cache still valid\n", p->map);
628 				}
629 				continue;
630 			}
631 			if (trace > 1)
632 				trace_prt(1, "%s freeing cache\n", p->map);
633 			assert(!p->in_use);
634 			error = autofs_rddir_cache_delete(p);
635 			assert(!error);
636 		}
637 		rw_unlock(&autofs_rddir_cache_lock);
638 
639 		/*
640 		 * wakeup the thread/threads waiting for the
641 		 * cleanup to finish
642 		 */
643 		mutex_lock(&cleanup_lock);
644 		cond_broadcast(&cleanup_done_cv);
645 	}
646 	/* NOTREACHED */
647 }
648