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 2006 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ 28 /* All Rights Reserved */ 29 30 31 #pragma ident "%Z%%M% %I% %E% SMI" 32 33 #include <sys/types.h> 34 #include <sys/sysmacros.h> 35 #include <sys/param.h> 36 #include <sys/systm.h> 37 #include <sys/acct.h> 38 #include <sys/cred.h> 39 #include <sys/user.h> 40 #include <sys/errno.h> 41 #include <sys/file.h> 42 #include <sys/vnode.h> 43 #include <sys/debug.h> 44 #include <sys/proc.h> 45 #include <sys/resource.h> 46 #include <sys/session.h> 47 #include <sys/modctl.h> 48 #include <sys/syscall.h> 49 #include <sys/policy.h> 50 #include <sys/list.h> 51 #include <sys/time.h> 52 #include <sys/msacct.h> 53 #include <sys/zone.h> 54 55 /* 56 * Each zone has its own accounting settings (on or off) and associated 57 * file. The global zone is not special in this aspect; it will only 58 * generate records for processes that ran in the global zone. We could 59 * allow the global zone to record all activity on the system, but there 60 * would be no way of knowing the zone in which the processes executed. 61 * sysacct() is thus virtualized to only act on the caller's zone. 62 */ 63 struct acct_globals { 64 struct acct acctbuf; 65 kmutex_t aclock; 66 struct vnode *acctvp; 67 list_node_t aclink; 68 }; 69 70 /* 71 * We need a list of all accounting settings for all zones, so we can 72 * accurately determine if a file is in use for accounting (possibly by 73 * another zone). 74 */ 75 static zone_key_t acct_zone_key; 76 static list_t acct_list; 77 kmutex_t acct_list_lock; 78 79 static struct sysent acctsysent = { 80 1, 81 SE_NOUNLOAD | SE_ARGC | SE_32RVAL1, 82 sysacct 83 }; 84 85 static struct modlsys modlsys = { 86 &mod_syscallops, "acct(2) syscall", &acctsysent 87 }; 88 89 #ifdef _SYSCALL32_IMPL 90 static struct modlsys modlsys32 = { 91 &mod_syscallops32, "32-bit acct(2) syscall", &acctsysent 92 }; 93 #endif 94 95 static struct modlinkage modlinkage = { 96 MODREV_1, 97 &modlsys, 98 #ifdef _SYSCALL32_IMPL 99 &modlsys32, 100 #endif 101 NULL 102 }; 103 104 /*ARGSUSED*/ 105 static void * 106 acct_init(zoneid_t zoneid) 107 { 108 struct acct_globals *ag; 109 110 ag = kmem_alloc(sizeof (*ag), KM_SLEEP); 111 bzero(&ag->acctbuf, sizeof (ag->acctbuf)); 112 mutex_init(&ag->aclock, NULL, MUTEX_DEFAULT, NULL); 113 ag->acctvp = NULL; 114 115 mutex_enter(&acct_list_lock); 116 list_insert_tail(&acct_list, ag); 117 mutex_exit(&acct_list_lock); 118 return (ag); 119 } 120 121 /* ARGSUSED */ 122 static void 123 acct_shutdown(zoneid_t zoneid, void *arg) 124 { 125 struct acct_globals *ag = arg; 126 127 mutex_enter(&ag->aclock); 128 if (ag->acctvp) { 129 /* 130 * This needs to be done as a shutdown callback, otherwise this 131 * held vnode may cause filesystems to be busy, and the zone 132 * shutdown operation to fail. 133 */ 134 (void) VOP_CLOSE(ag->acctvp, FWRITE, 1, (offset_t)0, kcred); 135 VN_RELE(ag->acctvp); 136 } 137 ag->acctvp = NULL; 138 mutex_exit(&ag->aclock); 139 } 140 141 /*ARGSUSED*/ 142 static void 143 acct_fini(zoneid_t zoneid, void *arg) 144 { 145 struct acct_globals *ag = arg; 146 147 mutex_enter(&acct_list_lock); 148 list_remove(&acct_list, ag); 149 mutex_exit(&acct_list_lock); 150 151 mutex_destroy(&ag->aclock); 152 kmem_free(ag, sizeof (*ag)); 153 } 154 155 int 156 _init(void) 157 { 158 int error; 159 160 mutex_init(&acct_list_lock, NULL, MUTEX_DEFAULT, NULL); 161 list_create(&acct_list, sizeof (struct acct_globals), 162 offsetof(struct acct_globals, aclink)); 163 /* 164 * Using an initializer here wastes a bit of memory for zones that 165 * don't use accounting, but vastly simplifies the locking. 166 */ 167 zone_key_create(&acct_zone_key, acct_init, acct_shutdown, acct_fini); 168 if ((error = mod_install(&modlinkage)) != 0) { 169 (void) zone_key_delete(acct_zone_key); 170 list_destroy(&acct_list); 171 mutex_destroy(&acct_list_lock); 172 } 173 return (error); 174 } 175 176 int 177 _info(struct modinfo *modinfop) 178 { 179 return (mod_info(&modlinkage, modinfop)); 180 } 181 182 /* 183 * acct() is a "weak stub" routine called from exit(). 184 * Once this module has been loaded, we refuse to allow 185 * it to unload - otherwise accounting would quietly 186 * cease. See 1211661. It's possible to make this module 187 * unloadable but it's substantially safer not to bother. 188 */ 189 int 190 _fini(void) 191 { 192 return (EBUSY); 193 } 194 195 /* 196 * See if vp is in use by the accounting system on any zone. This does a deep 197 * comparison of vnodes such that a file and a lofs "shadow" node of it will 198 * appear to be the same. 199 * 200 * If 'compare_vfs' is true, the function will do a comparison of vfs_t's 201 * instead (ie, is the vfs_t on which the vnode resides in use by the 202 * accounting system in any zone). 203 * 204 * Returns 1 if found (in use), 0 otherwise. 205 */ 206 static int 207 acct_find(vnode_t *vp, boolean_t compare_vfs) 208 { 209 struct acct_globals *ag; 210 vnode_t *realvp; 211 212 ASSERT(MUTEX_HELD(&acct_list_lock)); 213 ASSERT(vp != NULL); 214 215 if (VOP_REALVP(vp, &realvp)) 216 realvp = vp; 217 for (ag = list_head(&acct_list); ag != NULL; 218 ag = list_next(&acct_list, ag)) { 219 vnode_t *racctvp; 220 boolean_t found = B_FALSE; 221 222 mutex_enter(&ag->aclock); 223 if (ag->acctvp == NULL) { 224 mutex_exit(&ag->aclock); 225 continue; 226 } 227 if (VOP_REALVP(ag->acctvp, &racctvp)) 228 racctvp = ag->acctvp; 229 if (compare_vfs) { 230 if (racctvp->v_vfsp == realvp->v_vfsp) 231 found = B_TRUE; 232 } else { 233 if (VN_CMP(realvp, racctvp)) 234 found = B_TRUE; 235 } 236 mutex_exit(&ag->aclock); 237 if (found) 238 return (1); 239 } 240 return (0); 241 } 242 243 /* 244 * Returns 1 if the vfs that vnode resides on is in use for the accounting 245 * subsystem, 0 otherwise. 246 */ 247 int 248 acct_fs_in_use(vnode_t *vp) 249 { 250 int found; 251 252 if (vp == NULL) 253 return (0); 254 mutex_enter(&acct_list_lock); 255 found = acct_find(vp, B_TRUE); 256 mutex_exit(&acct_list_lock); 257 return (found); 258 } 259 260 /* 261 * Perform process accounting functions. 262 */ 263 int 264 sysacct(char *fname) 265 { 266 struct acct_globals *ag; 267 struct vnode *vp; 268 int error = 0; 269 270 if (secpolicy_acct(CRED()) != 0) 271 return (set_errno(EPERM)); 272 273 ag = zone_getspecific(acct_zone_key, curproc->p_zone); 274 ASSERT(ag != NULL); 275 276 if (fname == NULL) { 277 /* 278 * Close the file and stop accounting. 279 */ 280 mutex_enter(&ag->aclock); 281 vp = ag->acctvp; 282 ag->acctvp = NULL; 283 mutex_exit(&ag->aclock); 284 if (vp) { 285 error = VOP_CLOSE(vp, FWRITE, 1, (offset_t)0, CRED()); 286 VN_RELE(vp); 287 } 288 return (error == 0 ? 0 : set_errno(error)); 289 } 290 291 /* 292 * Either (a) open a new file and begin accounting -or- (b) 293 * switch accounting from an old to a new file. 294 * 295 * (Open the file without holding aclock in case it 296 * sleeps (holding the lock prevents process exit).) 297 */ 298 if ((error = vn_open(fname, UIO_USERSPACE, FWRITE, 299 0, &vp, (enum create)0, 0)) != 0) { 300 /* SVID compliance */ 301 if (error == EISDIR) 302 error = EACCES; 303 return (set_errno(error)); 304 } 305 306 if (vp->v_type != VREG) { 307 error = EACCES; 308 } else { 309 mutex_enter(&acct_list_lock); 310 if (acct_find(vp, B_FALSE)) { 311 error = EBUSY; 312 } else { 313 mutex_enter(&ag->aclock); 314 if (ag->acctvp) { 315 vnode_t *oldvp; 316 317 /* 318 * close old acctvp, and point acct() 319 * at new file by swapping vp and acctvp 320 */ 321 oldvp = ag->acctvp; 322 ag->acctvp = vp; 323 vp = oldvp; 324 } else { 325 /* 326 * no existing file, start accounting .. 327 */ 328 ag->acctvp = vp; 329 vp = NULL; 330 } 331 mutex_exit(&ag->aclock); 332 } 333 mutex_exit(&acct_list_lock); 334 } 335 336 if (vp) { 337 (void) VOP_CLOSE(vp, FWRITE, 1, (offset_t)0, CRED()); 338 VN_RELE(vp); 339 } 340 return (error == 0 ? 0 : set_errno(error)); 341 } 342 343 /* 344 * Produce a pseudo-floating point representation 345 * with 3 bits base-8 exponent, 13 bits fraction. 346 */ 347 static comp_t 348 acct_compress(ulong_t t) 349 { 350 int exp = 0, round = 0; 351 352 while (t >= 8192) { 353 exp++; 354 round = t & 04; 355 t >>= 3; 356 } 357 if (round) { 358 t++; 359 if (t >= 8192) { 360 t >>= 3; 361 exp++; 362 } 363 } 364 #ifdef _LP64 365 if (exp > 7) { 366 /* prevent wraparound */ 367 t = 8191; 368 exp = 7; 369 } 370 #endif 371 return ((exp << 13) + t); 372 } 373 374 /* 375 * On exit, write a record on the accounting file. 376 */ 377 void 378 acct(char st) 379 { 380 struct vnode *vp; 381 struct cred *cr; 382 struct proc *p; 383 struct vattr va; 384 ssize_t resid = 0; 385 int error; 386 struct acct_globals *ag; 387 388 ag = zone_getspecific(acct_zone_key, curproc->p_zone); 389 390 mutex_enter(&ag->aclock); 391 if ((vp = ag->acctvp) == NULL) { 392 mutex_exit(&ag->aclock); 393 return; 394 } 395 396 /* 397 * This only gets called from exit after all lwp's have exited so no 398 * cred locking is needed. 399 */ 400 p = curproc; 401 bcopy(u.u_comm, ag->acctbuf.ac_comm, sizeof (ag->acctbuf.ac_comm)); 402 ag->acctbuf.ac_btime = u.u_start.tv_sec; 403 ag->acctbuf.ac_utime = acct_compress(NSEC_TO_TICK(p->p_acct[LMS_USER])); 404 ag->acctbuf.ac_stime = acct_compress( 405 NSEC_TO_TICK(p->p_acct[LMS_SYSTEM] + p->p_acct[LMS_TRAP])); 406 ag->acctbuf.ac_etime = acct_compress(lbolt - u.u_ticks); 407 ag->acctbuf.ac_mem = acct_compress((ulong_t)u.u_mem); 408 ag->acctbuf.ac_io = acct_compress((ulong_t)p->p_ru.ioch); 409 ag->acctbuf.ac_rw = acct_compress((ulong_t)(p->p_ru.inblock + 410 p->p_ru.oublock)); 411 cr = CRED(); 412 ag->acctbuf.ac_uid = crgetruid(cr); 413 ag->acctbuf.ac_gid = crgetrgid(cr); 414 (void) cmpldev(&ag->acctbuf.ac_tty, cttydev(p)); 415 ag->acctbuf.ac_stat = st; 416 ag->acctbuf.ac_flag = (u.u_acflag | AEXPND); 417 418 /* 419 * Save the size. If the write fails, reset the size to avoid 420 * corrupted acct files. 421 * 422 * Large Files: We deliberately prevent accounting files from 423 * exceeding the 2GB limit as none of the accounting commands are 424 * currently large file aware. 425 */ 426 va.va_mask = AT_SIZE; 427 if (VOP_GETATTR(vp, &va, 0, kcred) == 0) { 428 error = vn_rdwr(UIO_WRITE, vp, (caddr_t)&ag->acctbuf, 429 sizeof (ag->acctbuf), 0LL, UIO_SYSSPACE, FAPPEND, 430 (rlim64_t)MAXOFF32_T, kcred, &resid); 431 if (error || resid) 432 (void) VOP_SETATTR(vp, &va, 0, kcred, NULL); 433 } 434 mutex_exit(&ag->aclock); 435 } 436