diff options
author | stevel@tonic-gate <none@none> | 2005-06-14 00:00:00 -0700 |
---|---|---|
committer | stevel@tonic-gate <none@none> | 2005-06-14 00:00:00 -0700 |
commit | 7c478bd95313f5f23a4c958a745db2134aa03244 (patch) | |
tree | c871e58545497667cbb4b0a4f2daf204743e1fe7 /usr/src/uts/common/os/acct.c | |
download | illumos-joyent-7c478bd95313f5f23a4c958a745db2134aa03244.tar.gz |
OpenSolaris Launch
Diffstat (limited to 'usr/src/uts/common/os/acct.c')
-rw-r--r-- | usr/src/uts/common/os/acct.c | 436 |
1 files changed, 436 insertions, 0 deletions
diff --git a/usr/src/uts/common/os/acct.c b/usr/src/uts/common/os/acct.c new file mode 100644 index 0000000000..e086b505c1 --- /dev/null +++ b/usr/src/uts/common/os/acct.c @@ -0,0 +1,436 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License, Version 1.0 only + * (the "License"). You may not use this file except in compliance + * with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2004 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ +/* All Rights Reserved */ + + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <sys/types.h> +#include <sys/sysmacros.h> +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/acct.h> +#include <sys/cred.h> +#include <sys/user.h> +#include <sys/errno.h> +#include <sys/file.h> +#include <sys/vnode.h> +#include <sys/debug.h> +#include <sys/proc.h> +#include <sys/resource.h> +#include <sys/session.h> +#include <sys/modctl.h> +#include <sys/syscall.h> +#include <sys/policy.h> +#include <sys/list.h> +#include <sys/time.h> +#include <sys/msacct.h> +#include <sys/zone.h> + +/* + * Each zone has its own accounting settings (on or off) and associated + * file. The global zone is not special in this aspect; it will only + * generate records for processes that ran in the global zone. We could + * allow the global zone to record all activity on the system, but there + * would be no way of knowing the zone in which the processes executed. + * sysacct() is thus virtualized to only act on the caller's zone. + */ +struct acct_globals { + struct acct acctbuf; + kmutex_t aclock; + struct vnode *acctvp; + list_node_t aclink; +}; + +/* + * We need a list of all accounting settings for all zones, so we can + * accurately determine if a file is in use for accounting (possibly by + * another zone). + */ +static zone_key_t acct_zone_key; +static list_t acct_list; +kmutex_t acct_list_lock; + +static struct sysent acctsysent = { + 1, + SE_NOUNLOAD | SE_ARGC | SE_32RVAL1, + sysacct +}; + +static struct modlsys modlsys = { + &mod_syscallops, "acct(2) syscall", &acctsysent +}; + +#ifdef _SYSCALL32_IMPL +static struct modlsys modlsys32 = { + &mod_syscallops32, "32-bit acct(2) syscall", &acctsysent +}; +#endif + +static struct modlinkage modlinkage = { + MODREV_1, + &modlsys, +#ifdef _SYSCALL32_IMPL + &modlsys32, +#endif + NULL +}; + +/*ARGSUSED*/ +static void * +acct_init(zoneid_t zoneid) +{ + struct acct_globals *ag; + + ag = kmem_alloc(sizeof (*ag), KM_SLEEP); + bzero(&ag->acctbuf, sizeof (ag->acctbuf)); + mutex_init(&ag->aclock, NULL, MUTEX_DEFAULT, NULL); + ag->acctvp = NULL; + + mutex_enter(&acct_list_lock); + list_insert_tail(&acct_list, ag); + mutex_exit(&acct_list_lock); + return (ag); +} + +/* ARGSUSED */ +static void +acct_shutdown(zoneid_t zoneid, void *arg) +{ + struct acct_globals *ag = arg; + + mutex_enter(&ag->aclock); + if (ag->acctvp) { + /* + * This needs to be done as a shutdown callback, otherwise this + * held vnode may cause filesystems to be busy, and the zone + * shutdown operation to fail. + */ + (void) VOP_CLOSE(ag->acctvp, FWRITE, 1, (offset_t)0, kcred); + VN_RELE(ag->acctvp); + } + ag->acctvp = NULL; + mutex_exit(&ag->aclock); +} + +/*ARGSUSED*/ +static void +acct_fini(zoneid_t zoneid, void *arg) +{ + struct acct_globals *ag = arg; + + mutex_enter(&acct_list_lock); + list_remove(&acct_list, ag); + mutex_exit(&acct_list_lock); + + mutex_destroy(&ag->aclock); + kmem_free(ag, sizeof (*ag)); +} + +int +_init(void) +{ + int error; + + mutex_init(&acct_list_lock, NULL, MUTEX_DEFAULT, NULL); + list_create(&acct_list, sizeof (struct acct_globals), + offsetof(struct acct_globals, aclink)); + /* + * Using an initializer here wastes a bit of memory for zones that + * don't use accounting, but vastly simplifies the locking. + */ + zone_key_create(&acct_zone_key, acct_init, acct_shutdown, acct_fini); + if ((error = mod_install(&modlinkage)) != 0) { + (void) zone_key_delete(acct_zone_key); + list_destroy(&acct_list); + mutex_destroy(&acct_list_lock); + } + return (error); +} + +int +_info(struct modinfo *modinfop) +{ + return (mod_info(&modlinkage, modinfop)); +} + +/* + * acct() is a "weak stub" routine called from exit(). + * Once this module has been loaded, we refuse to allow + * it to unload - otherwise accounting would quietly + * cease. See 1211661. It's possible to make this module + * unloadable but it's substantially safer not to bother. + */ +int +_fini(void) +{ + return (EBUSY); +} + +/* + * See if vp is in use by the accounting system on any zone. This does a deep + * comparison of vnodes such that a file and a lofs "shadow" node of it will + * appear to be the same. + * + * If 'compare_vfs' is true, the function will do a comparison of vfs_t's + * instead (ie, is the vfs_t on which the vnode resides in use by the + * accounting system in any zone). + * + * Returns 1 if found (in use), 0 otherwise. + */ +static int +acct_find(vnode_t *vp, boolean_t compare_vfs) +{ + struct acct_globals *ag; + vnode_t *realvp; + + ASSERT(MUTEX_HELD(&acct_list_lock)); + ASSERT(vp != NULL); + + if (VOP_REALVP(vp, &realvp)) + realvp = vp; + for (ag = list_head(&acct_list); ag != NULL; + ag = list_next(&acct_list, ag)) { + vnode_t *racctvp; + boolean_t found = B_FALSE; + + mutex_enter(&ag->aclock); + if (ag->acctvp == NULL) { + mutex_exit(&ag->aclock); + continue; + } + if (VOP_REALVP(ag->acctvp, &racctvp)) + racctvp = ag->acctvp; + if (compare_vfs) { + if (racctvp->v_vfsp == realvp->v_vfsp) + found = B_TRUE; + } else { + if (VN_CMP(realvp, racctvp)) + found = B_TRUE; + } + mutex_exit(&ag->aclock); + if (found) + return (1); + } + return (0); +} + +/* + * Returns 1 if the vfs that vnode resides on is in use for the accounting + * subsystem, 0 otherwise. + */ +int +acct_fs_in_use(vnode_t *vp) +{ + int found; + + if (vp == NULL) + return (0); + mutex_enter(&acct_list_lock); + found = acct_find(vp, B_TRUE); + mutex_exit(&acct_list_lock); + return (found); +} + +/* + * Perform process accounting functions. + */ +int +sysacct(char *fname) +{ + struct acct_globals *ag; + struct vnode *vp; + int error = 0; + + if (secpolicy_acct(CRED()) != 0) + return (set_errno(EPERM)); + + ag = zone_getspecific(acct_zone_key, curproc->p_zone); + ASSERT(ag != NULL); + + if (fname == NULL) { + /* + * Close the file and stop accounting. + */ + mutex_enter(&ag->aclock); + vp = ag->acctvp; + ag->acctvp = NULL; + mutex_exit(&ag->aclock); + if (vp) { + error = VOP_CLOSE(vp, FWRITE, 1, (offset_t)0, CRED()); + VN_RELE(vp); + } + return (error == 0 ? 0 : set_errno(error)); + } + + /* + * Either (a) open a new file and begin accounting -or- (b) + * switch accounting from an old to a new file. + * + * (Open the file without holding aclock in case it + * sleeps (holding the lock prevents process exit).) + */ + if ((error = vn_open(fname, UIO_USERSPACE, FWRITE, + 0, &vp, (enum create)0, 0)) != 0) { + /* SVID compliance */ + if (error == EISDIR) + error = EACCES; + return (set_errno(error)); + } + + if (vp->v_type != VREG) { + error = EACCES; + } else { + mutex_enter(&acct_list_lock); + if (acct_find(vp, B_FALSE)) { + error = EBUSY; + } else { + mutex_enter(&ag->aclock); + if (ag->acctvp) { + vnode_t *oldvp; + + /* + * close old acctvp, and point acct() + * at new file by swapping vp and acctvp + */ + oldvp = ag->acctvp; + ag->acctvp = vp; + vp = oldvp; + } else { + /* + * no existing file, start accounting .. + */ + ag->acctvp = vp; + vp = NULL; + } + mutex_exit(&ag->aclock); + } + mutex_exit(&acct_list_lock); + } + + if (vp) { + (void) VOP_CLOSE(vp, FWRITE, 1, (offset_t)0, CRED()); + VN_RELE(vp); + } + return (error == 0 ? 0 : set_errno(error)); +} + +/* + * Produce a pseudo-floating point representation + * with 3 bits base-8 exponent, 13 bits fraction. + */ +static comp_t +acct_compress(ulong_t t) +{ + int exp = 0, round = 0; + + while (t >= 8192) { + exp++; + round = t & 04; + t >>= 3; + } + if (round) { + t++; + if (t >= 8192) { + t >>= 3; + exp++; + } + } +#ifdef _LP64 + if (exp > 7) { + /* prevent wraparound */ + t = 8191; + exp = 7; + } +#endif + return ((exp << 13) + t); +} + +/* + * On exit, write a record on the accounting file. + */ +void +acct(char st) +{ + struct vnode *vp; + struct cred *cr; + struct proc *p; + struct vattr va; + ssize_t resid = 0; + int error; + struct acct_globals *ag; + + ag = zone_getspecific(acct_zone_key, curproc->p_zone); + + mutex_enter(&ag->aclock); + if ((vp = ag->acctvp) == NULL) { + mutex_exit(&ag->aclock); + return; + } + + /* + * This only gets called from exit after all lwp's have exited so no + * cred locking is needed. + */ + p = curproc; + bcopy(u.u_comm, ag->acctbuf.ac_comm, sizeof (ag->acctbuf.ac_comm)); + ag->acctbuf.ac_btime = u.u_start.tv_sec; + ag->acctbuf.ac_utime = acct_compress( + (comp_t)NSEC_TO_TICK(p->p_acct[LMS_USER])); + ag->acctbuf.ac_stime = acct_compress( + (comp_t)NSEC_TO_TICK(p->p_acct[LMS_SYSTEM] + p->p_acct[LMS_TRAP])); + ag->acctbuf.ac_etime = acct_compress(lbolt - u.u_ticks); + ag->acctbuf.ac_mem = acct_compress((ulong_t)u.u_mem); + ag->acctbuf.ac_io = acct_compress((ulong_t)p->p_ru.ioch); + ag->acctbuf.ac_rw = acct_compress((ulong_t)(p->p_ru.inblock + + p->p_ru.oublock)); + cr = CRED(); + ag->acctbuf.ac_uid = crgetruid(cr); + ag->acctbuf.ac_gid = crgetrgid(cr); + (void) cmpldev(&ag->acctbuf.ac_tty, cttydev(p)); + ag->acctbuf.ac_stat = st; + ag->acctbuf.ac_flag = (u.u_acflag | AEXPND); + + /* + * Save the size. If the write fails, reset the size to avoid + * corrupted acct files. + * + * Large Files: We deliberately prevent accounting files from + * exceeding the 2GB limit as none of the accounting commands are + * currently large file aware. + */ + va.va_mask = AT_SIZE; + if (VOP_GETATTR(vp, &va, 0, kcred) == 0) { + error = vn_rdwr(UIO_WRITE, vp, (caddr_t)&ag->acctbuf, + sizeof (ag->acctbuf), 0LL, UIO_SYSSPACE, FAPPEND, + (rlim64_t)MAXOFF32_T, kcred, &resid); + if (error || resid) + (void) VOP_SETATTR(vp, &va, 0, kcred, NULL); + } + mutex_exit(&ag->aclock); +} |