dsr.c revision a08731ec17cb062d80f19791149c20e0f2f43b01
/*
* 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 2006 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"
/* disk/tape info */
static di_node_t di_root; /* for devid */
static di_dim_t di_dim; /* for /dev names */
typedef struct {
char *minor_name;
int minor_isdisk;
} minor_match_t;
static minor_match_t mm_disk = {"a", 1};
static minor_match_t mm_tape = {"", 0};
static char md_minor_name[MAXPATHLEN];
static minor_match_t mm_md = {md_minor_name, 0};
static minor_match_t *mma_disk_tape[] = {&mm_disk, &mm_tape, NULL};
static minor_match_t *mma_md[] = {&mm_md, NULL};
static char *mdsetno2name(int setno);
#define DISKLIST_MOD 256 /* ^2 instunit mod hash */
static disk_list_t *disklist[DISKLIST_MOD];
/* nfs info */
extern kstat_ctl_t *kc;
extern mnt_t *nfs;
static int nfs_tried;
static char *get_nfs_by_minor(uint_t);
static char *cur_hostname(uint_t, kstat_ctl_t *);
static char *cur_special(char *, char *);
/*
* Clear the snapshot so a cache miss in lookup_ks_name() will cause a fresh
* snapshot in drvinstunit2dev().
*/
void
cleanup_iodevs_snapshot()
{
if (di_dim) {
di_dim_fini(di_dim);
di_dim = NULL;
}
if (di_root) {
di_fini(di_root);
di_root = DI_NODE_NIL;
}
nfs_tried = 0;
}
/*
* Find information for (driver, instunit) device: return zero on failure.
*
* NOTE: Failure of drvinstunit2dev works out OK for the caller if the kstat
* name is the same as public name: the caller will just use kstat name.
*/
static int
drvinstunit2dev(char *driver, int instunit,
char **devpathp, char **adevpathp, char **devidp, int *isdiskp)
{
int instance;
minor_match_t **mma;
minor_match_t *mm;
char *devpath;
char *devid;
char *a, *s;
int mdsetno;
char *mdsetname = NULL;
char amdsetname[MAXPATHLEN];
char *devicespath;
di_node_t node;
/* setup "no result" return values */
if (devpathp)
*devpathp = NULL;
if (adevpathp)
*adevpathp = NULL;
if (devidp)
*devidp = NULL;
if (isdiskp)
*isdiskp = 0;
/* take <driver><instance><minor_name> snapshot if not established */
if (di_dim == NULL) {
di_dim = di_dim_init();
if (di_dim == NULL)
return (0);
}
/*
* Determine if 'instunit' is an 'instance' or 'unit' based on the
* 'driver'. The current code only detects 'md' metadevice 'units',
* and defaults to 'instance' for everything else.
*
* For a metadevice, 'driver' is either "md" or "<setno>/md".
*/
s = strstr(driver, "/md");
if ((strcmp(driver, "md") == 0) ||
(s && isdigit(*driver) && (strcmp(s, "/md") == 0))) {
/*
* "md" unit: Special case translation of "md" kstat names.
* For the local set the kstat name is "md<unit>", and for
* a shared set the kstat name is "<setno>/md<unit>": we map
* these to the minor paths "/pseudo/md@0:<unit>,blk" and
* "/pseudo/md@0:<set>,<unit>,blk" respectively.
*/
if (isdigit(*driver)) {
mdsetno = atoi(driver);
/* convert setno to setname */
mdsetname = mdsetno2name(mdsetno);
} else
mdsetno = 0;
driver = "md";
instance = 0;
mma = mma_md; /* metadevice dynamic minor */
(void) snprintf(md_minor_name, sizeof (md_minor_name),
"%d,%d,blk", mdsetno, instunit);
} else {
instance = instunit;
mma = mma_disk_tape; /* disk/tape minors */
}
/* Try to find a minor_match that works */
for (mm = *mma++; mm; mm = *mma++) {
if ((devpath = di_dim_path_dev(di_dim,
driver, instance, mm->minor_name)) != NULL)
break;
}
if (devpath == NULL)
return (0);
/*
* At this point we have a devpath result. Return the information about
* the result that the caller is asking for.
*/
if (devpathp) /* devpath */
*devpathp = safe_strdup(devpath);
if (adevpathp) { /* abbreviated devpath */
if (mm->minor_isdisk) {
/*
* For disks we return the last component (with
* trailing "s#" or "p#" stripped off for disks).
* For example for devpath of "/dev/dsk/c0t0d0s0" the
* abbreviated devpath would be "c0t0d0".
*/
a = strrchr(devpath, '/');
if (a == NULL) {
free(devpath);
return (0);
}
a++;
s = strrchr(a, 's');
if (s == NULL) {
s = strrchr(a, 'p');
if (s == NULL) {
free(devpath);
return (0);
}
}
/* don't include slice information in devpath */
*s = '\0';
} else {
/*
* remove "/dev/", and "/dsk/", from 'devpath' (like
* "/dev/md/dsk/d0") to form the abbreviated devpath
* (like "md/d0").
*/
if ((s = strstr(devpath, "/dev/")) != NULL)
(void) strcpy(s + 1, s + 5);
if ((s = strstr(devpath, "/dsk/")) != NULL)
(void) strcpy(s + 1, s + 5);
/*
* If we have an mdsetname, convert abbreviated setno
* notation (like "md/shared/1/d0" to abbreviated
* setname notation (like "md/red/d0").
*/
if (mdsetname) {
a = strrchr(devpath, '/');
(void) snprintf(amdsetname, sizeof (amdsetname),
"md/%s%s", mdsetname, a);
free(mdsetname);
a = amdsetname;
} else {
if (*devpath == '/')
a = devpath + 1;
else
a = devpath;
}
}
*adevpathp = safe_strdup(a);
}
if (devidp) { /* lookup the devid */
/* take snapshots if not established */
if (di_root == DI_NODE_NIL) {
di_root = di_init("/", DINFOCACHE);
}
if (di_root) {
/* get path to /devices devinfo node */
devicespath = di_dim_path_devices(di_dim,
driver, instance, NULL);
if (devicespath) {
/* find the node in the snapshot */
node = di_lookup_node(di_root, devicespath);
free(devicespath);
/* and lookup devid property on the node */
if (di_prop_lookup_strings(DDI_DEV_T_ANY, node,
DEVID_PROP_NAME, &devid) != -1)
*devidp = devid;
}
}
}
if (isdiskp)
*isdiskp = mm->minor_isdisk;
free(devpath);
return (1); /* success */
}
/*
* Find/create a disk_list entry for "<driver><instunit>" given a kstat name.
* The basic format of a kstat name is "<driver><instunit>,<partition>". The
* <instunit> is a base10 number, and the ",<partition>" part is optional.
*
* NOTE: In the case of non-local metadevices, the format of "<driver>" in
* a kstat name is acutally "<setno>/md".
*/
disk_list_t *
lookup_ks_name(char *ks_name, int want_devid)
{
char *p;
int len;
char driver[MAXNAMELEN];
int instunit;
disk_list_t **dlhp; /* disklist head */
disk_list_t *entry;
char *devpath;
char *adevpath = NULL;
char *devid = NULL;
int isdisk;
/*
* Extract <driver> and <instunit> from kstat name.
* Filter out illegal forms (like all digits).
*/
if ((ks_name == NULL) || (*ks_name == 0) ||
(strspn(ks_name, "0123456789") == strlen(ks_name)))
return (NULL);
p = strrchr(ks_name, ','); /* start of ",partition" */
if (p == NULL)
p = &ks_name[strlen(ks_name) - 1]; /* last char */
else
p--; /* before ",partition" */
while ((p >= ks_name) && isdigit(*p))
p--; /* backwards over digits */
p++; /* start of instunit */
if ((*p == '\0') || (*p == ','))
return (NULL); /* no <instunit> */
len = p - ks_name;
(void) strncpy(driver, ks_name, len);
driver[len] = '\0';
instunit = atoi(p);
/* hash and search for existing disklist entry */
dlhp = &disklist[instunit & (DISKLIST_MOD - 1)];
for (entry = *dlhp; entry; entry = entry->next) {
if ((strcmp(entry->dtype, driver) == 0) &&
(entry->dnum == instunit)) {
return (entry);
}
}
/* not found, try to get dev information */
if (drvinstunit2dev(driver, instunit, &devpath, &adevpath,
want_devid ? &devid : NULL, &isdisk) == 0) {
return (NULL);
}
/* and make a new disklist entry ... */
entry = safe_alloc(sizeof (disk_list_t));
entry->dtype = safe_strdup(driver);
entry->dnum = instunit;
entry->dname = devpath;
entry->dsk = adevpath;
entry->devidstr = devid;
entry->flags = 0;
if (isdisk) {
entry->flags |= SLICES_OK;
#if defined(__i386)
entry->flags |= PARTITIONS_OK;
#endif
}
entry->seen = 0;
/* add new entry to head of instunit hashed list */
entry->next = *dlhp;
*dlhp = entry;
return (entry);
}
/*
* Convert metadevice setno to setname by looking in /dev/md for symlinks
* that point to "shared/setno" - the name of such a symlink is the setname.
* The caller is responsible for freeing the returned string.
*/
static char *
mdsetno2name(int setno)
{
char setlink[MAXPATHLEN + 1];
char link[MAXPATHLEN + 1];
char path[MAXPATHLEN + 1];
char *p;
DIR *dirp;
struct dirent *dp;
size_t len;
char *mdsetname = NULL;
/* we are looking for a link to setlink */
(void) snprintf(setlink, MAXPATHLEN, "shared/%d", setno);
/* in the directory /dev/md */
(void) strcpy(path, "/dev/md/");
p = path + strlen(path);
dirp = opendir(path);
if (dirp == NULL)
return (NULL);
/* loop through /dev/md directory entries */
while ((dp = readdir(dirp)) != NULL) {
/* doing a readlink of entry (fails for non-symlinks) */
*p = '\0';
(void) strcpy(p, dp->d_name);
if ((len = readlink(path, link, MAXPATHLEN)) == (size_t)-1)
continue;
/* and looking for a link to setlink */
link[len] = '\0';
if (strcmp(setlink, link))
continue;
/* found- name of link is the setname */
mdsetname = safe_strdup(dp->d_name);
break;
}
(void) closedir(dirp);
return (mdsetname);
}
char *
lookup_nfs_name(char *ks, kstat_ctl_t *kc)
{
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 (nfs_tried == 0) {
nfs_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);
}