#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>
struct acct_globals {
struct acct acctbuf;
kmutex_t aclock;
struct vnode *acctvp;
list_node_t aclink;
};
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
};
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);
}
static void
acct_shutdown(zoneid_t zoneid, void *arg)
{
struct acct_globals *ag = arg;
mutex_enter(&ag->aclock);
if (ag->acctvp) {
(void) VOP_CLOSE(ag->acctvp, FWRITE, 1, (offset_t)0, kcred,
NULL);
VN_RELE(ag->acctvp);
}
ag->acctvp = NULL;
mutex_exit(&ag->aclock);
}
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));
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));
}
int
_fini(void)
{
return (EBUSY);
}
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, NULL))
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, NULL))
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);
}
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);
}
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) {
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(),
NULL);
VN_RELE(vp);
}
return (error == 0 ? 0 : set_errno(error));
}
if ((error = vn_open(fname, UIO_USERSPACE, FWRITE,
0, &vp, (enum create)0, 0)) != 0) {
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;
oldvp = ag->acctvp;
ag->acctvp = vp;
vp = oldvp;
} else {
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(), NULL);
VN_RELE(vp);
}
return (error == 0 ? 0 : set_errno(error));
}
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) {
t = 8191;
exp = 7;
}
#endif
return ((exp << 13) + t);
}
void
acct(char st)
{
struct vnode *vp;
struct cred *cr;
struct proc *p;
user_t *ua;
struct vattr va;
ssize_t resid = 0;
int error;
struct acct_globals *ag;
ag = zone_getspecific(acct_zone_key, curproc->p_zone);
if (ag == NULL)
return;
mutex_enter(&ag->aclock);
if ((vp = ag->acctvp) == NULL) {
mutex_exit(&ag->aclock);
return;
}
p = curproc;
ua = PTOU(p);
bcopy(ua->u_comm, ag->acctbuf.ac_comm, sizeof (ag->acctbuf.ac_comm));
ag->acctbuf.ac_btime = ua->u_start.tv_sec;
ag->acctbuf.ac_utime = acct_compress(NSEC_TO_TICK(p->p_acct[LMS_USER]));
ag->acctbuf.ac_stime = acct_compress(
NSEC_TO_TICK(p->p_acct[LMS_SYSTEM] + p->p_acct[LMS_TRAP]));
ag->acctbuf.ac_etime = acct_compress(ddi_get_lbolt() - ua->u_ticks);
ag->acctbuf.ac_mem = acct_compress((ulong_t)ua->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 = (ua->u_acflag | AEXPND);
va.va_mask = AT_SIZE;
if (VOP_GETATTR(vp, &va, 0, kcred, NULL) == 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);
}