NetBSD/sys/dev/dksubr.c
elric 99c563ccca Modify dksubr.c to add a function that sets the disk properties in
the drvctl framework.  And call this new functionality from cgd(4),
the consumer of dksubr.c.  We do this to allow gpt(8) to be able
to label cgd(4) disks.  We also add in some DIOCGSECTORSIZE logic
and we ensure that the WEDGE ioctls are not called on either
uninitialised disks or disks which have not been opened for write
access.
2012-05-25 10:53:46 +00:00

726 lines
17 KiB
C

/* $NetBSD: dksubr.c,v 1.43 2012/05/25 10:53:46 elric Exp $ */
/*-
* Copyright (c) 1996, 1997, 1998, 1999, 2002, 2008 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Jason R. Thorpe and Roland C. Dowdeswell.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: dksubr.c,v 1.43 2012/05/25 10:53:46 elric Exp $");
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/stat.h>
#include <sys/proc.h>
#include <sys/ioctl.h>
#include <sys/device.h>
#include <sys/disk.h>
#include <sys/disklabel.h>
#include <sys/buf.h>
#include <sys/bufq.h>
#include <sys/vnode.h>
#include <sys/fcntl.h>
#include <sys/namei.h>
#include <dev/dkvar.h>
int dkdebug = 0xff;
#define DEBUG 1
#ifdef DEBUG
#define DKDB_FOLLOW 0x1
#define DKDB_INIT 0x2
#define DKDB_VNODE 0x4
#define IFDEBUG(x,y) if (dkdebug & (x)) y
#define DPRINTF(x,y) IFDEBUG(x, printf y)
#define DPRINTF_FOLLOW(y) DPRINTF(DKDB_FOLLOW, y)
#else
#define IFDEBUG(x,y)
#define DPRINTF(x,y)
#define DPRINTF_FOLLOW(y)
#endif
#define DKLABELDEV(dev) \
(MAKEDISKDEV(major((dev)), DISKUNIT((dev)), RAW_PART))
static void dk_makedisklabel(struct dk_intf *, struct dk_softc *);
void
dk_sc_init(struct dk_softc *dksc, const char *xname)
{
memset(dksc, 0x0, sizeof(*dksc));
strncpy(dksc->sc_xname, xname, DK_XNAME_SIZE);
dksc->sc_dkdev.dk_name = dksc->sc_xname;
}
/* ARGSUSED */
int
dk_open(struct dk_intf *di, struct dk_softc *dksc, dev_t dev,
int flags, int fmt, struct lwp *l)
{
struct disklabel *lp = dksc->sc_dkdev.dk_label;
int part = DISKPART(dev);
int pmask = 1 << part;
int ret = 0;
struct disk *dk = &dksc->sc_dkdev;
DPRINTF_FOLLOW(("dk_open(%s, %p, 0x%"PRIx64", 0x%x)\n",
di->di_dkname, dksc, dev, flags));
mutex_enter(&dk->dk_openlock);
part = DISKPART(dev);
/*
* If there are wedges, and this is not RAW_PART, then we
* need to fail.
*/
if (dk->dk_nwedges != 0 && part != RAW_PART) {
ret = EBUSY;
goto done;
}
pmask = 1 << part;
/*
* If we're init'ed and there are no other open partitions then
* update the in-core disklabel.
*/
if ((dksc->sc_flags & DKF_INITED)) {
if (dk->dk_openmask == 0) {
dk_getdisklabel(di, dksc, dev);
}
/* XXX re-discover wedges? */
}
/* Fail if we can't find the partition. */
if ((part != RAW_PART) &&
(((dksc->sc_flags & DKF_INITED) == 0) ||
((part >= lp->d_npartitions) ||
(lp->d_partitions[part].p_fstype == FS_UNUSED)))) {
ret = ENXIO;
goto done;
}
/* Mark our unit as open. */
switch (fmt) {
case S_IFCHR:
dk->dk_copenmask |= pmask;
break;
case S_IFBLK:
dk->dk_bopenmask |= pmask;
break;
}
dk->dk_openmask = dk->dk_copenmask | dk->dk_bopenmask;
done:
mutex_exit(&dk->dk_openlock);
return ret;
}
/* ARGSUSED */
int
dk_close(struct dk_intf *di, struct dk_softc *dksc, dev_t dev,
int flags, int fmt, struct lwp *l)
{
int part = DISKPART(dev);
int pmask = 1 << part;
struct disk *dk = &dksc->sc_dkdev;
DPRINTF_FOLLOW(("dk_close(%s, %p, 0x%"PRIx64", 0x%x)\n",
di->di_dkname, dksc, dev, flags));
mutex_enter(&dk->dk_openlock);
switch (fmt) {
case S_IFCHR:
dk->dk_copenmask &= ~pmask;
break;
case S_IFBLK:
dk->dk_bopenmask &= ~pmask;
break;
}
dk->dk_openmask = dk->dk_copenmask | dk->dk_bopenmask;
mutex_exit(&dk->dk_openlock);
return 0;
}
void
dk_strategy(struct dk_intf *di, struct dk_softc *dksc, struct buf *bp)
{
int s;
int wlabel;
daddr_t blkno;
DPRINTF_FOLLOW(("dk_strategy(%s, %p, %p)\n",
di->di_dkname, dksc, bp));
if (!(dksc->sc_flags & DKF_INITED)) {
DPRINTF_FOLLOW(("dk_strategy: not inited\n"));
bp->b_error = ENXIO;
biodone(bp);
return;
}
/* XXX look for some more errors, c.f. ld.c */
bp->b_resid = bp->b_bcount;
/* If there is nothing to do, then we are done */
if (bp->b_bcount == 0) {
biodone(bp);
return;
}
wlabel = dksc->sc_flags & (DKF_WLABEL|DKF_LABELLING);
if (DISKPART(bp->b_dev) != RAW_PART &&
bounds_check_with_label(&dksc->sc_dkdev, bp, wlabel) <= 0) {
biodone(bp);
return;
}
blkno = bp->b_blkno;
if (DISKPART(bp->b_dev) != RAW_PART) {
struct partition *pp;
pp =
&dksc->sc_dkdev.dk_label->d_partitions[DISKPART(bp->b_dev)];
blkno += pp->p_offset;
}
bp->b_rawblkno = blkno;
/*
* Start the unit by calling the start routine
* provided by the individual driver.
*/
s = splbio();
bufq_put(dksc->sc_bufq, bp);
dk_start(di, dksc);
splx(s);
return;
}
void
dk_start(struct dk_intf *di, struct dk_softc *dksc)
{
struct buf *bp;
DPRINTF_FOLLOW(("dk_start(%s, %p)\n", di->di_dkname, dksc));
/* Process the work queue */
while ((bp = bufq_get(dksc->sc_bufq)) != NULL) {
if (di->di_diskstart(dksc, bp) != 0) {
bufq_put(dksc->sc_bufq, bp);
break;
}
}
}
void
dk_iodone(struct dk_intf *di, struct dk_softc *dksc)
{
DPRINTF_FOLLOW(("dk_iodone(%s, %p)\n", di->di_dkname, dksc));
/* We kick the queue in case we are able to get more work done */
dk_start(di, dksc);
}
int
dk_size(struct dk_intf *di, struct dk_softc *dksc, dev_t dev)
{
struct disklabel *lp;
int is_open;
int part;
int size;
if ((dksc->sc_flags & DKF_INITED) == 0)
return -1;
part = DISKPART(dev);
is_open = dksc->sc_dkdev.dk_openmask & (1 << part);
if (!is_open && di->di_open(dev, 0, S_IFBLK, curlwp))
return -1;
lp = dksc->sc_dkdev.dk_label;
if (lp->d_partitions[part].p_fstype != FS_SWAP)
size = -1;
else
size = lp->d_partitions[part].p_size *
(lp->d_secsize / DEV_BSIZE);
if (!is_open && di->di_close(dev, 0, S_IFBLK, curlwp))
return 1;
return size;
}
int
dk_ioctl(struct dk_intf *di, struct dk_softc *dksc, dev_t dev,
u_long cmd, void *data, int flag, struct lwp *l)
{
struct disklabel *lp;
struct disk *dk;
#ifdef __HAVE_OLD_DISKLABEL
struct disklabel newlabel;
#endif
int error = 0;
DPRINTF_FOLLOW(("dk_ioctl(%s, %p, 0x%"PRIx64", 0x%lx)\n",
di->di_dkname, dksc, dev, cmd));
/* ensure that the pseudo disk is open for writes for these commands */
switch (cmd) {
case DIOCSDINFO:
case DIOCWDINFO:
#ifdef __HAVE_OLD_DISKLABEL
case ODIOCSDINFO:
case ODIOCWDINFO:
#endif
case DIOCWLABEL:
case DIOCAWEDGE:
case DIOCDWEDGE:
if ((flag & FWRITE) == 0)
return EBADF;
}
/* ensure that the pseudo-disk is initialized for these */
switch (cmd) {
#ifdef DIOCGSECTORSIZE
case DIOCGSECTORSIZE:
case DIOCGMEDIASIZE:
#endif
case DIOCGDINFO:
case DIOCSDINFO:
case DIOCWDINFO:
case DIOCGPART:
case DIOCWLABEL:
case DIOCGDEFLABEL:
case DIOCAWEDGE:
case DIOCDWEDGE:
case DIOCLWEDGES:
case DIOCCACHESYNC:
#ifdef __HAVE_OLD_DISKLABEL
case ODIOCGDINFO:
case ODIOCSDINFO:
case ODIOCWDINFO:
case ODIOCGDEFLABEL:
#endif
if ((dksc->sc_flags & DKF_INITED) == 0)
return ENXIO;
}
switch (cmd) {
#ifdef DIOCGSECTORSIZE
case DIOCGSECTORSIZE:
*(u_int *)data = dksc->sc_geom.pdg_secsize;
return 0;
case DIOCGMEDIASIZE:
*(off_t *)data =
(off_t)dksc->sc_geom.pdg_secsize *
dksc->sc_geom.pdg_nsectors;
return 0;
#endif
case DIOCGDINFO:
*(struct disklabel *)data = *(dksc->sc_dkdev.dk_label);
break;
#ifdef __HAVE_OLD_DISKLABEL
case ODIOCGDINFO:
newlabel = *(dksc->sc_dkdev.dk_label);
if (newlabel.d_npartitions > OLDMAXPARTITIONS)
return ENOTTY;
memcpy(data, &newlabel, sizeof (struct olddisklabel));
break;
#endif
case DIOCGPART:
((struct partinfo *)data)->disklab = dksc->sc_dkdev.dk_label;
((struct partinfo *)data)->part =
&dksc->sc_dkdev.dk_label->d_partitions[DISKPART(dev)];
break;
case DIOCWDINFO:
case DIOCSDINFO:
#ifdef __HAVE_OLD_DISKLABEL
case ODIOCWDINFO:
case ODIOCSDINFO:
#endif
#ifdef __HAVE_OLD_DISKLABEL
if (cmd == ODIOCSDINFO || cmd == ODIOCWDINFO) {
memset(&newlabel, 0, sizeof newlabel);
memcpy(&newlabel, data, sizeof (struct olddisklabel));
lp = &newlabel;
} else
#endif
lp = (struct disklabel *)data;
dk = &dksc->sc_dkdev;
mutex_enter(&dk->dk_openlock);
dksc->sc_flags |= DKF_LABELLING;
error = setdisklabel(dksc->sc_dkdev.dk_label,
lp, 0, dksc->sc_dkdev.dk_cpulabel);
if (error == 0) {
if (cmd == DIOCWDINFO
#ifdef __HAVE_OLD_DISKLABEL
|| cmd == ODIOCWDINFO
#endif
)
error = writedisklabel(DKLABELDEV(dev),
di->di_strategy, dksc->sc_dkdev.dk_label,
dksc->sc_dkdev.dk_cpulabel);
}
dksc->sc_flags &= ~DKF_LABELLING;
mutex_exit(&dk->dk_openlock);
break;
case DIOCWLABEL:
if (*(int *)data != 0)
dksc->sc_flags |= DKF_WLABEL;
else
dksc->sc_flags &= ~DKF_WLABEL;
break;
case DIOCGDEFLABEL:
dk_getdefaultlabel(di, dksc, (struct disklabel *)data);
break;
#ifdef __HAVE_OLD_DISKLABEL
case ODIOCGDEFLABEL:
dk_getdefaultlabel(di, dksc, &newlabel);
if (newlabel.d_npartitions > OLDMAXPARTITIONS)
return ENOTTY;
memcpy(data, &newlabel, sizeof (struct olddisklabel));
break;
#endif
case DIOCAWEDGE:
{
struct dkwedge_info *dkw = (void *)data;
if ((flag & FWRITE) == 0)
return (EBADF);
/* If the ioctl happens here, the parent is us. */
strcpy(dkw->dkw_parent, dksc->sc_dkdev.dk_name);
return (dkwedge_add(dkw));
}
case DIOCDWEDGE:
{
struct dkwedge_info *dkw = (void *)data;
if ((flag & FWRITE) == 0)
return (EBADF);
/* If the ioctl happens here, the parent is us. */
strcpy(dkw->dkw_parent, dksc->sc_dkdev.dk_name);
return (dkwedge_del(dkw));
}
case DIOCLWEDGES:
{
struct dkwedge_list *dkwl = (void *)data;
return (dkwedge_list(&dksc->sc_dkdev, dkwl, l));
}
case DIOCGSTRATEGY:
{
struct disk_strategy *dks = (void *)data;
int s;
s = splbio();
strlcpy(dks->dks_name, bufq_getstrategyname(dksc->sc_bufq),
sizeof(dks->dks_name));
splx(s);
dks->dks_paramlen = 0;
return 0;
}
case DIOCSSTRATEGY:
{
struct disk_strategy *dks = (void *)data;
struct bufq_state *new;
struct bufq_state *old;
int s;
if ((flag & FWRITE) == 0) {
return EBADF;
}
if (dks->dks_param != NULL) {
return EINVAL;
}
dks->dks_name[sizeof(dks->dks_name) - 1] = 0; /* ensure term */
error = bufq_alloc(&new, dks->dks_name,
BUFQ_EXACT|BUFQ_SORT_RAWBLOCK);
if (error) {
return error;
}
s = splbio();
old = dksc->sc_bufq;
bufq_move(new, old);
dksc->sc_bufq = new;
splx(s);
bufq_free(old);
return 0;
}
default:
error = ENOTTY;
}
return error;
}
/*
* dk_dump dumps all of physical memory into the partition specified.
* This requires substantially more framework than {s,w}ddump, and hence
* is probably much more fragile.
*
* XXX: we currently do not implement this.
*/
#define DKF_READYFORDUMP (DKF_INITED|DKF_TAKEDUMP)
#define DKFF_READYFORDUMP(x) (((x) & DKF_READYFORDUMP) == DKF_READYFORDUMP)
static volatile int dk_dumping = 0;
/* ARGSUSED */
int
dk_dump(struct dk_intf *di, struct dk_softc *dksc, dev_t dev,
daddr_t blkno, void *va, size_t size)
{
/*
* ensure that we consider this device to be safe for dumping,
* and that the device is configured.
*/
if (!DKFF_READYFORDUMP(dksc->sc_flags))
return ENXIO;
/* ensure that we are not already dumping */
if (dk_dumping)
return EFAULT;
dk_dumping = 1;
/* XXX: unimplemented */
dk_dumping = 0;
/* XXX: actually for now, we are going to leave this alone */
return ENXIO;
}
/* ARGSUSED */
void
dk_getdefaultlabel(struct dk_intf *di, struct dk_softc *dksc,
struct disklabel *lp)
{
struct dk_geom *pdg = &dksc->sc_geom;
memset(lp, 0, sizeof(*lp));
lp->d_secperunit = dksc->sc_size;
lp->d_secsize = pdg->pdg_secsize;
lp->d_nsectors = pdg->pdg_nsectors;
lp->d_ntracks = pdg->pdg_ntracks;
lp->d_ncylinders = pdg->pdg_ncylinders;
lp->d_secpercyl = lp->d_ntracks * lp->d_nsectors;
strncpy(lp->d_typename, di->di_dkname, sizeof(lp->d_typename));
lp->d_type = di->di_dtype;
strncpy(lp->d_packname, "fictitious", sizeof(lp->d_packname));
lp->d_rpm = 3600;
lp->d_interleave = 1;
lp->d_flags = 0;
lp->d_partitions[RAW_PART].p_offset = 0;
lp->d_partitions[RAW_PART].p_size = dksc->sc_size;
lp->d_partitions[RAW_PART].p_fstype = FS_UNUSED;
lp->d_npartitions = RAW_PART + 1;
lp->d_magic = DISKMAGIC;
lp->d_magic2 = DISKMAGIC;
lp->d_checksum = dkcksum(dksc->sc_dkdev.dk_label);
}
/* ARGSUSED */
void
dk_getdisklabel(struct dk_intf *di, struct dk_softc *dksc, dev_t dev)
{
struct disklabel *lp = dksc->sc_dkdev.dk_label;
struct cpu_disklabel *clp = dksc->sc_dkdev.dk_cpulabel;
struct partition *pp;
int i;
const char *errstring;
memset(clp, 0x0, sizeof(*clp));
dk_getdefaultlabel(di, dksc, lp);
errstring = readdisklabel(DKLABELDEV(dev), di->di_strategy,
dksc->sc_dkdev.dk_label, dksc->sc_dkdev.dk_cpulabel);
if (errstring) {
dk_makedisklabel(di, dksc);
if (dksc->sc_flags & DKF_WARNLABEL)
printf("%s: %s\n", dksc->sc_xname, errstring);
return;
}
if ((dksc->sc_flags & DKF_LABELSANITY) == 0)
return;
/* Sanity check */
if (lp->d_secperunit != dksc->sc_size)
printf("WARNING: %s: total sector size in disklabel (%d) "
"!= the size of %s (%lu)\n", dksc->sc_xname,
lp->d_secperunit, di->di_dkname, (u_long)dksc->sc_size);
for (i=0; i < lp->d_npartitions; i++) {
pp = &lp->d_partitions[i];
if (pp->p_offset + pp->p_size > dksc->sc_size)
printf("WARNING: %s: end of partition `%c' exceeds "
"the size of %s (%lu)\n", dksc->sc_xname,
'a' + i, di->di_dkname, (u_long)dksc->sc_size);
}
}
/* ARGSUSED */
static void
dk_makedisklabel(struct dk_intf *di, struct dk_softc *dksc)
{
struct disklabel *lp = dksc->sc_dkdev.dk_label;
lp->d_partitions[RAW_PART].p_fstype = FS_BSDFFS;
strncpy(lp->d_packname, "default label", sizeof(lp->d_packname));
lp->d_checksum = dkcksum(lp);
}
void
dk_set_properties(struct dk_intf *di, struct dk_softc *dksc)
{
prop_dictionary_t disk_info, odisk_info, geom;
disk_info = prop_dictionary_create();
geom = prop_dictionary_create();
prop_dictionary_set_uint64(geom, "sectors-per-unit",
dksc->sc_geom.pdg_nsectors * dksc->sc_geom.pdg_ntracks *
dksc->sc_geom.pdg_ncylinders);
prop_dictionary_set_uint32(geom, "sector-size",
dksc->sc_geom.pdg_secsize);
prop_dictionary_set_uint16(geom, "sectors-per-track",
dksc->sc_geom.pdg_nsectors);
prop_dictionary_set_uint16(geom, "tracks-per-cylinder",
dksc->sc_geom.pdg_ntracks);
prop_dictionary_set_uint64(geom, "cylinders-per-unit",
dksc->sc_geom.pdg_ncylinders);
prop_dictionary_set(disk_info, "geometry", geom);
prop_object_release(geom);
prop_dictionary_set(device_properties(dksc->sc_dev),
"disk-info", disk_info);
/*
* Don't release disk_info here; we keep a reference to it.
* disk_detach() will release it when we go away.
*/
odisk_info = dksc->sc_dkdev.dk_info;
dksc->sc_dkdev.dk_info = disk_info;
if (odisk_info)
prop_object_release(odisk_info);
}
/* This function is taken from ccd.c:1.76 --rcd */
/*
* XXX this function looks too generic for dksubr.c, shouldn't we
* put it somewhere better?
*/
/*
* Lookup the provided name in the filesystem. If the file exists,
* is a valid block device, and isn't being used by anyone else,
* set *vpp to the file's vnode.
*/
int
dk_lookup(struct pathbuf *pb, struct lwp *l, struct vnode **vpp)
{
struct nameidata nd;
struct vnode *vp;
struct vattr va;
int error;
if (l == NULL)
return ESRCH; /* Is ESRCH the best choice? */
NDINIT(&nd, LOOKUP, FOLLOW, pb);
if ((error = vn_open(&nd, FREAD | FWRITE, 0)) != 0) {
DPRINTF((DKDB_FOLLOW|DKDB_INIT),
("dk_lookup: vn_open error = %d\n", error));
return error;
}
vp = nd.ni_vp;
if ((error = VOP_GETATTR(vp, &va, l->l_cred)) != 0) {
DPRINTF((DKDB_FOLLOW|DKDB_INIT),
("dk_lookup: getattr error = %d\n", error));
goto out;
}
/* XXX: eventually we should handle VREG, too. */
if (va.va_type != VBLK) {
error = ENOTBLK;
goto out;
}
IFDEBUG(DKDB_VNODE, vprint("dk_lookup: vnode info", vp));
VOP_UNLOCK(vp);
*vpp = vp;
return 0;
out:
VOP_UNLOCK(vp);
(void) vn_close(vp, FREAD | FWRITE, l->l_cred);
return error;
}