/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (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 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * Copyright 2015 Nexenta Systems, Inc. All rights reserved. */ /* * kernel statistics driver */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static dev_info_t *kstat_devi; static int read_kstat_data(int *rvalp, void *user_ksp, int flag) { kstat_t user_kstat, *ksp; #ifdef _MULTI_DATAMODEL kstat32_t user_kstat32; #endif void *kbuf = NULL; size_t kbufsize, ubufsize, copysize; int error = 0; uint_t model; switch (model = ddi_model_convert_from(flag & FMODELS)) { #ifdef _MULTI_DATAMODEL case DDI_MODEL_ILP32: if (copyin(user_ksp, &user_kstat32, sizeof (kstat32_t)) != 0) return (EFAULT); user_kstat.ks_kid = user_kstat32.ks_kid; user_kstat.ks_data = (void *)(uintptr_t)user_kstat32.ks_data; user_kstat.ks_data_size = (size_t)user_kstat32.ks_data_size; break; #endif default: case DDI_MODEL_NONE: if (copyin(user_ksp, &user_kstat, sizeof (kstat_t)) != 0) return (EFAULT); } ksp = kstat_hold_bykid(user_kstat.ks_kid, getzoneid()); if (ksp == NULL) { /* * There is no kstat with the specified KID */ return (ENXIO); } if (ksp->ks_flags & KSTAT_FLAG_INVALID) { /* * The kstat exists, but is momentarily in some * indeterminate state (e.g. the data section is not * yet initialized). Try again in a few milliseconds. */ kstat_rele(ksp); return (EAGAIN); } /* * If it's a fixed-size kstat, allocate the buffer now, so we * don't have to do it under the kstat's data lock. (If it's a * var-size kstat or one with long strings, we don't know the size * until after the update routine is called, so we can't do this * optimization.) * The allocator relies on this behavior to prevent recursive * mutex_enter in its (fixed-size) kstat update routine. * It's a zalloc to prevent unintentional exposure of random * juicy morsels of (old) kernel data. */ if (!(ksp->ks_flags & (KSTAT_FLAG_VAR_SIZE | KSTAT_FLAG_LONGSTRINGS))) { kbufsize = ksp->ks_data_size; kbuf = kmem_zalloc(kbufsize + 1, KM_NOSLEEP); if (kbuf == NULL) { kstat_rele(ksp); return (EAGAIN); } } KSTAT_ENTER(ksp); if ((error = KSTAT_UPDATE(ksp, KSTAT_READ)) != 0) { KSTAT_EXIT(ksp); kstat_rele(ksp); if (kbuf != NULL) kmem_free(kbuf, kbufsize + 1); return (error); } kbufsize = ksp->ks_data_size; ubufsize = user_kstat.ks_data_size; if (ubufsize < kbufsize) { error = ENOMEM; } else { if (kbuf == NULL) kbuf = kmem_zalloc(kbufsize + 1, KM_NOSLEEP); if (kbuf == NULL) { error = EAGAIN; } else { error = KSTAT_SNAPSHOT(ksp, kbuf, KSTAT_READ); } } /* * The following info must be returned to user level, * even if the the update or snapshot failed. This allows * kstat readers to get a handle on variable-size kstats, * detect dormant kstats, etc. */ user_kstat.ks_ndata = ksp->ks_ndata; user_kstat.ks_data_size = kbufsize; user_kstat.ks_flags = ksp->ks_flags; user_kstat.ks_snaptime = ksp->ks_snaptime; *rvalp = kstat_chain_id; KSTAT_EXIT(ksp); kstat_rele(ksp); if (kbuf == NULL) goto out; /* * Copy the buffer containing the kstat back to userland. */ copysize = kbufsize; switch (model) { int i; #ifdef _MULTI_DATAMODEL kstat32_t *k32; kstat_t *k; case DDI_MODEL_ILP32: if (ksp->ks_type == KSTAT_TYPE_NAMED) { kstat_named_t *kn = kbuf; char *strbuf = (char *)((kstat_named_t *)kn + ksp->ks_ndata); for (i = 0; i < user_kstat.ks_ndata; kn++, i++) switch (kn->data_type) { /* * Named statistics have fields of type 'long'. * For a 32-bit application looking at a 64-bit * kernel, forcibly truncate these 64-bit * quantities to 32-bit values. */ case KSTAT_DATA_LONG: kn->value.i32 = (int32_t)kn->value.l; kn->data_type = KSTAT_DATA_INT32; break; case KSTAT_DATA_ULONG: kn->value.ui32 = (uint32_t)kn->value.ul; kn->data_type = KSTAT_DATA_UINT32; break; /* * Long strings must be massaged before being * copied out to userland. Do that here. */ case KSTAT_DATA_STRING: if (KSTAT_NAMED_STR_PTR(kn) == NULL) break; /* * If the string lies outside of kbuf * copy it there and update the pointer. */ if (KSTAT_NAMED_STR_PTR(kn) < (char *)kbuf || KSTAT_NAMED_STR_PTR(kn) + KSTAT_NAMED_STR_BUFLEN(kn) > (char *)kbuf + kbufsize + 1) { bcopy(KSTAT_NAMED_STR_PTR(kn), strbuf, KSTAT_NAMED_STR_BUFLEN(kn)); KSTAT_NAMED_STR_PTR(kn) = strbuf; strbuf += KSTAT_NAMED_STR_BUFLEN(kn); ASSERT(strbuf <= (char *)kbuf + kbufsize + 1); } /* * The offsets within the buffers are * the same, so add the offset to the * beginning of the new buffer to fix * the pointer. */ KSTAT_NAMED_STR_PTR(kn) = (char *)user_kstat.ks_data + (KSTAT_NAMED_STR_PTR(kn) - (char *)kbuf); /* * Make sure the string pointer lies * within the allocated buffer. */ ASSERT(KSTAT_NAMED_STR_PTR(kn) + KSTAT_NAMED_STR_BUFLEN(kn) <= ((char *)user_kstat.ks_data + ubufsize)); ASSERT(KSTAT_NAMED_STR_PTR(kn) >= (char *)((kstat_named_t *) user_kstat.ks_data + user_kstat.ks_ndata)); /* * Cast 64-bit ptr to 32-bit. */ kn->value.str.addr.ptr32 = (caddr32_t)(uintptr_t) KSTAT_NAMED_STR_PTR(kn); break; default: break; } } if (user_kstat.ks_kid != 0) break; /* * This is the special case of the kstat header * list for the entire system. Reshape the * array in place, then copy it out. */ k32 = kbuf; k = kbuf; for (i = 0; i < user_kstat.ks_ndata; k32++, k++, i++) { k32->ks_crtime = k->ks_crtime; k32->ks_next = 0; k32->ks_kid = k->ks_kid; (void) strcpy(k32->ks_module, k->ks_module); k32->ks_resv = k->ks_resv; k32->ks_instance = k->ks_instance; (void) strcpy(k32->ks_name, k->ks_name); k32->ks_type = k->ks_type; (void) strcpy(k32->ks_class, k->ks_class); k32->ks_flags = k->ks_flags; k32->ks_data = 0; k32->ks_ndata = k->ks_ndata; if (k->ks_data_size > UINT32_MAX) { error = EOVERFLOW; break; } k32->ks_data_size = (size32_t)k->ks_data_size; k32->ks_snaptime = k->ks_snaptime; } /* * XXX In this case we copy less data than is * claimed in the header. */ copysize = user_kstat.ks_ndata * sizeof (kstat32_t); break; #endif /* _MULTI_DATAMODEL */ default: case DDI_MODEL_NONE: if (ksp->ks_type == KSTAT_TYPE_NAMED) { kstat_named_t *kn = kbuf; char *strbuf = (char *)((kstat_named_t *)kn + ksp->ks_ndata); for (i = 0; i < user_kstat.ks_ndata; kn++, i++) switch (kn->data_type) { #ifdef _LP64 case KSTAT_DATA_LONG: kn->data_type = KSTAT_DATA_INT64; break; case KSTAT_DATA_ULONG: kn->data_type = KSTAT_DATA_UINT64; break; #endif /* _LP64 */ case KSTAT_DATA_STRING: if (KSTAT_NAMED_STR_PTR(kn) == NULL) break; /* * If the string lies outside of kbuf * copy it there and update the pointer. */ if (KSTAT_NAMED_STR_PTR(kn) < (char *)kbuf || KSTAT_NAMED_STR_PTR(kn) + KSTAT_NAMED_STR_BUFLEN(kn) > (char *)kbuf + kbufsize + 1) { bcopy(KSTAT_NAMED_STR_PTR(kn), strbuf, KSTAT_NAMED_STR_BUFLEN(kn)); KSTAT_NAMED_STR_PTR(kn) = strbuf; strbuf += KSTAT_NAMED_STR_BUFLEN(kn); ASSERT(strbuf <= (char *)kbuf + kbufsize + 1); } KSTAT_NAMED_STR_PTR(kn) = (char *)user_kstat.ks_data + (KSTAT_NAMED_STR_PTR(kn) - (char *)kbuf); ASSERT(KSTAT_NAMED_STR_PTR(kn) + KSTAT_NAMED_STR_BUFLEN(kn) <= ((char *)user_kstat.ks_data + ubufsize)); ASSERT(KSTAT_NAMED_STR_PTR(kn) >= (char *)((kstat_named_t *) user_kstat.ks_data + user_kstat.ks_ndata)); break; default: break; } } break; } if (error == 0 && copyout(kbuf, user_kstat.ks_data, copysize)) error = EFAULT; kmem_free(kbuf, kbufsize + 1); out: /* * We have modified the ks_ndata, ks_data_size, ks_flags, and * ks_snaptime fields of the user kstat; now copy it back to userland. */ switch (model) { #ifdef _MULTI_DATAMODEL case DDI_MODEL_ILP32: if (kbufsize > UINT32_MAX) { error = EOVERFLOW; break; } user_kstat32.ks_ndata = user_kstat.ks_ndata; user_kstat32.ks_data_size = (size32_t)kbufsize; user_kstat32.ks_flags = user_kstat.ks_flags; user_kstat32.ks_snaptime = user_kstat.ks_snaptime; if (copyout(&user_kstat32, user_ksp, sizeof (kstat32_t)) && error == 0) error = EFAULT; break; #endif default: case DDI_MODEL_NONE: if (copyout(&user_kstat, user_ksp, sizeof (kstat_t)) && error == 0) error = EFAULT; break; } return (error); } static int write_kstat_data(int *rvalp, void *user_ksp, int flag, cred_t *cred) { kstat_t user_kstat, *ksp; void *buf = NULL; size_t bufsize; int error = 0; if (secpolicy_sys_config(cred, B_FALSE) != 0) return (EPERM); switch (ddi_model_convert_from(flag & FMODELS)) { #ifdef _MULTI_DATAMODEL kstat32_t user_kstat32; case DDI_MODEL_ILP32: if (copyin(user_ksp, &user_kstat32, sizeof (kstat32_t))) return (EFAULT); /* * These are the only fields we actually look at. */ user_kstat.ks_kid = user_kstat32.ks_kid; user_kstat.ks_data = (void *)(uintptr_t)user_kstat32.ks_data; user_kstat.ks_data_size = (size_t)user_kstat32.ks_data_size; user_kstat.ks_ndata = user_kstat32.ks_ndata; break; #endif default: case DDI_MODEL_NONE: if (copyin(user_ksp, &user_kstat, sizeof (kstat_t))) return (EFAULT); } bufsize = user_kstat.ks_data_size; buf = kmem_alloc(bufsize + 1, KM_NOSLEEP); if (buf == NULL) return (EAGAIN); if (copyin(user_kstat.ks_data, buf, bufsize)) { kmem_free(buf, bufsize + 1); return (EFAULT); } ksp = kstat_hold_bykid(user_kstat.ks_kid, getzoneid()); if (ksp == NULL) { kmem_free(buf, bufsize + 1); return (ENXIO); } if (ksp->ks_flags & KSTAT_FLAG_INVALID) { kstat_rele(ksp); kmem_free(buf, bufsize + 1); return (EAGAIN); } if (!(ksp->ks_flags & KSTAT_FLAG_WRITABLE)) { kstat_rele(ksp); kmem_free(buf, bufsize + 1); return (EACCES); } /* * With KSTAT_FLAG_VAR_SIZE, one must call the kstat's update callback * routine to ensure ks_data_size is up to date. * In this case it makes sense to do it anyhow, as it will be shortly * followed by a KSTAT_SNAPSHOT(). */ KSTAT_ENTER(ksp); error = KSTAT_UPDATE(ksp, KSTAT_READ); if (error || user_kstat.ks_data_size != ksp->ks_data_size || user_kstat.ks_ndata != ksp->ks_ndata) { KSTAT_EXIT(ksp); kstat_rele(ksp); kmem_free(buf, bufsize + 1); return (error ? error : EINVAL); } /* * We have to ensure that we don't accidentally change the type of * existing kstat_named statistics when writing over them. * Since read_kstat_data() modifies some of the types on their way * out, we need to be sure to handle these types seperately. */ if (ksp->ks_type == KSTAT_TYPE_NAMED) { void *kbuf; kstat_named_t *kold; kstat_named_t *knew = buf; int i; #ifdef _MULTI_DATAMODEL int model = ddi_model_convert_from(flag & FMODELS); #endif /* * Since ksp->ks_data may be NULL, we need to take a snapshot * of the published data to look at the types. */ kbuf = kmem_alloc(bufsize + 1, KM_NOSLEEP); if (kbuf == NULL) { KSTAT_EXIT(ksp); kstat_rele(ksp); kmem_free(buf, bufsize + 1); return (EAGAIN); } error = KSTAT_SNAPSHOT(ksp, kbuf, KSTAT_READ); if (error) { KSTAT_EXIT(ksp); kstat_rele(ksp); kmem_free(kbuf, bufsize + 1); kmem_free(buf, bufsize + 1); return (error); } kold = kbuf; /* * read_kstat_data() changes the types of * KSTAT_DATA_LONG / KSTAT_DATA_ULONG, so we need to * make sure that these (modified) types are considered * valid. */ for (i = 0; i < ksp->ks_ndata; i++, kold++, knew++) { switch (kold->data_type) { #ifdef _MULTI_DATAMODEL case KSTAT_DATA_LONG: switch (model) { case DDI_MODEL_ILP32: if (knew->data_type == KSTAT_DATA_INT32) { knew->value.l = (long)knew->value.i32; knew->data_type = KSTAT_DATA_LONG; } break; default: case DDI_MODEL_NONE: #ifdef _LP64 if (knew->data_type == KSTAT_DATA_INT64) { knew->value.l = (long)knew->value.i64; knew->data_type = KSTAT_DATA_LONG; } #endif /* _LP64 */ break; } break; case KSTAT_DATA_ULONG: switch (model) { case DDI_MODEL_ILP32: if (knew->data_type == KSTAT_DATA_UINT32) { knew->value.ul = (ulong_t)knew->value.ui32; knew->data_type = KSTAT_DATA_ULONG; } break; default: case DDI_MODEL_NONE: #ifdef _LP64 if (knew->data_type == KSTAT_DATA_UINT64) { knew->value.ul = (ulong_t)knew->value.ui64; knew->data_type = KSTAT_DATA_ULONG; } #endif /* _LP64 */ break; } break; #endif /* _MULTI_DATAMODEL */ case KSTAT_DATA_STRING: if (knew->data_type != KSTAT_DATA_STRING) { KSTAT_EXIT(ksp); kstat_rele(ksp); kmem_free(kbuf, bufsize + 1); kmem_free(buf, bufsize + 1); return (EINVAL); } #ifdef _MULTI_DATAMODEL if (model == DDI_MODEL_ILP32) KSTAT_NAMED_STR_PTR(knew) = (char *)(uintptr_t) knew->value.str.addr.ptr32; #endif /* * Nothing special for NULL */ if (KSTAT_NAMED_STR_PTR(knew) == NULL) break; /* * Check to see that the pointers all point * to within the buffer and after the array * of kstat_named_t's. */ if (KSTAT_NAMED_STR_PTR(knew) < (char *) ((kstat_named_t *)user_kstat.ks_data + ksp->ks_ndata)) { KSTAT_EXIT(ksp); kstat_rele(ksp); kmem_free(kbuf, bufsize + 1); kmem_free(buf, bufsize + 1); return (EINVAL); } if (KSTAT_NAMED_STR_PTR(knew) + KSTAT_NAMED_STR_BUFLEN(knew) > ((char *)user_kstat.ks_data + ksp->ks_data_size)) { KSTAT_EXIT(ksp); kstat_rele(ksp); kmem_free(kbuf, bufsize + 1); kmem_free(buf, bufsize + 1); return (EINVAL); } /* * Update the pointers within the buffer */ KSTAT_NAMED_STR_PTR(knew) = (char *)buf + (KSTAT_NAMED_STR_PTR(knew) - (char *)user_kstat.ks_data); break; default: break; } } kold = kbuf; knew = buf; /* * Now make sure the types are what we expected them to be. */ for (i = 0; i < ksp->ks_ndata; i++, kold++, knew++) if (kold->data_type != knew->data_type) { KSTAT_EXIT(ksp); kstat_rele(ksp); kmem_free(kbuf, bufsize + 1); kmem_free(buf, bufsize + 1); return (EINVAL); } kmem_free(kbuf, bufsize + 1); } error = KSTAT_SNAPSHOT(ksp, buf, KSTAT_WRITE); if (!error) error = KSTAT_UPDATE(ksp, KSTAT_WRITE); *rvalp = kstat_chain_id; KSTAT_EXIT(ksp); kstat_rele(ksp); kmem_free(buf, bufsize + 1); return (error); } /*ARGSUSED*/ static int kstat_ioctl(dev_t dev, int cmd, intptr_t data, int flag, cred_t *cr, int *rvalp) { int rc = 0; switch (cmd) { case KSTAT_IOC_CHAIN_ID: *rvalp = kstat_chain_id; break; case KSTAT_IOC_READ: rc = read_kstat_data(rvalp, (void *)data, flag); break; case KSTAT_IOC_WRITE: rc = write_kstat_data(rvalp, (void *)data, flag, cr); break; default: /* invalid request */ rc = EINVAL; } return (rc); } /* ARGSUSED */ static int kstat_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result) { switch (infocmd) { case DDI_INFO_DEVT2DEVINFO: *result = kstat_devi; return (DDI_SUCCESS); case DDI_INFO_DEVT2INSTANCE: *result = NULL; return (DDI_SUCCESS); } return (DDI_FAILURE); } static int kstat_attach(dev_info_t *devi, ddi_attach_cmd_t cmd) { if (cmd != DDI_ATTACH) return (DDI_FAILURE); if (ddi_create_minor_node(devi, "kstat", S_IFCHR, 0, DDI_PSEUDO, 0) == DDI_FAILURE) { ddi_remove_minor_node(devi, NULL); return (DDI_FAILURE); } kstat_devi = devi; return (DDI_SUCCESS); } static int kstat_detach(dev_info_t *devi, ddi_detach_cmd_t cmd) { if (cmd != DDI_DETACH) return (DDI_FAILURE); ddi_remove_minor_node(devi, NULL); return (DDI_SUCCESS); } static struct cb_ops kstat_cb_ops = { nulldev, /* open */ nulldev, /* close */ nodev, /* strategy */ nodev, /* print */ nodev, /* dump */ nodev, /* read */ nodev, /* write */ kstat_ioctl, /* ioctl */ nodev, /* devmap */ nodev, /* mmap */ nodev, /* segmap */ nochpoll, /* poll */ ddi_prop_op, /* prop_op */ 0, /* streamtab */ D_NEW | D_MP /* Driver compatibility flag */ }; static struct dev_ops kstat_ops = { DEVO_REV, /* devo_rev, */ 0, /* refcnt */ kstat_info, /* get_dev_info */ nulldev, /* identify */ nulldev, /* probe */ kstat_attach, /* attach */ kstat_detach, /* detach */ nodev, /* reset */ &kstat_cb_ops, /* driver operations */ (struct bus_ops *)0, /* no bus operations */ NULL, /* power */ ddi_quiesce_not_needed, /* quiesce */ }; static struct modldrv modldrv = { &mod_driverops, "kernel statistics driver", &kstat_ops, }; static struct modlinkage modlinkage = { MODREV_1, &modldrv, NULL }; int _init(void) { return (mod_install(&modlinkage)); } int _fini(void) { return (mod_remove(&modlinkage)); } int _info(struct modinfo *modinfop) { return (mod_info(&modlinkage, modinfop)); }