#include <sys/param.h>
#include <sys/systm.h>
#include <sys/buf.h>
#include <sys/conf.h>
#include <sys/disklabel.h>
#include <sys/disklabel32.h>
#include <sys/disklabel64.h>
#include <sys/diskslice.h>
#include <sys/disk.h>
#include <sys/diskmbr.h>
#include <sys/fcntl.h>
#include <sys/malloc.h>
#include <sys/stat.h>
#include <sys/syslog.h>
#include <sys/proc.h>
#include <sys/vnode.h>
#include <sys/device.h>
#include <vfs/ufs/dinode.h>
#include <vfs/ufs/fs.h>
#include <sys/devfs.h>
static int dsreadandsetlabel(cdev_t dev, u_int flags,
struct diskslices *ssp, struct diskslice *sp,
struct disk_info *info);
static void free_ds_label (struct diskslices *ssp, int slice);
static void set_ds_label (struct diskslices *ssp, int slice, disklabel_t lp,
disklabel_ops_t ops);
static void set_ds_wlabel (struct diskslices *ssp, int slice, int wlabel);
struct bio *
dscheck(cdev_t dev, struct bio *bio, struct diskslices *ssp)
{
struct buf *bp = bio->bio_buf;
struct bio *nbio;
disklabel_t lp;
disklabel_ops_t ops;
long nsec;
u_int64_t secno;
u_int64_t endsecno;
u_int64_t slicerel_secno;
struct diskslice *sp;
u_int32_t part;
u_int32_t slice;
int shift;
int mask;
slice = dkslice(dev);
part = dkpart(dev);
if (bio->bio_offset < 0) {
kprintf("dscheck(%s): negative bio_offset %lld\n",
devtoname(dev), (long long)bio->bio_offset);
goto bad;
}
if (slice >= ssp->dss_nslices) {
kprintf("dscheck(%s): slice too large %d/%d\n",
devtoname(dev), slice, ssp->dss_nslices);
goto bad;
}
sp = &ssp->dss_slices[slice];
if (ssp->dss_secmult == 1) {
shift = DEV_BSHIFT;
goto doshift;
} else if (ssp->dss_secshift != -1) {
shift = DEV_BSHIFT + ssp->dss_secshift;
doshift:
mask = (1 << shift) - 1;
if ((int)bp->b_bcount & mask)
goto bad_bcount;
if ((int)bio->bio_offset & mask)
goto bad_blkno;
secno = bio->bio_offset >> shift;
nsec = bp->b_bcount >> shift;
} else {
if (bp->b_bcount % ssp->dss_secsize)
goto bad_bcount;
if (bio->bio_offset % ssp->dss_secsize)
goto bad_blkno;
secno = bio->bio_offset / ssp->dss_secsize;
nsec = bp->b_bcount / ssp->dss_secsize;
}
if (slice == WHOLE_DISK_SLICE) {
lp.opaque = NULL;
if (part < 128) {
kprintf("dscheck(%s): illegal partition number (%d) "
"for WHOLE_DISK_SLICE access\n",
devtoname(dev), part);
goto bad;
} else if (part != WHOLE_SLICE_PART) {
nbio = push_bio(bio);
nbio->bio_offset = bio->bio_offset |
(u_int64_t)part << 56;
return(nbio);
} else {
if (bp->b_cmd == BUF_CMD_WRITE)
sp->ds_flags |= DSF_REPROBE;
}
endsecno = sp->ds_size;
slicerel_secno = secno;
} else if (part == WHOLE_SLICE_PART) {
endsecno = sp->ds_size;
slicerel_secno = secno;
} else if ((lp = sp->ds_label).opaque != NULL) {
ops = sp->ds_ops;
if (ops->op_getpartbounds(ssp, lp, part,
&slicerel_secno, &endsecno)) {
kprintf("dscheck(%s): partition %d out of bounds\n",
devtoname(dev), part);
goto bad;
}
slicerel_secno += secno;
} else {
kprintf("dscheck(%s): attempt to access non-existent partition\n",
devtoname(dev));
goto bad;
}
if (slicerel_secno < sp->ds_reserved && nsec &&
bp->b_cmd == BUF_CMD_WRITE) {
if (sp->ds_wlabel == 0) {
bp->b_error = EROFS;
goto error;
}
sp->ds_flags |= DSF_REPROBE;
}
if ((bio->bio_offset & (ssp->dss_secsize - 1)) ||
(ssp->dss_secsize ^ (ssp->dss_secsize - 1)) !=
((ssp->dss_secsize << 1) - 1)) {
kprintf("%s: invalid BIO offset, not sector aligned or"
" invalid sector size (not power of 2) %08llx %d\n",
devtoname(dev), (long long)bio->bio_offset,
ssp->dss_secsize);
goto bad;
}
if (secno + nsec > endsecno) {
if (secno > endsecno || (bp->b_flags & B_BNOCLIP))
goto bad;
if (secno == endsecno) {
bp->b_resid = bp->b_bcount;
bp->b_flags |= B_INVAL;
goto done;
}
nsec = endsecno - secno;
bp->b_bcount = nsec * ssp->dss_secsize;
}
nbio = push_bio(bio);
nbio->bio_offset = (off_t)(sp->ds_offset + slicerel_secno) *
ssp->dss_secsize;
return (nbio);
bad_bcount:
kprintf("dscheck(%s): b_bcount %d is not on a sector boundary "
"(ssize %d)\n",
devtoname(dev), bp->b_bcount, ssp->dss_secsize);
goto bad;
bad_blkno:
kprintf("dscheck(%s): bio_offset %lld is not on a sector boundary "
"(ssize %d)\n",
devtoname(dev), (long long)bio->bio_offset, ssp->dss_secsize);
bad:
bp->b_error = EINVAL;
error:
bp->b_resid = bp->b_bcount;
bp->b_flags |= B_ERROR | B_INVAL;
done:
return (NULL);
}
void
dsclose(cdev_t dev, int mode, struct diskslices *ssp)
{
u_int32_t part;
u_int32_t slice;
struct diskslice *sp;
slice = dkslice(dev);
part = dkpart(dev);
if (slice < ssp->dss_nslices) {
sp = &ssp->dss_slices[slice];
dsclrmask(sp, part);
if (sp->ds_flags & DSF_REPROBE) {
sp->ds_flags &= ~DSF_REPROBE;
if (slice == WHOLE_DISK_SLICE) {
disk_msg_send_sync(DISK_DISK_REPROBE,
dev->si_disk, NULL);
devfs_config();
} else {
disk_msg_send_sync(DISK_SLICE_REPROBE,
dev->si_disk, sp);
devfs_config();
}
}
}
}
void
dsgone(struct diskslices **sspp)
{
int slice;
struct diskslices *ssp;
if ((ssp = *sspp) != NULL) {
for (slice = 0; slice < ssp->dss_nslices; slice++)
free_ds_label(ssp, slice);
kfree(ssp, M_DEVBUF);
*sspp = NULL;
}
}
int
dsioctl(cdev_t dev, u_long cmd, caddr_t data, int flags,
struct diskslices **sspp, struct disk_info *info)
{
int error;
disklabel_t lp;
disklabel_t lptmp;
disklabel_ops_t ops;
int old_wlabel;
u_int32_t openmask[DKMAXPARTITIONS/(sizeof(u_int32_t)*8)];
int part;
int slice;
struct diskslice *sp;
struct diskslices *ssp;
slice = dkslice(dev);
part = dkpart(dev);
ssp = *sspp;
if (ssp == NULL)
return (EINVAL);
if (slice >= ssp->dss_nslices)
return (EINVAL);
sp = &ssp->dss_slices[slice];
lp = sp->ds_label;
ops = sp->ds_ops;
switch (cmd) {
case DIOCGDVIRGIN32:
ops = &disklabel32_ops;
case DIOCGDVIRGIN64:
if (cmd != DIOCGDVIRGIN32)
ops = &disklabel64_ops;
if (slice != WHOLE_DISK_SLICE &&
part != WHOLE_SLICE_PART) {
return(EINVAL);
}
lp.opaque = data;
ops->op_makevirginlabel(lp, ssp, sp, info);
return (0);
case DIOCGDINFO32:
case DIOCGDINFO64:
error = 0;
if (part != WHOLE_SLICE_PART)
return(EINVAL);
if (slice == WHOLE_DISK_SLICE &&
(info->d_dsflags & DSO_COMPATLABEL) == 0) {
return (ENODEV);
}
if (sp->ds_label.opaque == NULL) {
error = dsreadandsetlabel(dev, info->d_dsflags,
ssp, sp, info);
ops = sp->ds_ops;
}
if (error == 0 && IOCPARM_LEN(cmd) != ops->labelsize)
error = ENOATTR;
if (error == 0)
bcopy(sp->ds_label.opaque, data, ops->labelsize);
return (error);
case DIOCGMEDIASIZE:
if (sp->ds_label.opaque == NULL &&
part == WHOLE_SLICE_PART &&
slice != WHOLE_DISK_SLICE) {
dsreadandsetlabel(dev, info->d_dsflags,
ssp, sp, info);
ops = sp->ds_ops;
}
*(off_t *)data = 0;
if (slice != WHOLE_DISK_SLICE && part != WHOLE_SLICE_PART) {
u_int64_t start;
u_int64_t blocks;
if (lp.opaque == NULL)
return(EINVAL);
if (ops->op_getpartbounds(ssp, lp, part,
&start, &blocks)) {
return(EINVAL);
}
*(off_t *)data = blocks * info->d_media_blksize;
} else {
*(off_t *)data = (u_int64_t)sp->ds_size *
info->d_media_blksize;
}
return 0;
case DIOCGSECTORSIZE:
*(u_int *)data = info->d_media_blksize;
return 0;
case DIOCGPART:
{
struct partinfo *dpart = (void *)data;
if (sp->ds_label.opaque == NULL &&
part == WHOLE_SLICE_PART &&
slice != WHOLE_DISK_SLICE) {
dsreadandsetlabel(dev, info->d_dsflags,
ssp, sp, info);
ops = sp->ds_ops;
}
bzero(dpart, sizeof(*dpart));
dpart->media_offset = (u_int64_t)sp->ds_offset *
info->d_media_blksize;
dpart->media_size = (u_int64_t)sp->ds_size *
info->d_media_blksize;
dpart->media_blocks = sp->ds_size;
dpart->media_blksize = info->d_media_blksize;
dpart->reserved_blocks= sp->ds_reserved;
dpart->fstype_uuid = sp->ds_type_uuid;
dpart->storage_uuid = sp->ds_stor_uuid;
if (slice != WHOLE_DISK_SLICE &&
part != WHOLE_SLICE_PART) {
u_int64_t start;
u_int64_t blocks;
if (lp.opaque == NULL)
return(EINVAL);
if (ops->op_getpartbounds(ssp, lp, part,
&start, &blocks)) {
return(EINVAL);
}
ops->op_loadpartinfo(lp, part, dpart);
dpart->media_offset += start *
info->d_media_blksize;
dpart->media_size = blocks *
info->d_media_blksize;
dpart->media_blocks = blocks;
if (dpart->reserved_blocks > start)
dpart->reserved_blocks -= start;
else
dpart->reserved_blocks = 0;
}
dpart->d_nheads = info->d_nheads;
dpart->d_ncylinders = info->d_ncylinders;
dpart->d_secpertrack = info->d_secpertrack;
dpart->d_secpercyl = info->d_secpercyl;
}
return (0);
case DIOCSDINFO32:
ops = &disklabel32_ops;
case DIOCSDINFO64:
if (cmd != DIOCSDINFO32)
ops = &disklabel64_ops;
if (slice != WHOLE_DISK_SLICE &&
part != WHOLE_SLICE_PART) {
return(EINVAL);
}
if (slice == WHOLE_DISK_SLICE)
return (ENODEV);
if (!(flags & FWRITE))
return (EBADF);
if (sp->ds_label.opaque && sp->ds_ops != ops)
return (ENOATTR);
lp.opaque = kmalloc(ops->labelsize, M_DEVBUF, M_WAITOK);
if (sp->ds_label.opaque == NULL)
bzero(lp.opaque, ops->labelsize);
else
bcopy(sp->ds_label.opaque, lp.opaque, ops->labelsize);
if (sp->ds_label.opaque == NULL) {
bzero(openmask, sizeof(openmask));
} else {
bcopy(sp->ds_openmask, openmask, sizeof(openmask));
}
lptmp.opaque = data;
error = ops->op_setdisklabel(lp, lptmp, ssp, sp, openmask);
disk_msg_send_sync(DISK_SLICE_REPROBE, dev->si_disk, sp);
devfs_config();
if (error != 0) {
kfree(lp.opaque, M_DEVBUF);
return (error);
}
free_ds_label(ssp, slice);
set_ds_label(ssp, slice, lp, ops);
return (0);
case DIOCWDINFO32:
case DIOCWDINFO64:
error = dsioctl(dev, ((cmd == DIOCWDINFO32) ?
DIOCSDINFO32 : DIOCSDINFO64),
data, flags, &ssp, info);
if (error == 0 && sp->ds_label.opaque == NULL)
error = EINVAL;
if (part != WHOLE_SLICE_PART)
error = EINVAL;
if (error != 0)
return (error);
ops = sp->ds_ops;
old_wlabel = sp->ds_wlabel;
set_ds_wlabel(ssp, slice, TRUE);
error = ops->op_writedisklabel(dev, ssp, sp, sp->ds_label);
disk_msg_send_sync(DISK_SLICE_REPROBE, dev->si_disk, sp);
devfs_config();
set_ds_wlabel(ssp, slice, old_wlabel);
return (error);
case DIOCWLABEL:
if (slice == WHOLE_DISK_SLICE)
return (ENODEV);
if (!(flags & FWRITE))
return (EBADF);
set_ds_wlabel(ssp, slice, *(int *)data != 0);
return (0);
default:
return (ENOIOCTL);
}
}
int
dsisopen(struct diskslices *ssp)
{
int slice;
if (ssp == NULL)
return (0);
for (slice = 0; slice < ssp->dss_nslices; slice++) {
if (dscountmask(&ssp->dss_slices[slice]))
return (1);
}
return (0);
}
struct diskslices *
dsmakeslicestruct(int nslices, struct disk_info *info)
{
struct diskslice *sp;
struct diskslices *ssp;
ssp = kmalloc(offsetof(struct diskslices, dss_slices) +
nslices * sizeof(*sp), M_DEVBUF, M_WAITOK);
ssp->dss_first_bsd_slice = COMPATIBILITY_SLICE;
ssp->dss_nslices = nslices;
ssp->dss_oflags = 0;
if ((info->d_media_blksize ^ (info->d_media_blksize - 1)) ==
(info->d_media_blksize << 1) - 1)
{
ssp->dss_secmult = info->d_media_blksize / DEV_BSIZE;
if (ssp->dss_secmult & (ssp->dss_secmult - 1))
ssp->dss_secshift = -1;
else
ssp->dss_secshift = ffs(ssp->dss_secmult) - 1;
} else {
ssp->dss_secmult = 0;
ssp->dss_secshift = -1;
}
ssp->dss_secsize = info->d_media_blksize;
sp = &ssp->dss_slices[0];
bzero(sp, nslices * sizeof(*sp));
sp[WHOLE_DISK_SLICE].ds_size = info->d_media_blocks;
return (ssp);
}
char *
dsname(cdev_t dev, int unit, int slice, int part, char *partname)
{
return dev->si_name;
}
int
dsopen(cdev_t dev, int mode, u_int flags,
struct diskslices **sspp, struct disk_info *info)
{
struct diskslice *sp;
struct diskslices *ssp;
int slice;
int part;
ssp = *sspp;
dev->si_bsize_phys = info->d_media_blksize;
slice = dkslice(dev);
part = dkpart(dev);
sp = &ssp->dss_slices[slice];
dssetmask(sp, part);
return 0;
}
static
int
dsreadandsetlabel(cdev_t dev, u_int flags,
struct diskslices *ssp, struct diskslice *sp,
struct disk_info *info)
{
disklabel_t lp;
disklabel_ops_t ops;
const char *msg;
const char *sname;
char partname[2];
int slice = dkslice(dev);
lp.opaque = NULL;
sname = dsname(dev, dkunit(dev), slice, WHOLE_SLICE_PART, partname);
ops = &disklabel32_ops;
msg = ops->op_readdisklabel(dev, sp, &lp, info);
if (msg && strcmp(msg, "no disk label") == 0) {
ops = &disklabel64_ops;
msg = disklabel64_ops.op_readdisklabel(dev, sp, &lp, info);
}
if (msg != NULL && (flags & DSO_COMPATLABEL)) {
msg = NULL;
if (sp->ds_size >= 0x100000000ULL)
ops = &disklabel64_ops;
else
ops = &disklabel32_ops;
lp = ops->op_clone_label(info, sp);
}
if (msg != NULL) {
if (sp->ds_type == DOSPTYP_386BSD ||
sp->ds_type == DOSPTYP_DFLYBSD) {
log(LOG_WARNING, "%s: cannot find label (%s)\n",
sname, msg);
}
if (lp.opaque)
kfree(lp.opaque, M_DEVBUF);
} else {
set_ds_label(ssp, slice, lp, ops);
set_ds_wlabel(ssp, slice, FALSE);
}
return (msg ? EINVAL : 0);
}
int64_t
dssize(cdev_t dev, struct diskslices **sspp)
{
disklabel_t lp;
disklabel_ops_t ops;
int part;
int slice;
struct diskslices *ssp;
u_int64_t start;
u_int64_t blocks;
slice = dkslice(dev);
part = dkpart(dev);
ssp = *sspp;
if (ssp == NULL || slice >= ssp->dss_nslices
|| !dschkmask(&ssp->dss_slices[slice], part)) {
if (dev_dopen(dev, FREAD, S_IFCHR,
proc0.p_ucred, NULL, NULL) != 0)
{
return (-1);
}
dev_dclose(dev, FREAD, S_IFCHR, NULL);
ssp = *sspp;
}
lp = ssp->dss_slices[slice].ds_label;
if (part == WHOLE_SLICE_PART) {
blocks = ssp->dss_slices[slice].ds_size;
} else if (lp.opaque == NULL) {
blocks = (u_int64_t)-1;
} else {
ops = ssp->dss_slices[slice].ds_ops;
if (ops->op_getpartbounds(ssp, lp, part, &start, &blocks))
return (-1);
}
return ((int64_t)blocks);
}
static void
free_ds_label(struct diskslices *ssp, int slice)
{
struct diskslice *sp;
disklabel_t lp;
sp = &ssp->dss_slices[slice];
lp = sp->ds_label;
if (lp.opaque != NULL) {
kfree(lp.opaque, M_DEVBUF);
lp.opaque = NULL;
set_ds_label(ssp, slice, lp, NULL);
}
}
static void
set_ds_label(struct diskslices *ssp, int slice,
disklabel_t lp, disklabel_ops_t ops)
{
struct diskslice *sp = &ssp->dss_slices[slice];
sp->ds_label = lp;
sp->ds_ops = ops;
if (lp.opaque && slice != WHOLE_DISK_SLICE)
ops->op_adjust_label_reserved(ssp, slice, sp);
else
sp->ds_reserved = 0;
}
static void
set_ds_wlabel(struct diskslices *ssp, int slice, int wlabel)
{
ssp->dss_slices[slice].ds_wlabel = wlabel;
}