dsr.c revision 7c478bd95313f5f23a4c958a745db2134aa03244
/*
* 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.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include <sys/stat.h>
#include <sys/types.h>
/*
* Dependent on types.h, but not including it...
*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/dkio.h>
#include <sys/dktp/fdisk.h>
#include <sys/mnttab.h>
#include <sys/mntent.h>
#include <sys/sysmacros.h>
#include <sys/mkdev.h>
#include <sys/vfs.h>
#include <nfs/nfs.h>
#include <nfs/nfs_clnt.h>
#include <kstat.h>
#include <ctype.h>
#include <dirent.h>
#include <libdevinfo.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <devid.h>
#include "dsr.h"
#include "statcommon.h"
static void rummage_dev(ldinfo_t *);
static void do_snm(char *, char *);
static int look_up_name(const char *, disk_list_t *);
static disk_list_t *make_an_entry(char *, char *,
char *, dir_info_t *, int, ldinfo_t *);
static char *trim(char *, char *, int);
static ldinfo_t *rummage_devinfo(void);
static void pline(char *, int, char *, char *, ldinfo_t **);
static void insert_dlist_ent(disk_list_t *, disk_list_t **);
static int str_is_digit(char *);
static ldinfo_t *find_ldinfo_match(char *, ldinfo_t *);
static void insert_into_dlist(dir_info_t *, disk_list_t *);
static void cleanup_dlist(dir_info_t *);
static void cleanup_ldinfo(ldinfo_t *);
static int devinfo_ident_disks(di_node_t, void *);
static int devinfo_ident_tapes(di_node_t, void *);
static void process_dir_ent(char *dent, int curr_type,
char *last_snm, dir_info_t *, ldinfo_t *);
static char *get_nfs_by_minor(uint_t);
static char *cur_hostname(uint_t, kstat_ctl_t *);
static char *cur_special(char *, char *);
extern kstat_ctl_t *kc;
extern mnt_t *nfs;
/*
* To do: add VXVM support: /dev/vx/dsk and ap support: /dev/ap/
*
* Note: Adding support for VxVM is *not* as simple as adding another
* entry in the table and magically getting to see stuff related to
* VxVM. The structure is radically different *AND* they don't produce
* any IO kstats.
*/
#define OSA_DISK 0
#define DISK 1
#define MD_DISK 2
#define TAPE 3
#define MAX_TYPES 4
#define OSA_DISK_PATH "/dev/osa/dev/dsk"
#define MD_DISK_PATH "/dev/md/dsk"
#define DISK_PATH "/dev/dsk"
#define TAPE_PATH "/dev/rmt"
#define BASE_TRIM "../../devices"
#define MD_TRIM "../../../devices"
#define COLON ':'
#define COMMA ','
#define NAME_BUFLEN 256
static dir_info_t dlist[MAX_TYPES] = {
OSA_DISK_PATH, 0, 0, 0, 0, "sd", BASE_TRIM, COLON,
DISK_PATH, 0, 0, 0, 0, "sd", BASE_TRIM, COLON,
MD_DISK_PATH, 0, 0, 0, 1, "md", MD_TRIM, COMMA,
TAPE_PATH, 0, 0, 0, 0, "st", BASE_TRIM, COLON,
};
/*
* Build a list of disks attached to the system.
*/
static void
build_disk_list(void)
{
ldinfo_t *ptoi;
/*
* Build the list of devices connected to the system.
*/
ptoi = rummage_devinfo();
rummage_dev(ptoi);
cleanup_ldinfo(ptoi);
}
/*
* Walk the /dev/dsk and /dev/rmt directories building a
* list of interesting devices. Interesting is everything in the
* /dev/dsk directory. We skip some of the stuff in the /dev/rmt
* directory.
*
* Note that not finding one or more of the directories is not an
* error.
*/
static void
rummage_dev(ldinfo_t *ptoi)
{
DIR *dskp;
int i;
struct stat buf;
for (i = 0; i < MAX_TYPES; i++) {
if (stat(dlist[i].name, &buf) == 0) {
if (dlist[i].mtime != buf.st_mtime) {
/*
* We've found a change. We need to cleanup
* old information and then rebuild the list
* for this device type.
*/
cleanup_dlist(&dlist[i]);
dlist[i].mtime = buf.st_mtime;
if ((dskp = opendir(dlist[i].name))) {
struct dirent *bpt;
char last_snm[NAME_BUFLEN];
last_snm[0] = NULL;
while ((bpt = readdir(dskp)) != NULL) {
if (bpt->d_name[0] != '.') {
process_dir_ent(
bpt->d_name,
i, last_snm,
&dlist[i],
ptoi);
}
}
}
(void) closedir(dskp);
}
}
}
}
/*
* Walk the list of located devices and see if we've
* seen this device before. We look at the short name.
*/
static int
look_up_name(const char *nm, disk_list_t *list)
{
while (list) {
if (strcmp(list->dsk, nm) != 0)
list = list->next;
else {
return (1);
}
}
return (0);
}
/*
* Take a name of the form cNtNdNsN or cNtNdNpN
* or /dev/dsk/CNtNdNsN or /dev/dsk/cNtNdNpN
* remove the trailing sN or pN. Simply looking
* for the first 's' or 'p' doesn't cut it.
*/
static void
do_snm(char *orig, char *shortnm)
{
char *tmp;
char *ptmp;
int done = 0;
char repl_char = 0;
tmp = strrchr(orig, 's');
if (tmp) {
ptmp = tmp;
ptmp++;
done = str_is_digit(ptmp);
}
if (done == 0) {
/*
* The string either has no 's' in it
* or the stuff trailing the s has a
* non-numeric in it. Look to see if
* we have an ending 'p' followed by
* numerics.
*/
tmp = strrchr(orig, 'p');
if (tmp) {
ptmp = tmp;
ptmp++;
if (str_is_digit(ptmp))
repl_char = 'p';
else
tmp = 0;
}
} else {
repl_char = 's';
}
if (tmp)
*tmp = '\0';
(void) strcpy(shortnm, orig);
if (repl_char)
*tmp = repl_char;
}
/*
* Create and insert an entry into the device list.
*/
static disk_list_t *
make_an_entry(char *lname, char *shortnm, char *longnm,
dir_info_t *drent, int devtype, ldinfo_t *ptoi)
{
disk_list_t *entry;
char *nlnm;
char snm[NAME_BUFLEN];
ldinfo_t *p;
entry = safe_alloc(sizeof (disk_list_t));
nlnm = trim(lname, drent->trimstr, drent->trimchr);
entry->dsk = safe_strdup(shortnm);
do_snm(longnm, snm);
entry->dname = safe_strdup(snm);
entry->devtype = devtype;
entry->devidstr = NULL;
if ((p = find_ldinfo_match(nlnm, ptoi))) {
entry->dnum = p->dnum;
entry->dtype = safe_strdup(p->dtype);
if (p->devidstr)
entry->devidstr = safe_strdup(p->devidstr);
} else {
entry->dtype = safe_strdup(drent->dtype);
entry->dnum = -1;
if (drent->dtype) {
if (strcmp(drent->dtype, "md") == 0) {
(void) sscanf(shortnm, "d%d", &entry->dnum);
}
}
}
entry->seen = 0;
entry->next = 0;
insert_dlist_ent(entry, &drent->list);
return (entry);
}
/*
* slice stuff off beginning and end of /devices directory names derived from
* device links.
*/
static char *
trim(char *fnm, char *lname, int rchr)
{
char *ptr;
while (*lname == *fnm) {
lname++;
fnm++;
}
if ((ptr = strrchr(fnm, rchr)))
*ptr = NULL;
return (fnm);
}
/*
* Find an entry matching the name passed in
*/
static ldinfo_t *
find_ldinfo_match(char *name, ldinfo_t *ptoi)
{
if (name) {
while (ptoi) {
if (strcmp(ptoi->name, name))
ptoi = ptoi->next;
else
return (ptoi);
}
}
return (NULL);
}
/*
* Determine if a name is already in the list of disks. If not, insert the
* name in the list.
*/
static void
insert_dlist_ent(disk_list_t *n, disk_list_t **hd)
{
disk_list_t *tmp_ptr;
if (n->dtype != NULL) {
tmp_ptr = *hd;
while (tmp_ptr) {
if (strcmp(n->dsk, tmp_ptr->dsk) != 0)
tmp_ptr = tmp_ptr->next;
else
break;
}
if (tmp_ptr == NULL) {
/*
* We don't do anything with MD_DISK types here
* since they don't have partitions.
*/
if (n->devtype == DISK || n->devtype == OSA_DISK) {
n->flags = SLICES_OK;
#if defined(i386) || defined(__ia64)
n->flags |= PARTITIONS_OK;
#endif
} else {
n->flags = 0;
}
/*
* Figure out where to insert the name. The list is
* ostensibly in sorted order.
*/
if (*hd) {
disk_list_t *follw;
int mv;
tmp_ptr = *hd;
/*
* Look through the list. While the strcmp
* value is less than the current value,
*/
while (tmp_ptr) {
if ((mv = strcmp(n->dtype,
tmp_ptr->dtype)) < 0) {
follw = tmp_ptr;
tmp_ptr = tmp_ptr->next;
} else
break;
}
if (mv == 0) {
/*
* We're now in the area where the
* leading chars of the kstat name
* match. We need to insert in numeric
* order after that.
*/
while (tmp_ptr) {
if (strcmp(n->dtype,
tmp_ptr->dtype) != 0)
break;
if (n->dnum > tmp_ptr->dnum) {
follw = tmp_ptr;
tmp_ptr = tmp_ptr->next;
} else
break;
}
}
/*
* We should now be ready to insert an
* entry...
*/
if (mv >= 0) {
if (tmp_ptr == *hd) {
n->next = tmp_ptr;
*hd = n;
} else {
n->next = follw->next;
follw->next = n;
}
} else {
/*
* insert at the end of the
* list
*/
follw->next = n;
n->next = 0;
}
} else {
*hd = n;
n->next = 0;
}
}
}
}
/*
* find an entry matching the given kstat name in the list
* of disks, tapes and metadevices.
*/
disk_list_t *
lookup_ks_name(char *dev_nm)
{
int tried = 0;
int dv;
int len;
char cmpbuf[PATH_MAX + 1];
struct list_of_disks *list;
char *nm;
dev_name_t *tmp;
uint_t i;
/*
* extract the device type from the kstat name. We expect the
* name to be one or more alphabetics followed by the device
* numeric id. We do this solely for speed purposes .
*/
len = 0;
nm = dev_nm;
while (*nm) {
if (isalpha(*nm)) {
nm++;
len++;
} else
break;
}
if (!*nm)
return (NULL);
/*
* For each of the elements in the dlist array we keep
* an array of pointers to chains for each of the kstat
* prefixes found within that directory. This is typically
* 'sd' and 'ssd'. We walk the list in the directory and
* match on that type. Since the same prefixes can be
* in multiple places we keep checking if we don't find
* it in the first place.
*/
(void) strncpy(cmpbuf, dev_nm, len);
cmpbuf[len] = NULL;
dv = atoi(nm);
retry:
for (i = 0; i < MAX_TYPES; i++) {
tmp = dlist[i].nf;
while (tmp) {
if (strcmp(tmp->name, cmpbuf) == 0) {
/*
* As an optimization we keep mins
* and maxes for the devices found.
* This helps chop the lists up and
* avoid some really long chains as
* we would get if we kept only prefix
* lists.
*/
if (dv >= tmp->min && dv <= tmp->max) {
list = tmp->list_start;
while (list) {
if (list->dnum < dv)
list = list->next;
else
break;
}
if (list && list->dnum == dv) {
return (list);
}
}
}
tmp = tmp->next;
}
}
if (!tried) {
tried = 1;
build_disk_list();
goto retry;
}
return (0);
}
static int
str_is_digit(char *str)
{
while (*str) {
if (isdigit(*str))
str++;
else
return (0);
}
return (1);
}
static void
insert_into_dlist(dir_info_t *d, disk_list_t *e)
{
dev_name_t *tmp;
tmp = d->nf;
while (tmp) {
if (strcmp(e->dtype, tmp->name) != 0) {
tmp = tmp->next;
} else {
if (e->dnum < tmp->min) {
tmp->min = e->dnum;
tmp->list_start = e;
} else if (e->dnum > tmp->max) {
tmp->max = e->dnum;
tmp->list_end = e;
}
break;
}
}
if (tmp == NULL) {
tmp = safe_alloc(sizeof (dev_name_t));
tmp->name = e->dtype;
tmp->min = e->dnum;
tmp->max = e->dnum;
tmp->list_start = e;
tmp->list_end = e;
tmp->next = d->nf;
d->nf = tmp;
}
}
/*
* devinfo_ident_disks() and devinfo_ident_tapes() are the callback functions we
* use while walking the device tree snapshot provided by devinfo. If
* devinfo_ident_disks() identifies that the device being considered has one or
* more minor nodes _and_ is a block device, then it is a potential disk.
* Similarly for devinfo_ident_tapes(), except that the second criterion is that
* the minor_node be a character device. (This is more inclusive than only
* tape devices, but will match any entries in /dev/rmt/.)
*
* Note: if a driver was previously loaded but is now unloaded, the kstat may
* still be around (e.g., st) but no information will be found in the
* libdevinfo tree.
*/
static int
devinfo_ident_disks(di_node_t node, void *arg)
{
di_minor_t minor = DI_MINOR_NIL;
if ((minor = di_minor_next(node, minor)) != DI_MINOR_NIL) {
int spectype = di_minor_spectype(minor);
if (S_ISBLK(spectype)) {
char *physical_path = di_devfs_path(node);
int instance = di_instance(node);
char *driver_name = di_driver_name(node);
char *devidstr;
/* lookup the devid, devt specific first */
if ((di_prop_lookup_strings(di_minor_devt(minor), node,
DEVID_PROP_NAME, &devidstr) == -1) &&
(di_prop_lookup_strings(DDI_DEV_T_ANY, node,
DEVID_PROP_NAME, &devidstr) == -1))
devidstr = NULL;
if (driver_name == NULL)
driver_name = "<nil>";
pline(physical_path, instance,
driver_name, devidstr, arg);
di_devfs_path_free(physical_path);
}
}
return (DI_WALK_CONTINUE);
}
static int
devinfo_ident_tapes(di_node_t node, void *arg)
{
di_minor_t minor = DI_MINOR_NIL;
if ((minor = di_minor_next(node, minor)) != DI_MINOR_NIL) {
int spectype = di_minor_spectype(minor);
if (S_ISCHR(spectype)) {
char *physical_path = di_devfs_path(node);
int instance = di_instance(node);
char *binding_name = di_binding_name(node);
pline(physical_path, instance,
binding_name, NULL, arg);
di_devfs_path_free(physical_path);
}
}
return (DI_WALK_CONTINUE);
}
/*
* rummage_devinfo() is the driver routine that walks the devinfo snapshot.
*/
static ldinfo_t *
rummage_devinfo(void)
{
di_node_t root_node;
ldinfo_t *rv = NULL;
if ((root_node = di_init("/", DINFOCPYALL)) != DI_NODE_NIL) {
(void) di_walk_node(root_node, DI_WALK_CLDFIRST, (void *)&rv,
devinfo_ident_disks);
(void) di_walk_node(root_node, DI_WALK_CLDFIRST, (void *)&rv,
devinfo_ident_tapes);
di_fini(root_node);
}
return (rv);
}
/*
* pline() performs the lookup of the device path in the current list of disks,
* and adds the appropriate information to the nms list in the case of a match.
*/
static void
pline(char *devfs_path, int instance,
char *driver_name, char *devidstr, ldinfo_t **list)
{
ldinfo_t *entry;
entry = safe_alloc(sizeof (ldinfo_t));
entry->dnum = instance;
entry->name = safe_strdup(devfs_path);
entry->dtype = safe_strdup(driver_name);
entry->devidstr = safe_strdup(devidstr);
entry->next = *list;
*list = entry;
}
/*
* Cleanup space allocated in dlist processing.
* We're only interested in cleaning up the list and nf
* fields in the structure. Everything else is static
* data.
*/
static void
cleanup_dlist(dir_info_t *d)
{
dev_name_t *tmp;
dev_name_t *t1;
disk_list_t *t2;
disk_list_t *t3;
/*
* All of the entries in a dev_name_t use information
* from a disk_list_t structure that is freed later.
* All we need do here is free the dev_name_t
* structure itself.
*/
tmp = d->nf;
while (tmp) {
t1 = tmp->next;
free(tmp);
tmp = t1;
}
d->nf = 0;
/*
* "Later". Free the disk_list_t structures and their
* data attached to this portion of the dir_info
* structure.
*/
t2 = d->list;
while (t2) {
if (t2->dtype) {
free(t2->dtype);
t2->dtype = NULL;
}
if (t2->dsk) {
free(t2->dsk);
t2->dsk = NULL;
}
if (t2->dname) {
free(t2->dname);
t2->dname = NULL;
}
t3 = t2->next;
free(t2);
t2 = t3;
}
d->list = 0;
}
static void
process_dir_ent(char *dent, int curr_type, char *last_snm,
dir_info_t *dp, ldinfo_t *ptoi)
{
struct stat sbuf;
char dnmbuf[PATH_MAX + 1];
char lnm[NAME_BUFLEN];
char snm[NAME_BUFLEN];
char *npt;
snm[0] = NULL;
if (curr_type == DISK || curr_type == OSA_DISK) {
/*
* get the short name - omitting
* the trailing sN or PN
*/
(void) strcpy(lnm, dent);
do_snm(dent, snm);
} else if (curr_type == MD_DISK) {
(void) strcpy(lnm, dent);
(void) strcpy(snm, dent);
} else {
/*
* don't want all rewind/etc
* devices for a tape
*/
if (!str_is_digit(dent))
return;
(void) snprintf(snm, sizeof (snm), "rmt/%s", dent);
(void) snprintf(lnm, sizeof (snm), "rmt/%s", dent);
}
/*
* See if we've already processed an entry for this device.
* If so, we're just another partition so we get another
* entry.
*
* last_snm is an optimization to avoid the function call
* and lookup since we'll often see partition records
* immediately after the disk record.
*/
if (dp->skip_lookup == 0) {
if (strcmp(snm, last_snm) != 0) {
/*
* a zero return means that
* no record was found. We'd
* return a pointer otherwise.
*/
if (look_up_name(snm,
dp->list) == 0) {
(void) strcpy(last_snm, snm);
} else
return;
} else
return;
}
/*
* Get the real device name for this beast
* by following the link into /devices.
*/
(void) snprintf(dnmbuf, sizeof (dnmbuf), "%s/%s", dp->name, dent);
if (lstat(dnmbuf, &sbuf) != -1) {
if ((sbuf.st_mode & S_IFMT) == S_IFLNK) {
/*
* It's a link. Get the real name.
*/
char nmbuf[PATH_MAX + 1];
int nbyr;
if ((nbyr = readlink(dnmbuf, nmbuf,
sizeof (nmbuf))) != 1) {
npt = nmbuf;
/*
* readlink does not terminate
* the string so we have to
* do it.
*/
nmbuf[nbyr] = NULL;
} else
npt = NULL;
} else
npt = lnm;
/*
* make an entry in the device list
*/
if (npt) {
disk_list_t *d;
d = make_an_entry(npt, snm,
dnmbuf, dp,
curr_type, ptoi);
insert_into_dlist(dp, d);
}
}
}
static void
cleanup_ldinfo(ldinfo_t *list)
{
ldinfo_t *tmp;
while (list) {
tmp = list;
list = list->next;
free(tmp->name);
free(tmp->dtype);
if (tmp->devidstr)
free(tmp->devidstr);
free(tmp);
}
}
char *
lookup_nfs_name(char *ks, kstat_ctl_t *kc)
{
int tried = 0;
uint_t minor;
char *host, *path;
char *cp;
char *rstr = 0;
size_t len;
if (sscanf(ks, "nfs%u", &minor) == 1) {
retry:
cp = get_nfs_by_minor(minor);
if (cp) {
if (strchr(cp, ',') == NULL) {
rstr = safe_strdup(cp);
return (rstr);
}
host = cur_hostname(minor, kc);
if (host) {
if (*host) {
path = cur_special(host, cp);
if (path) {
len = strlen(host);
len += strlen(path);
len += 2;
rstr = safe_alloc(len);
(void) snprintf(rstr, len,
"%s:%s", host, path);
} else {
rstr = safe_strdup(cp);
}
} else {
rstr = safe_strdup(ks);
}
free(host);
} else {
rstr = safe_strdup(cp);
}
} else if (!tried) {
tried = 1;
do_mnttab();
goto retry;
}
}
return (rstr);
}
static char *
get_nfs_by_minor(uint_t minor)
{
mnt_t *localnfs;
localnfs = nfs;
while (localnfs) {
if (localnfs->minor == minor) {
return (localnfs->device_name);
}
localnfs = localnfs->next;
}
return (0);
}
/*
* Read the cur_hostname from the mntinfo kstat
*/
static char *
cur_hostname(uint_t minor, kstat_ctl_t *kc)
{
kstat_t *ksp;
static struct mntinfo_kstat mik;
char *rstr;
for (ksp = kc->kc_chain; ksp; ksp = ksp->ks_next) {
if (ksp->ks_type != KSTAT_TYPE_RAW)
continue;
if (ksp->ks_instance != minor)
continue;
if (strcmp(ksp->ks_module, "nfs"))
continue;
if (strcmp(ksp->ks_name, "mntinfo"))
continue;
if (ksp->ks_flags & KSTAT_FLAG_INVALID)
return (NULL);
if (kstat_read(kc, ksp, &mik) == -1)
return (NULL);
rstr = safe_strdup(mik.mik_curserver);
return (rstr);
}
return (NULL);
}
/*
* Given the hostname of the mounted server, extract the server
* mount point from the mnttab string.
*
* Common forms:
* server1,server2,server3:/path
* server1:/path,server2:/path
* or a hybrid of the two
*/
static char *
cur_special(char *hostname, char *special)
{
char *cp;
char *path;
size_t hlen = strlen(hostname);
/*
* find hostname in string
*/
again:
if ((cp = strstr(special, hostname)) == NULL)
return (NULL);
/*
* hostname must be followed by ',' or ':'
*/
if (cp[hlen] != ',' && cp[hlen] != ':') {
special = &cp[hlen];
goto again;
}
/*
* If hostname is followed by a ',' eat all characters until a ':'
*/
cp = &cp[hlen];
if (*cp == ',') {
cp++;
while (*cp != ':') {
if (*cp == NULL)
return (NULL);
cp++;
}
}
path = ++cp; /* skip ':' */
/*
* path is terminated by either 0, or space or ','
*/
while (*cp) {
if (isspace(*cp) || *cp == ',') {
*cp = NULL;
return (path);
}
cp++;
}
return (path);
}