xref: /titanic_44/usr/src/uts/common/syscall/acctctl.c (revision b7f45089ccbe01bab3d7c7377b49d80d2ae18a69)
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 2003 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 <sys/proc.h>
30 #include <sys/systm.h>
31 #include <sys/param.h>
32 #include <sys/kmem.h>
33 #include <sys/sysmacros.h>
34 #include <sys/types.h>
35 #include <sys/cmn_err.h>
36 #include <sys/user.h>
37 #include <sys/cred.h>
38 #include <sys/vnode.h>
39 #include <sys/file.h>
40 #include <sys/pathname.h>
41 #include <sys/modctl.h>
42 #include <sys/acctctl.h>
43 #include <sys/bitmap.h>
44 #include <sys/exacct.h>
45 #include <sys/policy.h>
46 
47 /*
48  * acctctl(2)
49  *
50  *   acctctl() provides the administrative interface to the extended accounting
51  *   subsystem.  The process and task accounting facilities are configurable:
52  *   resources can be individually specified for recording in the appropriate
53  *   accounting file.
54  *
55  *   The current implementation of acctctl() requires that the process and task
56  *   and flow files be distinct across all zones.
57  *
58  * Locking
59  *   Each accounting species has an ac_info_t which contains a mutex,
60  *   used to protect the ac_info_t's contents, and to serialize access to the
61  *   appropriate file.
62  */
63 
64 static list_t exacct_globals_list;
65 static kmutex_t exacct_globals_list_lock;
66 
67 static int
68 ac_state_set(ac_info_t *info, void *buf, size_t bufsz)
69 {
70 	int state;
71 
72 	if (buf == NULL || (bufsz != sizeof (int)))
73 		return (EINVAL);
74 
75 	if (copyin(buf, &state, bufsz) != 0)
76 		return (EFAULT);
77 
78 	if (state != AC_ON && state != AC_OFF)
79 		return (EINVAL);
80 
81 	mutex_enter(&info->ac_lock);
82 	info->ac_state = state;
83 	mutex_exit(&info->ac_lock);
84 	return (0);
85 }
86 
87 static int
88 ac_state_get(ac_info_t *info, void *buf, size_t bufsz)
89 {
90 	if (buf == NULL || (bufsz != sizeof (int)))
91 		return (EINVAL);
92 
93 	mutex_enter(&info->ac_lock);
94 	if (copyout(&info->ac_state, buf, bufsz) != 0) {
95 		mutex_exit(&info->ac_lock);
96 		return (EFAULT);
97 	}
98 	mutex_exit(&info->ac_lock);
99 	return (0);
100 }
101 
102 static boolean_t
103 ac_file_in_use(vnode_t *vp)
104 {
105 	boolean_t in_use = B_FALSE;
106 	struct exacct_globals *acg;
107 
108 	if (vp == NULL)
109 		return (B_FALSE);
110 	mutex_enter(&exacct_globals_list_lock);
111 	/*
112 	 * Start off by grabbing all locks.
113 	 */
114 	for (acg = list_head(&exacct_globals_list); acg != NULL;
115 	    acg = list_next(&exacct_globals_list, acg)) {
116 		mutex_enter(&acg->ac_proc.ac_lock);
117 		mutex_enter(&acg->ac_task.ac_lock);
118 		mutex_enter(&acg->ac_flow.ac_lock);
119 	}
120 
121 	for (acg = list_head(&exacct_globals_list); !in_use && acg != NULL;
122 	    acg = list_next(&exacct_globals_list, acg)) {
123 		/*
124 		 * We need to verify that we aren't already using this file for
125 		 * accounting in any zone.
126 		 */
127 		if (vn_compare(acg->ac_proc.ac_vnode, vp) ||
128 		    vn_compare(acg->ac_task.ac_vnode, vp) ||
129 		    vn_compare(acg->ac_flow.ac_vnode, vp))
130 			in_use = B_TRUE;
131 	}
132 
133 	/*
134 	 * Drop all locks.
135 	 */
136 	for (acg = list_head(&exacct_globals_list); acg != NULL;
137 	    acg = list_next(&exacct_globals_list, acg)) {
138 		mutex_exit(&acg->ac_proc.ac_lock);
139 		mutex_exit(&acg->ac_task.ac_lock);
140 		mutex_exit(&acg->ac_flow.ac_lock);
141 	}
142 	mutex_exit(&exacct_globals_list_lock);
143 	return (in_use);
144 }
145 
146 static int
147 ac_file_set(ac_info_t *info, void *ubuf, size_t bufsz)
148 {
149 	int error = 0;
150 	void *kbuf;
151 	void *namebuf;
152 	int namelen;
153 	vnode_t *vp;
154 	void *hdr;
155 	size_t hdrsize;
156 
157 	if (ubuf == NULL) {
158 		mutex_enter(&info->ac_lock);
159 
160 		/*
161 		 * Closing accounting file
162 		 */
163 		if (info->ac_vnode != NULL) {
164 			error = VOP_CLOSE(info->ac_vnode, FWRITE, 1, 0, CRED());
165 			if (error) {
166 				mutex_exit(&info->ac_lock);
167 				return (error);
168 			}
169 			VN_RELE(info->ac_vnode);
170 			info->ac_vnode = NULL;
171 		}
172 		if (info->ac_file != NULL) {
173 			kmem_free(info->ac_file, strlen(info->ac_file) + 1);
174 			info->ac_file = NULL;
175 		}
176 
177 		mutex_exit(&info->ac_lock);
178 		return (error);
179 	}
180 
181 	if (bufsz < 2 || bufsz > MAXPATHLEN)
182 		return (EINVAL);
183 
184 	/*
185 	 * We have to copy in the whole buffer since we can't tell the length
186 	 * of the string in user's address space.
187 	 */
188 	kbuf = kmem_zalloc(bufsz, KM_SLEEP);
189 	if ((error = copyinstr((char *)ubuf, (char *)kbuf, bufsz, NULL)) != 0) {
190 		kmem_free(kbuf, bufsz);
191 		return (error);
192 	}
193 	if (*((char *)kbuf) != '/') {
194 		kmem_free(kbuf, bufsz);
195 		return (EINVAL);
196 	}
197 
198 	/*
199 	 * Now, allocate the space where we are going to save the
200 	 * name of the accounting file and kmem_free kbuf. We have to do this
201 	 * now because it is not good to sleep in kmem_alloc() while
202 	 * holding ac_info's lock.
203 	 */
204 	namelen = strlen(kbuf) + 1;
205 	namebuf = kmem_alloc(namelen, KM_SLEEP);
206 	(void) strcpy(namebuf, kbuf);
207 	kmem_free(kbuf, bufsz);
208 
209 	/*
210 	 * Check if this file already exists.
211 	 */
212 	error = lookupname(namebuf, UIO_SYSSPACE, FOLLOW, NULLVPP, &vp);
213 
214 	/*
215 	 * Check if the file is already in use.
216 	 */
217 	if (!error) {
218 		if (ac_file_in_use(vp)) {
219 			/*
220 			 * If we're already using it then return EBUSY
221 			 */
222 			kmem_free(namebuf, namelen);
223 			VN_RELE(vp);
224 			return (EBUSY);
225 		}
226 		VN_RELE(vp);
227 	}
228 
229 	/*
230 	 * Now, grab info's ac_lock and try to set up everything.
231 	 */
232 	mutex_enter(&info->ac_lock);
233 
234 	if ((error = vn_open(namebuf, UIO_SYSSPACE,
235 	    FCREAT | FWRITE | FTRUNC, 0600, &vp, CRCREAT, 0)) != 0) {
236 		mutex_exit(&info->ac_lock);
237 		kmem_free(namebuf, namelen);
238 		return (error);
239 	}
240 
241 	if (vp->v_type != VREG) {
242 		VN_RELE(vp);
243 		mutex_exit(&info->ac_lock);
244 		kmem_free(namebuf, namelen);
245 		return (EACCES);
246 	}
247 
248 	if (info->ac_vnode != NULL) {
249 		/*
250 		 * Switch from an old file to a new file by swapping
251 		 * their vnode pointers.
252 		 */
253 		vnode_t *oldvp;
254 		oldvp = info->ac_vnode;
255 		info->ac_vnode = vp;
256 		vp = oldvp;
257 	} else {
258 		/*
259 		 * Start writing accounting records to a new file.
260 		 */
261 		info->ac_vnode = vp;
262 		vp = NULL;
263 	}
264 	if (vp) {
265 		/*
266 		 * We still need to close the old file.
267 		 */
268 		if ((error = VOP_CLOSE(vp, FWRITE, 1, 0, CRED())) != 0) {
269 			VN_RELE(vp);
270 			mutex_exit(&info->ac_lock);
271 			kmem_free(namebuf, namelen);
272 			return (error);
273 		}
274 		VN_RELE(vp);
275 		if (info->ac_file != NULL) {
276 			kmem_free(info->ac_file,
277 			    strlen(info->ac_file) + 1);
278 			info->ac_file = NULL;
279 		}
280 	}
281 	/*
282 	 * Finally, point ac_file to the filename string and release the lock.
283 	 */
284 	info->ac_file = namebuf;
285 	mutex_exit(&info->ac_lock);
286 
287 	/*
288 	 * Create and write an exacct header to the file.
289 	 */
290 	hdr = exacct_create_header(&hdrsize);
291 	error = exacct_write_header(info, hdr, hdrsize);
292 
293 	return (error);
294 }
295 
296 static int
297 ac_file_get(ac_info_t *info, void *buf, size_t bufsz)
298 {
299 	int error = 0;
300 	vnode_t *vnode;
301 	char *file;
302 
303 	mutex_enter(&info->ac_lock);
304 	file = info->ac_file;
305 	vnode = info->ac_vnode;
306 
307 	if (file == NULL || vnode == NULL) {
308 		mutex_exit(&info->ac_lock);
309 		return (ENOTACTIVE);
310 	}
311 
312 	if (strlen(file) >= bufsz)
313 		error = ENOMEM;
314 	else
315 		error = copyoutstr(file, buf, MAXPATHLEN, NULL);
316 
317 	mutex_exit(&info->ac_lock);
318 	return (error);
319 }
320 
321 static int
322 ac_res_set(ac_info_t *info, void *buf, size_t bufsz, int maxres)
323 {
324 	ac_res_t *res;
325 	ac_res_t *tmp;
326 	ulong_t *maskp;
327 	int id;
328 	uint_t counter = 0;
329 
330 	/*
331 	 * Validate that a non-zero buffer, sized within limits and to an
332 	 * integral number of ac_res_t's has been specified.
333 	 */
334 	if (bufsz == 0 ||
335 	    bufsz > sizeof (ac_res_t) * (AC_MAX_RES + 1) ||
336 	    (bufsz / sizeof (ac_res_t)) * sizeof (ac_res_t) != bufsz)
337 		return (EINVAL);
338 
339 	tmp = res = kmem_alloc(bufsz, KM_SLEEP);
340 	if (copyin(buf, res, bufsz) != 0) {
341 		kmem_free(res, bufsz);
342 		return (EFAULT);
343 	}
344 
345 	maskp = (ulong_t *)&info->ac_mask;
346 
347 	mutex_enter(&info->ac_lock);
348 	while ((id = tmp->ar_id) != AC_NONE && counter < maxres + 1) {
349 		if (id > maxres || id < 0) {
350 			mutex_exit(&info->ac_lock);
351 			kmem_free(res, bufsz);
352 			return (EINVAL);
353 		}
354 		if (tmp->ar_state == AC_ON) {
355 			BT_SET(maskp, id);
356 		} else if (tmp->ar_state == AC_OFF) {
357 			BT_CLEAR(maskp, id);
358 		} else {
359 			mutex_exit(&info->ac_lock);
360 			kmem_free(res, bufsz);
361 			return (EINVAL);
362 		}
363 		tmp++;
364 		counter++;
365 	}
366 	mutex_exit(&info->ac_lock);
367 	kmem_free(res, bufsz);
368 	return (0);
369 }
370 
371 static int
372 ac_res_get(ac_info_t *info, void *buf, size_t bufsz, int maxres)
373 {
374 	int error = 0;
375 	ac_res_t *res;
376 	ac_res_t *tmp;
377 	size_t ressz = sizeof (ac_res_t) * (maxres + 1);
378 	ulong_t *maskp;
379 	int id;
380 
381 	if (bufsz < ressz)
382 		return (EINVAL);
383 	tmp = res = kmem_alloc(ressz, KM_SLEEP);
384 
385 	mutex_enter(&info->ac_lock);
386 	maskp = (ulong_t *)&info->ac_mask;
387 	for (id = 1; id <= maxres; id++) {
388 		tmp->ar_id = id;
389 		tmp->ar_state = BT_TEST(maskp, id);
390 		tmp++;
391 	}
392 	tmp->ar_id = AC_NONE;
393 	tmp->ar_state = AC_OFF;
394 	mutex_exit(&info->ac_lock);
395 	error = copyout(res, buf, ressz);
396 	kmem_free(res, ressz);
397 	return (error);
398 }
399 
400 /*
401  * acctctl()
402  *
403  * Overview
404  *   acctctl() is the entry point for the acctctl(2) system call.
405  *
406  * Return values
407  *   On successful completion, return 0; otherwise -1 is returned and errno is
408  *   set appropriately.
409  *
410  * Caller's context
411  *   Called from the system call path.
412  */
413 int
414 acctctl(int cmd, void *buf, size_t bufsz)
415 {
416 	int error = 0;
417 	int mode = AC_MODE(cmd);
418 	int option = AC_OPTION(cmd);
419 	int maxres;
420 	ac_info_t *info;
421 	zone_t *zone = curproc->p_zone;
422 	struct exacct_globals *acg;
423 
424 	acg = zone_getspecific(exacct_zone_key, zone);
425 	/*
426 	 * exacct_zone_key and associated per-zone state were initialized when
427 	 * the module was loaded.
428 	 */
429 	ASSERT(exacct_zone_key != ZONE_KEY_UNINITIALIZED);
430 	ASSERT(acg != NULL);
431 
432 	switch (mode) {	/* sanity check */
433 	case AC_TASK:
434 		info = &acg->ac_task;
435 		maxres = AC_TASK_MAX_RES;
436 		break;
437 	case AC_PROC:
438 		info = &acg->ac_proc;
439 		maxres = AC_PROC_MAX_RES;
440 		break;
441 	case AC_FLOW:
442 		/*
443 		 * Flow accounting isn't currently configurable in non-global
444 		 * zones, but we have this field on a per-zone basis for future
445 		 * expansion as well as the ability to return default "unset"
446 		 * values for the various AC_*_GET queries.  AC_*_SET commands
447 		 * fail with EPERM for AC_FLOW in non-global zones.
448 		 */
449 		info = &acg->ac_flow;
450 		maxres = AC_FLOW_MAX_RES;
451 		break;
452 	default:
453 		return (set_errno(EINVAL));
454 	}
455 
456 	switch (option) {
457 	case AC_STATE_SET:
458 		if ((error = secpolicy_acct(CRED())) != 0)
459 			break;
460 		if (mode == AC_FLOW && getzoneid() != GLOBAL_ZONEID) {
461 			error = EPERM;
462 			break;
463 		}
464 		error = ac_state_set(info, buf, bufsz);
465 		break;
466 	case AC_STATE_GET:
467 		error = ac_state_get(info, buf, bufsz);
468 		break;
469 	case AC_FILE_SET:
470 		if ((error = secpolicy_acct(CRED())) != 0)
471 			break;
472 		if (mode == AC_FLOW && getzoneid() != GLOBAL_ZONEID) {
473 			error = EPERM;
474 			break;
475 		}
476 		error = ac_file_set(info, buf, bufsz);
477 		break;
478 	case AC_FILE_GET:
479 		error = ac_file_get(info, buf, bufsz);
480 		break;
481 	case AC_RES_SET:
482 		if ((error = secpolicy_acct(CRED())) != 0)
483 			break;
484 		if (mode == AC_FLOW && getzoneid() != GLOBAL_ZONEID) {
485 			error = EPERM;
486 			break;
487 		}
488 		error = ac_res_set(info, buf, bufsz, maxres);
489 		break;
490 	case AC_RES_GET:
491 		error = ac_res_get(info, buf, bufsz, maxres);
492 		break;
493 	default:
494 		return (set_errno(EINVAL));
495 	}
496 	if (error)
497 		return (set_errno(error));
498 	return (0);
499 }
500 
501 static struct sysent ac_sysent = {
502 	3,
503 	SE_NOUNLOAD | SE_ARGC | SE_32RVAL1,
504 	acctctl
505 };
506 
507 static struct modlsys modlsys = {
508 	&mod_syscallops,
509 	"acctctl system call",
510 	&ac_sysent
511 };
512 
513 #ifdef _SYSCALL32_IMPL
514 static struct modlsys modlsys32 = {
515 	&mod_syscallops32,
516 	"32-bit acctctl system call",
517 	&ac_sysent
518 };
519 #endif
520 
521 static struct modlinkage modlinkage = {
522 	MODREV_1,
523 	&modlsys,
524 #ifdef _SYSCALL32_IMPL
525 	&modlsys32,
526 #endif
527 	NULL
528 };
529 
530 /* ARGSUSED */
531 static void *
532 exacct_zone_init(zoneid_t zoneid)
533 {
534 	struct exacct_globals *acg;
535 
536 	acg = kmem_zalloc(sizeof (*acg), KM_SLEEP);
537 	mutex_enter(&exacct_globals_list_lock);
538 	list_insert_tail(&exacct_globals_list, acg);
539 	mutex_exit(&exacct_globals_list_lock);
540 	return (acg);
541 }
542 
543 static void
544 exacct_free_info(ac_info_t *info)
545 {
546 	mutex_enter(&info->ac_lock);
547 	if (info->ac_vnode) {
548 		(void) VOP_CLOSE(info->ac_vnode, FWRITE, 1, 0, kcred);
549 		VN_RELE(info->ac_vnode);
550 		kmem_free(info->ac_file, strlen(info->ac_file) + 1);
551 	}
552 	info->ac_state = AC_OFF;
553 	info->ac_vnode = NULL;
554 	info->ac_file = NULL;
555 	mutex_exit(&info->ac_lock);
556 }
557 
558 /* ARGSUSED */
559 static void
560 exacct_zone_shutdown(zoneid_t zoneid, void *data)
561 {
562 	struct exacct_globals *acg = data;
563 
564 	/*
565 	 * The accounting files need to be closed during shutdown rather than
566 	 * destroy, since otherwise the filesystem they reside on may fail to
567 	 * unmount, thus causing the entire zone halt/reboot to fail.
568 	 */
569 	exacct_free_info(&acg->ac_proc);
570 	exacct_free_info(&acg->ac_task);
571 	exacct_free_info(&acg->ac_flow);
572 }
573 
574 /* ARGSUSED */
575 static void
576 exacct_zone_fini(zoneid_t zoneid, void *data)
577 {
578 	struct exacct_globals *acg = data;
579 
580 	mutex_enter(&exacct_globals_list_lock);
581 	list_remove(&exacct_globals_list, acg);
582 	mutex_exit(&exacct_globals_list_lock);
583 
584 	mutex_destroy(&acg->ac_proc.ac_lock);
585 	mutex_destroy(&acg->ac_task.ac_lock);
586 	mutex_destroy(&acg->ac_flow.ac_lock);
587 	kmem_free(acg, sizeof (*acg));
588 }
589 
590 int
591 _init()
592 {
593 	int error;
594 
595 	mutex_init(&exacct_globals_list_lock, NULL, MUTEX_DEFAULT, NULL);
596 	list_create(&exacct_globals_list, sizeof (struct exacct_globals),
597 	    offsetof(struct exacct_globals, ac_link));
598 	zone_key_create(&exacct_zone_key, exacct_zone_init,
599 	    exacct_zone_shutdown, exacct_zone_fini);
600 
601 	if ((error = mod_install(&modlinkage)) != 0) {
602 		(void) zone_key_delete(exacct_zone_key);
603 		exacct_zone_key = ZONE_KEY_UNINITIALIZED;
604 		mutex_destroy(&exacct_globals_list_lock);
605 		list_destroy(&exacct_globals_list);
606 	}
607 	return (error);
608 }
609 
610 int
611 _info(struct modinfo *modinfop)
612 {
613 	return (mod_info(&modlinkage, modinfop));
614 }
615 
616 int
617 _fini()
618 {
619 	return (EBUSY);
620 }
621