#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: disksubr.c,v 1.31 2022/07/05 19:31:04 andvar Exp $");
#include "opt_disksubr.h"
#include <sys/buf.h>
#include <sys/disklabel.h>
#include <sys/bswap.h>
#define SD_C_ADJUSTS_NR
#define baddr(bp) (void *)((bp)->b_data)
static const char *read_dos_label(dev_t, void (*)(struct buf *),
struct disklabel *, struct cpu_disklabel *);
static int getFreeLabelEntry(struct disklabel *);
static int read_netbsd_label(dev_t, void (*)(struct buf *), struct disklabel *,
struct cpu_disklabel *);
#ifdef RDB_PART
static const char *read_rdb_label(dev_t, void (*)(struct buf *),
struct disklabel *, struct cpu_disklabel *);
static u_long rdbchksum(void *);
static struct adostype getadostype(u_long);
#endif
static const char *
read_dos_label(dev_t dev, void (*strat)(struct buf *), struct disklabel *lp,
struct cpu_disklabel *osdep)
{
struct buf *bp;
struct mbr_partition *bsdp, *dp;
const char *msg = NULL;
int i, slot, maxslot = 0;
u_int32_t bsdpartoff;
bp = geteblk((int)lp->d_secsize);
bp->b_dev = dev;
bp->b_blkno = MBR_BBSECTOR;
bp->b_bcount = lp->d_secsize;
bp->b_flags |= B_READ;
bp->b_cylinder = MBR_BBSECTOR / lp->d_secpercyl;
(*strat)(bp);
bsdpartoff = 0;
if (biowait(bp)) {
msg = "dos partition I/O error";
goto done;
}
dp = (struct mbr_partition *)((char *)bp->b_data + MBR_PART_OFFSET);
bsdp = NULL;
for (i = 0; i < MBR_PART_COUNT; i++, dp++) {
switch (dp->mbrp_type) {
case MBR_PTYPE_NETBSD:
bsdp = dp;
break;
case MBR_PTYPE_OPENBSD:
case MBR_PTYPE_386BSD:
if (!bsdp)
bsdp = dp;
break;
}
}
if (!bsdp) {
dp = (struct mbr_partition *)((char *)bp->b_data +
MBR_PART_OFFSET);
for (i = 0; i < MBR_PART_COUNT; i++, dp++) {
if (!dp->mbrp_type)
continue;
slot = getFreeLabelEntry(lp);
if (slot < 0)
break;
if (slot > maxslot)
maxslot = slot;
lp->d_partitions[slot].p_offset =
bswap32(dp->mbrp_start);
lp->d_partitions[slot].p_size = bswap32(dp->mbrp_size);
switch (dp->mbrp_type) {
case MBR_PTYPE_FAT12:
case MBR_PTYPE_FAT16S:
case MBR_PTYPE_FAT16B:
case MBR_PTYPE_FAT32:
case MBR_PTYPE_FAT32L:
case MBR_PTYPE_FAT16L:
lp->d_partitions[slot].p_fstype = FS_MSDOS;
break;
default:
lp->d_partitions[slot].p_fstype = FS_OTHER;
break;
}
}
msg = "no NetBSD disk label";
} else {
bsdpartoff = bswap32(bsdp->mbrp_start);
lp->d_partitions[2].p_size = bswap32(bsdp->mbrp_size);
lp->d_partitions[2].p_offset = bswap32(bsdp->mbrp_start);
if (2 > maxslot)
maxslot = 2;
osdep->cd_labelsector = bsdpartoff + LABELSECTOR;
osdep->cd_labeloffset = LABELOFFSET;
if (read_netbsd_label(dev, strat, lp, osdep))
goto done;
msg = "no NetBSD disk label";
}
lp->d_npartitions = ((maxslot >= RAW_PART) ? maxslot : RAW_PART) + 1;
done:
brelse(bp, 0);
return msg;
}
static int
getFreeLabelEntry(struct disklabel *lp)
{
int i;
for (i = 0; i < MAXPARTITIONS; i++) {
if ((i != RAW_PART)
&& (lp->d_partitions[i].p_fstype == FS_UNUSED))
return i;
}
return -1;
}
#ifdef RDB_PART
static const char *
read_rdb_label(dev_t dev, void (*strat)(struct buf *), struct disklabel *lp,
struct cpu_disklabel *osdep)
{
struct adostype adt;
struct buf *bp;
struct partition *pp = NULL;
struct partblock *pbp;
struct rdblock *rbp;
const char *msg;
char *bcpls, *s, bcpli;
int cindex, i, nopname;
u_long nextb;
osdep->rdblock = RDBNULL;
lp->d_npartitions = RAW_PART + 1;
if (lp->d_partitions[RAW_PART].p_size == 0)
lp->d_partitions[RAW_PART].p_size = 0x1fffffff;
lp->d_partitions[RAW_PART].p_offset = 0;
if (lp->d_partitions[0].p_size == 0) {
lp->d_partitions[0].p_size = lp->d_partitions[RAW_PART].p_size;
lp->d_partitions[0].p_offset = 0;
lp->d_partitions[0].p_fstype = FS_BSDFFS;
lp->d_partitions[0].p_fsize = 1024;
lp->d_partitions[0].p_frag = 8;
lp->d_partitions[0].p_cpg = 0;
}
bp = geteblk((int)lp->d_secsize);
#ifdef _KERNEL
bp->b_dev = MAKEDISKDEV(major(dev), DISKUNIT(dev), RAW_PART);
#else
bp->b_dev = dev;
#endif
msg = NULL;
for (nextb = 0; nextb < RDB_MAXBLOCKS; nextb++) {
bp->b_blkno = nextb;
bp->b_cylinder = bp->b_blkno / lp->d_secpercyl;
bp->b_bcount = lp->d_secsize;
bp->b_oflags &= ~(BO_DONE);
bp->b_flags |= B_READ;
#ifdef SD_C_ADJUSTS_NR
bp->b_blkno *= (lp->d_secsize / DEV_BSIZE);
#endif
(*strat)(bp);
if (biowait(bp)) {
msg = "rdb scan I/O error";
goto done;
}
rbp = baddr(bp);
if (rbp->id == RDBLOCK_ID) {
if (rdbchksum(rbp) == 0)
break;
else
msg = "rdb bad checksum";
}
}
if (nextb == RDB_MAXBLOCKS) {
if (msg == NULL)
msg = "no disk label";
goto done;
} else if (msg != NULL)
msg = NULL;
osdep->rdblock = nextb;
for (i = 0; i < MAXPARTITIONS; i++) {
osdep->pbindex[i] = -1;
osdep->pblist[i] = RDBNULL;
if (i == RAW_PART)
continue;
lp->d_partitions[i].p_size = 0;
lp->d_partitions[i].p_offset = 0;
}
lp->d_secsize = rbp->nbytes;
lp->d_nsectors = rbp->nsectors;
lp->d_ntracks = rbp->nheads;
if (rbp->highcyl == 0)
lp->d_ncylinders = rbp->ncylinders;
else
lp->d_ncylinders = rbp->highcyl + 1;
lp->d_secpercyl = uimin(rbp->secpercyl, lp->d_nsectors * lp->d_ntracks);
if (lp->d_secpercyl == 0)
lp->d_secpercyl = lp->d_nsectors * lp->d_ntracks;
#ifdef DIAGNOSTIC
if (lp->d_ncylinders != rbp->ncylinders)
printf("warning found rdb->ncylinders(%u) != "
"rdb->highcyl(%u) + 1\n", rbp->ncylinders,
rbp->highcyl);
if (lp->d_nsectors * lp->d_ntracks != rbp->secpercyl)
printf("warning found rdb->secpercyl(%u) != "
"rdb->nsectors(%u) * rdb->nheads(%u)\n", rbp->secpercyl,
rbp->nsectors, rbp->nheads);
#endif
lp->d_sparespercyl =
uimax(rbp->secpercyl, lp->d_nsectors * lp->d_ntracks)
- lp->d_secpercyl;
if (lp->d_sparespercyl == 0)
lp->d_sparespertrack = 0;
else {
lp->d_sparespertrack = lp->d_sparespercyl / lp->d_ntracks;
#ifdef DIAGNOSTIC
if (lp->d_sparespercyl % lp->d_ntracks)
printf("warning lp->d_sparespercyl(%u) not multiple "
"of lp->d_ntracks(%u)\n", lp->d_sparespercyl,
lp->d_ntracks);
#endif
}
lp->d_secperunit = lp->d_secpercyl * lp->d_ncylinders;
lp->d_acylinders = rbp->ncylinders - (rbp->highcyl - rbp->lowcyl + 1);
lp->d_rpm = 3600;
lp->d_interleave = rbp->interleave;
lp->d_headswitch = lp->d_flags = lp->d_trackskew = lp->d_cylskew = 0;
lp->d_trkseek = 0;
lp->d_partitions[RAW_PART].p_size = rbp->ncylinders * lp->d_secpercyl;
nopname = 1;
cindex = 0;
for (nextb = rbp->partbhead; nextb != RDBNULL; nextb = pbp->next) {
bp->b_blkno = nextb;
bp->b_cylinder = bp->b_blkno / lp->d_secpercyl;
bp->b_bcount = lp->d_secsize;
bp->b_oflags &= ~(BO_DONE);
bp->b_flags |= B_READ;
#ifdef SD_C_ADJUSTS_NR
bp->b_blkno *= (lp->d_secsize / DEV_BSIZE);
#endif
strat(bp);
if (biowait(bp)) {
msg = "partition scan I/O error";
goto done;
}
pbp = baddr(bp);
if (pbp->id != PARTBLOCK_ID) {
msg = "partition block with bad id";
goto done;
}
if (rdbchksum(pbp)) {
msg = "partition block bad checksum";
goto done;
}
if (pbp->e.tabsize < 11) {
msg = "bad partition info (environ < 11)";
goto done;
}
if (pbp->e.tabsize >= 16)
adt = getadostype(pbp->e.dostype);
else {
adt.archtype = ADT_UNKNOWN;
adt.fstype = FS_UNUSED;
}
switch (adt.archtype) {
case ADT_NETBSDROOT:
pp = &lp->d_partitions[0];
if (pp->p_size) {
#ifdef DIAGNOSTIC
printf("more than one root, ignoring\n");
#endif
osdep->rdblock = RDBNULL;
continue;
}
break;
case ADT_NETBSDSWAP:
pp = &lp->d_partitions[1];
if (pp->p_size) {
#ifdef DIAGNOSTIC
printf("more than one swap, ignoring\n");
#endif
osdep->rdblock = RDBNULL;
continue;
}
break;
case ADT_NETBSDUSER:
case ADT_AMIGADOS:
case ADT_AMIX:
case ADT_EXT2:
case ADT_RAID:
case ADT_MSD:
case ADT_UNKNOWN:
pp = &lp->d_partitions[lp->d_npartitions];
break;
}
if (lp->d_npartitions <= (pp - lp->d_partitions))
lp->d_npartitions = (pp - lp->d_partitions) + 1;
#ifdef DIAGNOSTIC
if (lp->d_secpercyl * lp->d_secsize !=
(pbp->e.secpertrk * pbp->e.numheads * pbp->e.sizeblock<<2)) {
if (pbp->partname[0] < sizeof(pbp->partname))
pbp->partname[pbp->partname[0] + 1] = 0;
else
pbp->partname[sizeof(pbp->partname) - 1] = 0;
printf("Partition '%s' geometry %u/%u differs",
pbp->partname + 1, pbp->e.numheads,
pbp->e.secpertrk);
printf(" from RDB %u/%u=%u\n", lp->d_ntracks,
lp->d_nsectors, lp->d_secpercyl);
}
#endif
while ((pp - lp->d_partitions) > RAW_PART + 1) {
daddr_t boff;
boff = pbp->e.lowcyl * pbp->e.secpertrk
* pbp->e.numheads;
if (boff > (pp - 1)->p_offset)
break;
*pp = *(pp - 1);
pp--;
}
i = (pp - lp->d_partitions);
if (nopname || i == 1) {
s = lp->d_packname;
bcpls = &pbp->partname[1];
bcpli = pbp->partname[0];
if (sizeof(lp->d_packname) <= bcpli)
bcpli = sizeof(lp->d_packname) - 1;
while (bcpli--)
*s++ = *bcpls++;
*s = 0;
nopname = 0;
}
pp->p_size = (pbp->e.highcyl - pbp->e.lowcyl + 1)
* pbp->e.secpertrk * pbp->e.numheads
* ((pbp->e.sizeblock << 2) / lp->d_secsize);
pp->p_offset = pbp->e.lowcyl * pbp->e.secpertrk
* pbp->e.numheads
* ((pbp->e.sizeblock << 2) / lp->d_secsize);
pp->p_fstype = adt.fstype;
if (adt.archtype == ADT_AMIGADOS) {
int bsize, secperblk, minbsize, prefac;
minbsize = uimax(512, lp->d_secsize);
bsize = pbp->e.sizeblock << 2;
secperblk = pbp->e.secperblk;
prefac = pbp->e.prefac;
while (bsize > minbsize) {
bsize >>= 1;
secperblk <<= 1;
prefac <<= 1;
}
if (bsize == minbsize) {
pp->p_fsize = bsize;
pp->p_frag = secperblk;
pp->p_cpg = pbp->e.resvblocks;
pp->p_size -= prefac;
} else {
adt.archtype = ADT_UNKNOWN;
adt.fstype = FS_UNUSED;
}
} else if (pbp->e.tabsize > 22 && ISFSARCH_NETBSD(adt)) {
pp->p_fsize = pbp->e.fsize;
pp->p_frag = pbp->e.frag;
pp->p_cpg = pbp->e.cpg;
} else {
pp->p_fsize = 1024;
pp->p_frag = 8;
pp->p_cpg = 0;
}
osdep->pblist[osdep->pbindex[i] = cindex++] = nextb;
}
lp->d_magic = lp->d_magic2 = DISKMAGIC;
lp->d_checksum = 0;
lp->d_checksum = dkcksum(lp);
if (osdep->rdblock != RDBNULL)
osdep->valid = 1;
done:
if (osdep->valid == 0)
osdep->rdblock = RDBNULL;
brelse(bp, 0);
return msg;
}
static u_long
rdbchksum(void *bdata)
{
u_long *blp, cnt, val;
blp = bdata;
cnt = blp[1];
val = 0;
while (cnt--)
val += *blp++;
return val;
}
static struct adostype
getadostype(u_long dostype)
{
struct adostype adt;
u_long b1, t3;
t3 = dostype & 0xffffff00;
b1 = dostype & 0x000000ff;
adt.fstype = b1;
switch (t3) {
case DOST_NBR:
adt.archtype = ADT_NETBSDROOT;
return adt;
case DOST_NBS:
adt.archtype = ADT_NETBSDSWAP;
return adt;
case DOST_NBU:
adt.archtype = ADT_NETBSDUSER;
return adt;
case DOST_MUFS:
adt.archtype = ADT_AMIGADOS;
adt.fstype = FS_ADOS;
return adt;
case DOST_DOS:
adt.archtype = ADT_AMIGADOS;
if (b1 > 5)
adt.fstype = FS_UNUSED;
else
adt.fstype = FS_ADOS;
return adt;
case DOST_AMIX:
adt.archtype = ADT_AMIX;
if (b1 == 2)
adt.fstype = FS_BSDFFS;
else
adt.fstype = FS_UNUSED;
return adt;
case DOST_XXXBSD:
#ifdef DIAGNOSTIC
printf("found dostype: 0x%lx which is deprecated", dostype);
#endif
if (b1 == 'S') {
dostype = DOST_NBS;
dostype |= FS_SWAP;
} else {
if (b1 == 'R')
dostype = DOST_NBR;
else
dostype = DOST_NBU;
dostype |= FS_BSDFFS;
}
#ifdef DIAGNOSTIC
printf(" using: 0x%lx instead\n", dostype);
#endif
return(getadostype(dostype));
case DOST_EXT2:
adt.archtype = ADT_EXT2;
adt.fstype = FS_EX2FS;
return adt;
case DOST_RAID:
adt.archtype = ADT_RAID;
adt.fstype = FS_RAID;
return adt;
case DOST_MSD:
adt.archtype = ADT_MSD;
adt.fstype = FS_MSDOS;
return adt;
default:
#ifdef DIAGNOSTIC
printf("warning unknown dostype: 0x%lx marking unused\n",
dostype);
#endif
adt.archtype = ADT_UNKNOWN;
adt.fstype = FS_UNUSED;
return adt;
}
}
#endif
static int
read_netbsd_label(dev_t dev, void (*strat)(struct buf *), struct disklabel *lp,
struct cpu_disklabel *osdep)
{
struct buf *bp;
struct disklabel *dlp;
bp = geteblk((int)lp->d_secsize);
bp->b_dev = dev;
bp->b_blkno = osdep->cd_labelsector;
bp->b_bcount = lp->d_secsize;
bp->b_flags |= B_READ;
bp->b_cylinder = bp->b_blkno / (lp->d_secsize / DEV_BSIZE) / lp->d_secpercyl;
(*strat)(bp);
if (biowait(bp))
goto done;
for (dlp = (struct disklabel *)((char *)bp->b_data +
osdep->cd_labeloffset);
dlp <= (struct disklabel *)((char *)bp->b_data + lp->d_secsize -
sizeof (*dlp));
dlp = (struct disklabel *)((char *)dlp + sizeof(long))) {
if (dlp->d_magic == DISKMAGIC
&& dlp->d_magic2 == DISKMAGIC
&& dlp->d_npartitions <= MAXPARTITIONS
&& dkcksum(dlp) == 0) {
*lp = *dlp;
osdep->cd_labeloffset = (char *)dlp -
(char *)bp->b_data;
brelse(bp, 0);
return 1;
}
}
done:
brelse(bp, 0);
return 0;
}
const char *
readdisklabel(dev_t dev, void (*strat)(struct buf *), struct disklabel *lp,
struct cpu_disklabel *osdep)
{
struct buf *bp;
const char *msg = NULL;
if (lp->d_secperunit == 0)
lp->d_secperunit = 0x1fffffff;
if (lp->d_secpercyl == 0) {
return msg = "Zero secpercyl";
}
osdep->rdblock = RDBNULL;
osdep->cd_start = -1;
osdep->cd_labelsector = LABELSECTOR;
osdep->cd_labeloffset = LABELOFFSET;
bp = geteblk((int)lp->d_secsize);
bp->b_dev = dev;
bp->b_blkno = MBR_BBSECTOR;
bp->b_resid = 0;
bp->b_bcount = lp->d_secsize;
bp->b_flags |= B_READ;
bp->b_cylinder = MBR_BBSECTOR / lp->d_secpercyl;
(*strat)(bp);
if (biowait(bp)) {
msg = "I/O error reading block zero";
goto done;
}
if (bswap16(*(u_int16_t *)((char *)bp->b_data + MBR_MAGIC_OFFSET))
== MBR_MAGIC) {
msg = read_dos_label(dev, strat, lp, osdep);
} else {
#ifdef RDB_PART
msg = read_rdb_label(dev, strat, lp, osdep);
#else
msg = "no NetBSD disk label";
#endif
if (msg != NULL) {
if (read_netbsd_label(dev, strat, lp, osdep))
msg = NULL;
}
}
if (msg == NULL)
osdep->cd_start = 0;
done:
brelse(bp, 0);
return msg;
}
int
writedisklabel(dev_t dev, void (*strat)(struct buf *), struct disklabel *lp,
struct cpu_disklabel *osdep)
{
struct buf *bp;
int error;
struct disklabel label;
label = *lp;
readdisklabel(dev, strat, &label, osdep);
if (osdep->rdblock != RDBNULL)
return EINVAL;
bp = geteblk(lp->d_secsize);
bp->b_dev = dev;
bp->b_blkno = osdep->cd_labelsector;
bp->b_cylinder = bp->b_blkno / (lp->d_secsize / DEV_BSIZE) /
lp->d_secpercyl;
bp->b_bcount = lp->d_secsize;
bp->b_flags |= B_READ;
(*strat)(bp);
error = biowait(bp);
if (error != 0)
goto done;
bp->b_flags &= ~B_READ;
bp->b_flags |= B_WRITE;
bp->b_oflags &= ~BO_DONE;
memcpy((char *)bp->b_data + osdep->cd_labeloffset, (void *)lp,
sizeof *lp);
(*strat)(bp);
error = biowait(bp);
done:
brelse(bp, 0);
return error;
}