/*
* 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
* 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 2010 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* negative cache handling for the /dev fs
*/
#include <sys/sysmacros.h>
#include <sys/devcache.h>
/*
* ncache is a negative cache of failed lookups. An entry
* is added after an attempt to configure a device by that
* name failed. An accumulation of these entries over time
* gives us a set of device name for which implicit reconfiguration
* does not need to be attempted. If a name is created matching
* an entry in ncache, that entry is removed, with the
* persistent store updated.
*
* Implicit reconfig is initiated for any name during lookup that
* can't be resolved from the backing store and that isn't
* present in the negative cache. This functionality is
* enabled during system startup once communication with devfsadm
* can be achieved. Since readdir is more general, implicit
* reconfig initiated by reading a directory isn't enabled until
* the system is more fully booted, at the time of the multi-user
* milestone, corresponding to init state 2.
*
* A maximum is imposed on the number of entries in the cache
* to limit some script going wild and as a defense against attack.
* The default limit is 64 and can be adjusted via sdev_nc_max_entries.
*
* Each entry also has a expiration count. When looked up a name in
* the cache is set to the default. Subsequent boots will decrement
* the count if a name isn't referenced. This permits a once-only
* entry to eventually be removed over time.
*
* sdev_reconfig_delay implements a "debounce" of the timing beyond
* system available indication, providing what the filesystem considers
* to be the system-is-fully-booted state. This is provided to adjust
* the timing if some application startup is performing a readdir
* in /dev that initiates a troublesome implicit reconfig on every boot.
*
* sdev_nc_disable_reset can be used to disable clearing the negative cache
* on reconfig boot. The default is to clear the cache on reconfig boot.
* sdev_nc_disable can be used to disable the negative cache itself.
*
* sdev_reconfig_disable can be used to disable implicit reconfig.
* The default is that implicit reconfig is enabled.
*/
/* tunables and defaults */
/* tunables */
int sdev_reconfig_verbose = 0;
int sdev_reconfig_disable = 0;
int sdev_nc_disable = 0;
int sdev_nc_disable_reset = 0;
int sdev_nc_verbose = 0;
int sdev_cache_read_disable = 0;
int sdev_cache_write_disable = 0;
/* globals */
int sdev_reconfig_boot = 0;
/* static prototypes */
static void sdev_ncache_write_complete(nvf_handle_t);
static void sdev_ncache_write(void);
static void sdev_ncache_process_store(void);
static sdev_nc_list_t *sdev_nc_newlist(void);
static void sdev_nc_free_unlinked_node(sdev_nc_node_t *);
static void sdev_nc_free_bootonly(void);
static void sdev_ncache_list_free(nvf_handle_t);
static void sdev_nvp_free(nvp_devname_t *);
/*
* Registration for /etc/devices/devname_cache
*/
"/etc/devices/devname_cache", /* path to cache */
sdev_ncache_unpack_nvlist, /* read: unpack nvlist */
sdev_ncache_pack_list, /* write: pack list */
sdev_ncache_list_free, /* free data list */
sdev_ncache_write_complete /* write complete callback */
};
/*
* called once at filesystem initialization
*/
void
sdev_ncache_init(void)
{
}
/*
* called at mount of the global instance
* currently the global instance is never unmounted
*/
void
sdev_ncache_setup(void)
{
if (!sdev_cache_read_disable) {
(void) nvf_read_file(sdevfd_handle);
}
}
static void
{
int i;
char **p;
if (dp->nvp_npaths > 0) {
for (i = 0; i < dp->nvp_npaths; i++, p++) {
}
dp->nvp_npaths * sizeof (char *));
dp->nvp_npaths * sizeof (int));
}
}
static void
{
}
}
/*
* Used to decode the nvlist format into the internal representation
* when reading /etc/devices/devname_cache.
* Note that the expiration counts are optional, for compatibility
* with earlier instances of the cache. If not present, the
* expire counts are initialized to defaults.
*/
static int
{
char **strs;
int *cnts;
int rval, i;
/* name of the sublist must match what we created */
return (-1);
}
if (rval) {
return (-1);
}
for (i = 0; i < nstrs; i++) {
}
for (i = 0; i < nstrs; i++) {
}
if (rval == 0) {
for (i = 0; i < nstrs; i++) {
}
}
return (0);
}
/*
* Pack internal format cache data to a single nvlist.
* Used when writing the nvlist file.
* Note this is called indirectly by the nvpflush daemon.
*/
static int
{
int rval;
if (rval != 0) {
nvf_error("%s: nvlist alloc error %d\n",
return (DDI_FAILURE);
}
if (rval != 0) {
nvf_error("%s: nvlist alloc error %d\n",
goto err;
}
if (rval != 0) {
nvf_error("%s: nvlist add error %d (sdev)\n",
goto err;
}
if (rval != 0) {
nvf_error("%s: nvlist add error %d (sdev)\n",
goto err;
}
if (rval != 0) {
nvf_error("%s: nvlist add error %d (sublist)\n",
goto err;
}
}
return (DDI_SUCCESS);
err:
return (DDI_FAILURE);
}
/*
* Run through the data read from the backing cache store
* to establish the initial state of the neg. cache.
*/
static void
{
char *path;
int i, n;
if (sdev_nc_disable)
return;
for (i = 0; i < np->nvp_npaths; i++) {
sdcmn_err5((" %s %d\n",
KM_SLEEP);
} else if (sdev_nc_verbose) {
"?%s: truncating from ncache (max %d)\n",
}
}
}
}
/*
* called by nvpflush daemon to inform us that an update of
* the cache file has been completed.
*/
static void
{
sdcmn_err5(("ncache write complete but dirty again\n"));
} else {
sdcmn_err5(("ncache write complete\n"));
}
}
/*
* Prepare to perform an update of the neg. cache backing store.
*/
static void
sdev_ncache_write(void)
{
int n, i;
if (sdev_cache_write_disable) {
return;
}
/* proper lock ordering here is essential */
n = ncl->ncl_nentries;
ASSERT(n <= sdev_nc_max_entries);
np->nvp_npaths = n;
i = 0;
sdcmn_err5((" %s %d\n",
i++;
}
}
static void
sdev_nc_flush_updates(void)
{
return;
(NCL_LIST_DIRTY | NCL_LIST_WENABLE))) {
} else {
}
}
static void
{
if (sdev_nc_disable || sdev_cache_write_disable ||
return;
}
} else {
}
}
static void
{
/*
* Once boot is complete, decrement the expire count of each entry
* in the cache not touched by a reference. Remove any that
* goes to zero. This effectively removes random entries over
* time.
*/
}
} else {
if (--lp->ncn_expirecnt == 0) {
ncl->ncl_nentries--;
}
}
}
}
}
/*
* Upon transition to the login state on a reconfigure boot,
* a debounce timer is set up so that we cache all the nonsense
* lookups we're hit with by the windowing system startup.
*/
/*ARGSUSED*/
static void
{
}
static void
{
int nsecs;
if (nsecs == 0) {
} else {
}
}
/*
* Called to inform the filesystem of progress during boot,
* either a notice of reconfiguration boot or an indication of
* system boot complete. At system boot complete, set up a
* timer at the expiration of which no further failed lookups
* will be added to the negative cache.
*
* The dev filesystem infers from reconfig boot that implicit
* reconfig need not be invoked at all as all available devices
* will have already been named.
*
* The dev filesystem infers from "system available" that devfsadmd
* can now be run and hence implicit reconfiguration may be initiated.
* During early stages of system startup, implicit reconfig is
* not done to avoid impacting boot performance.
*/
void
sdev_devstate_change(void)
{
int new_state;
/*
* Track system state and manage interesting transitions
*/
if (i_ddi_reconfig())
if (i_ddi_sysavail())
if (sdev_boot_state < new_state) {
switch (new_state) {
case SDEV_BOOT_STATE_RECONFIG:
sdcmn_err5(("state change: reconfigure boot\n"));
/*
* The /dev filesystem fills a hot-plug .vs.
* public-namespace gap by invoking 'devfsadm' once
* as a result of the first /dev lookup failure
* that a reconfig reboot did not have a hot-plug gap,
* but this is not true - the gap is just smaller:
* it exists from the the time the smf invocation of
* devfsadm completes its forced devinfo snapshot,
* to the time when the smf devfsadmd daemon invocation
* is set up and listening for hotplug sysevents.
* Since there is still a gap with reconfig reboot,
* we no longer set 'sdev_reconfig_boot'.
*/
if (!sdev_nc_disable_reset)
break;
case SDEV_BOOT_STATE_SYSAVAIL:
sdcmn_err5(("system available\n"));
break;
}
}
}
/*
* Lookup: filter out entries in the negative cache
* Return 1 if the lookup should not cause a reconfig.
*/
int
{
int n;
char *path;
int rval = 0;
int changed = 0;
if (sdev_nc_disable)
return (0);
sdcmn_err5(("%s/%s: lookup by %s cached, no reconfig\n",
if (sdev_nc_verbose) {
"?%s/%s: lookup by %s cached, no reconfig\n",
}
changed = 1;
}
rval = 1;
}
if (changed)
return (rval);
}
void
{
if (sdev_nc_disable)
return;
/*
* If we're still in the initial boot stage, always update
* the cache - we may not have received notice of the
* reconfig boot state yet. On a reconfigure boot, entries
* from the backing store are not re-persisted on update,
* but new entries are marked as needing an update.
* Never cache dynamic or non-global nodes.
*/
!SDEV_IS_NO_NCACHE(dv) &&
((failed_flags & SLF_NO_NCACHE) == 0) &&
((sdev_reconfig_boot &&
(sdev_boot_state != SDEV_BOOT_STATE_COMPLETE)) ||
}
}
static sdev_nc_list_t *
sdev_nc_newlist(void)
{
return (ncl);
}
static void
{
}
static sdev_nc_node_t *
{
return (lp);
}
return (NULL);
}
static void
{
"%s by %s: not adding to ncache (max %d)\n",
ncl->ncl_nentries));
if (sdev_nc_verbose) {
"not adding to ncache (max %d)\n",
ncl->ncl_nentries);
}
} else {
ncl->ncl_nentries++;
/* don't mark list dirty for nodes from store */
sdcmn_err5(("%s by %s: add to ncache\n",
if (sdev_nc_verbose) {
"?%s by %s: add to ncache\n",
}
}
}
} else {
}
}
void
{
int n;
}
void
{
/* dynamic and non-global nodes are never cached */
!SDEV_IS_NO_NCACHE(dv)) {
}
}
void
{
if (sdev_nc_disable)
return;
return;
}
}
if (lp) {
ncl->ncl_nentries--;
} else {
}
sdcmn_err5(("%s by %s: removed from ncache\n",
if (sdev_nc_verbose) {
}
} else
}
static void
sdev_nc_free_bootonly(void)
{
ncl->ncl_nentries--;
}
}
}