xref: /illumos-gate/usr/src/cmd/nscd/getexec.c (revision 60405de4d8688d96dd05157c28db3ade5c9bc234)
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  * Copyright 2005 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 /*
30  * Routines to handle getexec* calls in nscd
31  */
32 
33 #include <assert.h>
34 #include <errno.h>
35 #include <memory.h>
36 #include <signal.h>
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <sys/door.h>
41 #include <sys/stat.h>
42 #include <sys/time.h>
43 #include <sys/types.h>
44 #include <sys/wait.h>
45 #include <thread.h>
46 #include <unistd.h>
47 #include <ucred.h>
48 #include <nss_common.h>
49 #include <nss_dbdefs.h>
50 
51 #include <exec_attr.h>
52 
53 #include <getxby_door.h>
54 #include "server_door.h"
55 #include "nscd.h"
56 
57 extern execstr_t *_getexecprof(char *, char *, char *, int, execstr_t *,
58     char *, int, int *);
59 
60 static hash_t *nam_hash;
61 static mutex_t  db_lock = DEFAULTMUTEX;
62 static waiter_t db_wait;
63 
64 static int getexec_namekeepalive(int keep, int interval);
65 static int update_exec_bucket(nsc_bucket_t **old, nsc_bucket_t *new,
66     int callnumber);
67 static nsc_bucket_t *fixbuffer(nsc_return_t *in, int maxlen);
68 static void do_findgnams(nsc_bucket_t *ptr, int *table, char *gnam);
69 static void do_invalidate(nsc_bucket_t **ptr, int callnumber);
70 static void getexec_invalidate_unlocked(void);
71 
72 
73 void
74 getexec_init(void)
75 {
76 	nam_hash = make_hash(current_admin.exec.nsc_suggestedsize);
77 }
78 
79 static void
80 do_invalidate(nsc_bucket_t ** ptr, int callnumber)
81 {
82 	if (*ptr != NULL && *ptr != (nsc_bucket_t *)-1) {
83 		/* leave pending calls alone */
84 		update_exec_bucket(ptr, NULL, callnumber);
85 	}
86 }
87 
88 static void
89 do_findgnams(nsc_bucket_t *ptr, int *table, char *gnam)
90 {
91 
92 	/*
93 	 * be careful with ptr - it may be -1 or NULL.
94 	 */
95 
96 	if (ptr != NULL && ptr != (nsc_bucket_t *)-1) {
97 		char *tmp = (char *)insertn(table, ptr->nsc_hits,
98 		    (int)strdup(gnam));
99 		if (tmp != (char *)-1)
100 			free(tmp);
101 	}
102 }
103 
104 void
105 getexec_revalidate(void)
106 {
107 	for (;;) {
108 		int slp;
109 		int interval;
110 		int count;
111 
112 		slp = current_admin.exec.nsc_pos_ttl;
113 
114 		if (slp < 60) {
115 			slp = 60;
116 		}
117 		if ((count = current_admin.exec.nsc_keephot) != 0) {
118 			interval = (slp / 2)/count;
119 			if (interval == 0) interval = 1;
120 			sleep(slp * 2 / 3);
121 			getexec_namekeepalive(count, interval);
122 		} else {
123 			sleep(slp);
124 		}
125 	}
126 }
127 
128 static int
129 getexec_namekeepalive(int keep, int interval)
130 {
131 	int *table;
132 	union {
133 		nsc_data_t  ping;
134 		char space[sizeof (nsc_data_t) + NSCDMAXNAMELEN];
135 	} u;
136 
137 	int i;
138 
139 	if (!keep)
140 		return (0);
141 
142 	table = maken(keep);
143 	mutex_lock(&db_lock);
144 	operate_hash(nam_hash, do_findgnams, (char *)table);
145 	mutex_unlock(&db_lock);
146 
147 	for (i = 1; i <= keep; i++) {
148 		char *tmp;
149 
150 		u.ping.nsc_call.nsc_callnumber = GETEXECID;
151 		if ((tmp = (char *)table[keep + 1 + i]) == (char *)-1)
152 			continue; /* unused slot in table */
153 		strcpy(u.ping.nsc_call.nsc_u.name, tmp);
154 		launch_update(&u.ping.nsc_call);
155 		sleep(interval);
156 	}
157 
158 	for (i = 1; i <= keep; i++) {
159 		char *tmp;
160 		if ((tmp = (char *)table[keep + 1 + i]) != (char *)-1)
161 			free(tmp);
162 	}
163 
164 	free(table);
165 	return (0);
166 }
167 
168 
169 /*
170  *   This routine marks all entries as invalid
171  *
172  */
173 
174 void
175 getexec_invalidate(void)
176 {
177 	mutex_lock(&db_lock);
178 	getexec_invalidate_unlocked();
179 	mutex_unlock(&db_lock);
180 }
181 
182 static void
183 getexec_invalidate_unlocked(void)
184 {
185 	operate_hash_addr(nam_hash, do_invalidate, (char *)GETEXECID);
186 	current_admin.exec.nsc_invalidate_count++;
187 }
188 
189 void
190 getexec_lookup(nsc_return_t *out, int maxsize, nsc_call_t *in, time_t now)
191 {
192 	int		out_of_date;
193 	nsc_bucket_t	*retb;
194 	char 		**bucket;
195 	char		*p_name;
196 	char		*p_type;
197 	char		*p_id;
198 	char		*unique;
199 	char 		*lasts;
200 	char		*sep = ":";
201 
202 	static time_t	lastmod;
203 
204 	int bufferspace = maxsize - sizeof (nsc_return_t);
205 
206 	if (current_admin.exec.nsc_enabled == 0) {
207 		out->nsc_return_code = NOSERVER;
208 		out->nsc_bufferbytesused = sizeof (*out);
209 		return;
210 	}
211 
212 	mutex_lock(&db_lock);
213 
214 	if (current_admin.exec.nsc_check_files) {
215 		struct stat buf;
216 
217 		if (stat(EXECATTR_FILENAME, &buf) < 0) {
218 			/*EMPTY*/;
219 		} else if (lastmod == 0) {
220 			lastmod = buf.st_mtime;
221 		} else if (lastmod < buf.st_mtime) {
222 			getexec_invalidate_unlocked();
223 			lastmod = buf.st_mtime;
224 		}
225 	}
226 
227 	if (current_admin.debug_level >= DBG_ALL) {
228 		logit("getexec_lookup: looking for name %s\n",
229 				in->nsc_u.name);
230 	}
231 
232 	for (;;) {
233 		if (attr_strlen(in->nsc_u.name) > NSCDMAXNAMELEN) {
234 			ucred_t *uc = NULL;
235 
236 			if (door_ucred(&uc) != 0) {
237 				logit("getexec_lookup: Name too long, "
238 				    "but no user credential: %s\n",
239 				    strerror(errno));
240 			} else {
241 				logit("getexec_lookup: Name too long from pid "
242 				    "%d uid %d\n", ucred_getpid(uc),
243 				    ucred_getruid(uc));
244 				ucred_free(uc);
245 			}
246 
247 			out->nsc_errno = NSS_NOTFOUND;
248 			out->nsc_return_code = NOTFOUND;
249 			out->nsc_bufferbytesused = sizeof (*out);
250 			goto getout;
251 		}
252 		bucket = get_hash(nam_hash, in->nsc_u.name);
253 
254 		if (*bucket == (char *)-1) {	/* pending lookup */
255 			if (get_clearance(in->nsc_callnumber) != 0) {
256 			    /* no threads available */
257 				out->nsc_return_code = NOSERVER;
258 				/* cannot process now */
259 				out->nsc_bufferbytesused =
260 				    sizeof (*out);
261 				current_admin.exec.nsc_throttle_count++;
262 				goto getout;
263 			}
264 			nscd_wait(&db_wait, &db_lock, bucket);
265 			release_clearance(in->nsc_callnumber);
266 			continue; /* go back and relookup hash bucket */
267 		}
268 		break;
269 	}
270 
271 	/*
272 	 * check for no name_service mode
273 	 */
274 
275 	if (*bucket == NULL && current_admin.avoid_nameservice) {
276 		out->nsc_return_code = NOTFOUND;
277 		out->nsc_bufferbytesused = sizeof (*out);
278 	} else if ((*bucket == NULL) || /* New entry in name service */
279 	    (in->nsc_callnumber & UPDATEBIT) || /* needs updating */
280 	    (out_of_date = (!current_admin.avoid_nameservice &&
281 	    (current_admin.exec.nsc_old_data_ok == 0) &&
282 	    (((nsc_bucket_t *)*bucket)->nsc_timestamp < now)))) {
283 		/* time has expired */
284 		int saved_errno;
285 		int saved_hits = 0;
286 		execstr_t *p;
287 
288 		if (get_clearance(in->nsc_callnumber) != 0) {
289 		    /* no threads available */
290 			out->nsc_return_code = NOSERVER;
291 			    /* cannot process now */
292 			out->nsc_bufferbytesused = sizeof (*out);
293 			current_admin.exec.nsc_throttle_count++;
294 			goto getout;
295 		}
296 
297 		if (*bucket != NULL) {
298 			saved_hits =
299 			    ((nsc_bucket_t *)*bucket)->nsc_hits;
300 		}
301 
302 		/*
303 		 *  block any threads accessing this bucket if data is
304 		 *  non-existent out of date
305 		 */
306 
307 		if (*bucket == NULL || out_of_date) {
308 			update_exec_bucket((nsc_bucket_t **)bucket,
309 			    (nsc_bucket_t *)-1,
310 			    in->nsc_callnumber);
311 		} else {
312 		/*
313 		 * if still not -1 bucket we are doing update...
314 		 * mark to prevent
315 		 * pileups of threads if the name service is hanging....
316 		 */
317 			((nsc_bucket_t *)(*bucket))->nsc_status |=
318 			    ST_UPDATE_PENDING;
319 			/* cleared by deletion of old data */
320 		}
321 		mutex_unlock(&db_lock);
322 
323 		/*
324 		 * Call non-caching version in libnsl.
325 		 */
326 		unique = strdup(in->nsc_u.name);
327 		p_name = strtok_r(unique, sep, &lasts);
328 		p_type = strtok_r(NULL, sep, &lasts);
329 		p_id = strtok_r(NULL, sep, &lasts);
330 		p = _getexecprof(p_name,
331 		    p_type,
332 		    p_id,
333 		    GET_ONE,
334 		    &out->nsc_u.exec,
335 		    out->nsc_u.buff + sizeof (execstr_t),
336 		    bufferspace,
337 		    &saved_errno);
338 
339 		free(unique);
340 		mutex_lock(&db_lock);
341 
342 		release_clearance(in->nsc_callnumber);
343 
344 		if (p == NULL) { /* data not found */
345 
346 			if (current_admin.debug_level >= DBG_CANT_FIND) {
347 		logit("getexec_lookup: nscd COULDN'T FIND exec_attr name %s\n",
348 						in->nsc_u.name);
349 			}
350 
351 			if (!(UPDATEBIT & in->nsc_callnumber))
352 			    current_admin.exec.nsc_neg_cache_misses++;
353 
354 			retb = (nsc_bucket_t *)malloc(sizeof (nsc_bucket_t));
355 
356 			retb->nsc_refcount = 1;
357 			retb->nsc_data.nsc_bufferbytesused =
358 				sizeof (nsc_return_t);
359 			retb->nsc_data.nsc_return_code = NOTFOUND;
360 			retb->nsc_data.nsc_errno = saved_errno;
361 			memcpy(out, &retb->nsc_data,
362 			    retb->nsc_data.nsc_bufferbytesused);
363 			update_exec_bucket((nsc_bucket_t **)bucket,
364 			    retb, in->nsc_callnumber);
365 			goto getout;
366 		} else {
367 			if (current_admin.debug_level >= DBG_ALL) {
368 		logit("getexec_lookup: nscd FOUND exec_attr name %s\n",
369 						in->nsc_u.name);
370 			}
371 			if (!(UPDATEBIT & in->nsc_callnumber))
372 			    current_admin.exec.nsc_pos_cache_misses++;
373 			retb = fixbuffer(out, bufferspace);
374 			update_exec_bucket((nsc_bucket_t **)bucket,
375 			    retb, in->nsc_callnumber);
376 			if (saved_hits)
377 				retb->nsc_hits = saved_hits;
378 		}
379 	} else { 	/* found entry in cache */
380 		retb = (nsc_bucket_t *)*bucket;
381 		retb->nsc_hits++;
382 		memcpy(out, &(retb->nsc_data),
383 		    retb->nsc_data.nsc_bufferbytesused);
384 		if (out->nsc_return_code == SUCCESS) {
385 			if (!(UPDATEBIT & in->nsc_callnumber))
386 			    current_admin.exec.nsc_pos_cache_hits++;
387 			if (current_admin.debug_level >= DBG_ALL) {
388 			logit("getexec_lookup: found name %s in cache\n",
389 						in->nsc_u.name);
390 			}
391 		} else {
392 			if (!(UPDATEBIT & in->nsc_callnumber))
393 			    current_admin.exec.nsc_neg_cache_hits++;
394 			if (current_admin.debug_level >= DBG_ALL) {
395 		logit("getexec_lookup: %s marked as NOT FOUND in cache.\n",
396 						in->nsc_u.name);
397 			}
398 		}
399 
400 		if ((retb->nsc_timestamp < now) &&
401 		    !(in->nsc_callnumber & UPDATEBIT) &&
402 		    !(retb->nsc_status & ST_UPDATE_PENDING)) {
403 		logit("launch update since time = %d\n", retb->nsc_timestamp);
404 			retb->nsc_status |= ST_UPDATE_PENDING;
405 			/* cleared by deletion of old data */
406 			launch_update(in);
407 		}
408 	}
409 
410 getout:
411 	mutex_unlock(&db_lock);
412 }
413 
414 /*ARGSUSED*/
415 static int
416 update_exec_bucket(nsc_bucket_t **old, nsc_bucket_t *new, int callnumber)
417 {
418 	if (*old != NULL && *old != (nsc_bucket_t *)-1) { /* old data exists */
419 		free(*old);
420 		current_admin.exec.nsc_entries--;
421 	}
422 
423 	/*
424 	 *  we can do this before reseting *old since we're holding the lock
425 	 */
426 
427 	else if (*old == (nsc_bucket_t *)-1) {
428 		nscd_signal(&db_wait, (char **)old);
429 	}
430 
431 	*old = new;
432 
433 	if ((new != NULL) && (new != (nsc_bucket_t *)-1)) {
434 		/* real data, not just update pending or invalidate */
435 		new->nsc_hits = 1;
436 		new->nsc_status = 0;
437 		new->nsc_refcount = 1;
438 		current_admin.exec.nsc_entries++;
439 
440 		if (new->nsc_data.nsc_return_code == SUCCESS) {
441 			new->nsc_timestamp = time(NULL) +
442 			    current_admin.exec.nsc_pos_ttl;
443 		} else {
444 			new->nsc_timestamp = time(NULL) +
445 			    current_admin.exec.nsc_neg_ttl;
446 		}
447 	}
448 	return (0);
449 }
450 
451 /*ARGSUSED*/
452 static nsc_bucket_t *
453 fixbuffer(nsc_return_t *in, int maxlen)
454 {
455 	nsc_bucket_t *retb;
456 	nsc_return_t *out;
457 	char 	*dest;
458 	int 	offset;
459 	int 	strs;
460 
461 	/*
462 	 * find out the size of the data block we're going to need
463 	 */
464 
465 	strs = attr_strlen(in->nsc_u.exec.name) +
466 	    attr_strlen(in->nsc_u.exec.policy) +
467 	    attr_strlen(in->nsc_u.exec.type) +
468 	    attr_strlen(in->nsc_u.exec.res1) +
469 	    attr_strlen(in->nsc_u.exec.res2) +
470 	    attr_strlen(in->nsc_u.exec.id) +
471 	    attr_strlen(in->nsc_u.exec.attr) + EXECATTR_DB_NCOL;
472 
473 	/*
474 	 * allocate it and copy it in
475 	 * code doesn't assume packing order in original buffer
476 	 */
477 
478 	if ((retb = (nsc_bucket_t *)malloc(sizeof (*retb) + strs)) == NULL) {
479 		return (NULL);
480 	}
481 
482 	out = &(retb->nsc_data);
483 	out->nsc_bufferbytesused = strs + ((int)&out->nsc_u.exec - (int)out) +
484 	    sizeof (execstr_t);
485 	out->nsc_return_code 	= SUCCESS;
486 	out->nsc_errno 		= 0;
487 
488 	dest = retb->nsc_data.nsc_u.buff + sizeof (execstr_t);
489 	offset = (int)dest;
490 
491 	attr_strcpy(dest, in->nsc_u.exec.name);
492 	strs = 1 + attr_strlen(in->nsc_u.exec.name);
493 	out->nsc_u.exec.name = dest - offset;
494 	dest += strs;
495 
496 	attr_strcpy(dest, in->nsc_u.exec.policy);
497 	strs = 1 + attr_strlen(in->nsc_u.exec.policy);
498 	out->nsc_u.exec.policy = dest - offset;
499 	dest += strs;
500 
501 	attr_strcpy(dest, in->nsc_u.exec.type);
502 	strs = 1 + attr_strlen(in->nsc_u.exec.type);
503 	out->nsc_u.exec.type = dest - offset;
504 	dest += strs;
505 
506 	attr_strcpy(dest, in->nsc_u.exec.res1);
507 	strs = 1 + attr_strlen(in->nsc_u.exec.res1);
508 	out->nsc_u.exec.res1 = dest - offset;
509 	dest += strs;
510 
511 	attr_strcpy(dest, in->nsc_u.exec.res2);
512 	strs = 1 + attr_strlen(in->nsc_u.exec.res2);
513 	out->nsc_u.exec.res2 = dest - offset;
514 	dest += strs;
515 
516 	attr_strcpy(dest, in->nsc_u.exec.id);
517 	strs = 1 + attr_strlen(in->nsc_u.exec.id);
518 	out->nsc_u.exec.id = dest - offset;
519 	dest += strs;
520 
521 	attr_strcpy(dest, in->nsc_u.exec.attr);
522 	out->nsc_u.exec.attr = dest - offset;
523 
524 	memcpy(in, out, out->nsc_bufferbytesused);
525 
526 	return (retb);
527 }
528 
529 void
530 getexec_reaper(void)
531 {
532 	nsc_reaper("getexec", nam_hash, &current_admin.exec, &db_lock);
533 }
534