#include <sys/param.h>
#include <sys/systm.h>
#include <sys/uio.h>
#include <sys/buf.h>
#include <sys/dirent.h>
#include <sys/fcntl.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/mutex.h>
#include <sys/mount.h>
#include <sys/namei.h>
#include <sys/pool.h>
#include <sys/proc.h>
#include <sys/lock.h>
#include <sys/queue.h>
#include <sys/vnode.h>
#include <sys/endian.h>
#include <sys/specdev.h>
#include <crypto/siphash.h>
#include <isofs/udf/ecma167-udf.h>
#include <isofs/udf/udf.h>
#include <isofs/udf/udf_extern.h>
struct pool udf_trans_pool;
struct pool unode_pool;
struct pool udf_ds_pool;
int udf_find_partmaps(struct umount *, struct logvol_desc *);
int udf_get_vpartmap(struct umount *, struct part_map_virt *);
int udf_get_spartmap(struct umount *, struct part_map_spare *);
int udf_get_mpartmap(struct umount *, struct part_map_meta *);
int udf_mountfs(struct vnode *, struct mount *, uint32_t, struct proc *);
const struct vfsops udf_vfsops = {
.vfs_mount = udf_mount,
.vfs_start = udf_start,
.vfs_unmount = udf_unmount,
.vfs_root = udf_root,
.vfs_quotactl = udf_quotactl,
.vfs_statfs = udf_statfs,
.vfs_sync = udf_sync,
.vfs_vget = udf_vget,
.vfs_fhtovp = udf_fhtovp,
.vfs_vptofh = udf_vptofh,
.vfs_init = udf_init,
.vfs_checkexp = udf_checkexp,
};
int
udf_init(struct vfsconf *foo)
{
pool_init(&udf_trans_pool, MAXNAMLEN * sizeof(unicode_t), 0, IPL_NONE,
PR_WAITOK, "udftrpl", NULL);
pool_init(&unode_pool, sizeof(struct unode), 0, IPL_NONE,
PR_WAITOK, "udfndpl", NULL);
pool_init(&udf_ds_pool, sizeof(struct udf_dirstream), 0, IPL_NONE,
PR_WAITOK, "udfdspl", NULL);
return (0);
}
int
udf_start(struct mount *mp, int flags, struct proc *p)
{
return (0);
}
int
udf_mount(struct mount *mp, const char *path, void *data,
struct nameidata *ndp, struct proc *p)
{
struct vnode *devvp;
struct udf_args *args = data;
char fspec[MNAMELEN];
int error;
if ((mp->mnt_flag & MNT_RDONLY) == 0) {
mp->mnt_flag |= MNT_RDONLY;
#ifdef UDF_DEBUG
printf("udf_mount: enforcing read-only mode\n");
#endif
}
if (mp->mnt_flag & MNT_ROOTFS)
return (EOPNOTSUPP);
if (mp->mnt_flag & MNT_UPDATE) {
return (0);
}
error = copyinstr(args->fspec, fspec, sizeof(fspec), NULL);
if (error)
return (error);
NDINIT(ndp, LOOKUP, FOLLOW, UIO_SYSSPACE, fspec, p);
if ((error = namei(ndp)))
return (error);
devvp = ndp->ni_vp;
if (devvp->v_type != VBLK) {
vrele(devvp);
return (ENOTBLK);
}
if (major(devvp->v_rdev) >= nblkdev) {
vrele(devvp);
return (ENXIO);
}
if ((error = udf_mountfs(devvp, mp, args->lastblock, p))) {
vrele(devvp);
return (error);
}
bzero(mp->mnt_stat.f_mntonname, MNAMELEN);
strlcpy(mp->mnt_stat.f_mntonname, path, MNAMELEN);
bzero(mp->mnt_stat.f_mntfromname, MNAMELEN);
strlcpy(mp->mnt_stat.f_mntfromname, fspec, MNAMELEN);
bzero(mp->mnt_stat.f_mntfromspec, MNAMELEN);
strlcpy(mp->mnt_stat.f_mntfromspec, fspec, MNAMELEN);
return (0);
};
int
udf_checktag(struct desc_tag *tag, uint16_t id)
{
uint8_t *itag;
uint8_t i, cksum = 0;
itag = (uint8_t *)tag;
if (letoh16(tag->id) != id)
return (EINVAL);
for (i = 0; i < 15; i++)
cksum = cksum + itag[i];
cksum = cksum - itag[4];
if (cksum == tag->cksum)
return (0);
return (EINVAL);
}
int
udf_mountfs(struct vnode *devvp, struct mount *mp, uint32_t lb, struct proc *p)
{
struct buf *bp = NULL;
struct anchor_vdp avdp;
struct umount *ump = NULL;
struct part_desc *pd;
struct logvol_desc *lvd;
struct fileset_desc *fsd;
struct extfile_entry *xfentry;
struct file_entry *fentry;
uint32_t sector, size, mvds_start, mvds_end;
uint32_t fsd_offset = 0;
uint16_t part_num = 0, fsd_part = 0;
int error = EINVAL;
int logvol_found = 0, part_found = 0, fsd_found = 0;
int bsize;
if ((error = vfs_mountedon(devvp)))
return (error);
if (vcount(devvp) > 1 && devvp != rootvp)
return (EBUSY);
vn_lock(devvp, LK_EXCLUSIVE | LK_RETRY);
error = vinvalbuf(devvp, V_SAVE, p->p_ucred, p, 0, INFSLP);
VOP_UNLOCK(devvp);
if (error)
return (error);
error = VOP_OPEN(devvp, FREAD, FSCRED, p);
if (error)
return (error);
ump = malloc(sizeof(*ump), M_UDFMOUNT, M_WAITOK | M_ZERO);
mp->mnt_data = ump;
mp->mnt_stat.f_fsid.val[0] = devvp->v_rdev;
mp->mnt_stat.f_fsid.val[1] = mp->mnt_vfc->vfc_typenum;
mp->mnt_stat.f_namemax = NAME_MAX;
mp->mnt_flag |= MNT_LOCAL;
ump->um_mountp = mp;
ump->um_dev = devvp->v_rdev;
ump->um_devvp = devvp;
bsize = 2048;
sector = 256;
if ((error = bread(devvp, sector * btodb(bsize), bsize, &bp)) != 0)
goto bail;
if ((error = udf_checktag((struct desc_tag *)bp->b_data, TAGID_ANCHOR)))
goto bail;
bcopy(bp->b_data, &avdp, sizeof(struct anchor_vdp));
brelse(bp);
bp = NULL;
mvds_start = letoh32(avdp.main_vds_ex.loc);
mvds_end = mvds_start + (letoh32(avdp.main_vds_ex.len) - 1) / bsize;
for (sector = mvds_start; sector < mvds_end; sector++) {
if ((error = bread(devvp, sector * btodb(bsize), bsize,
&bp)) != 0) {
printf("Can't read sector %d of VDS\n", sector);
goto bail;
}
lvd = (struct logvol_desc *)bp->b_data;
if (!udf_checktag(&lvd->tag, TAGID_LOGVOL)) {
ump->um_bsize = letoh32(lvd->lb_size);
ump->um_bmask = ump->um_bsize - 1;
ump->um_bshift = ffs(ump->um_bsize) - 1;
fsd_part = letoh16(lvd->_lvd_use.fsd_loc.loc.part_num);
fsd_offset = letoh32(lvd->_lvd_use.fsd_loc.loc.lb_num);
if (udf_find_partmaps(ump, lvd))
break;
logvol_found = 1;
}
pd = (struct part_desc *)bp->b_data;
if (!udf_checktag(&pd->tag, TAGID_PARTITION)) {
part_found = 1;
part_num = letoh16(pd->part_num);
ump->um_len = ump->um_reallen = letoh32(pd->part_len);
ump->um_start = ump->um_realstart = letoh32(pd->start_loc);
}
brelse(bp);
bp = NULL;
if ((part_found) && (logvol_found))
break;
}
if (!part_found || !logvol_found) {
error = EINVAL;
goto bail;
}
if (ISSET(ump->um_flags, UDF_MNT_USES_META)) {
struct long_ad *la;
sector = ump->um_start + ump->um_meta_start;
if ((error = RDSECTOR(devvp, sector, ump->um_bsize, &bp)) != 0) {
printf("Cannot read sector %d for Metadata File Entry\n", sector);
error = EINVAL;
goto bail;
}
xfentry = (struct extfile_entry *)bp->b_data;
fentry = (struct file_entry *)bp->b_data;
if (udf_checktag(&xfentry->tag, TAGID_EXTFENTRY) == 0)
la = (struct long_ad *)&xfentry->data[letoh32(xfentry->l_ea)];
else if (udf_checktag(&fentry->tag, TAGID_FENTRY) == 0)
la = (struct long_ad *)&fentry->data[letoh32(fentry->l_ea)];
else {
printf("Invalid Metadata File FE @ sector %d! (tag.id %d)\n",
sector, fentry->tag.id);
error = EINVAL;
goto bail;
}
ump->um_meta_start = letoh32(la->loc.lb_num);
ump->um_meta_len = letoh32(la->len);
if (bp != NULL) {
brelse(bp);
bp = NULL;
}
} else if (fsd_part != part_num) {
printf("FSD does not lie within the partition!\n");
error = EINVAL;
goto bail;
}
mtx_init(&ump->um_hashmtx, IPL_NONE);
ump->um_hashtbl = hashinit(UDF_HASHTBLSIZE, M_UDFMOUNT, M_WAITOK,
&ump->um_hashsz);
arc4random_buf(&ump->um_hashkey, sizeof(ump->um_hashkey));
if (ump->um_flags & UDF_MNT_FIND_VAT) {
error = udf_vat_get(ump, lb);
if (error)
goto bail;
}
if (ISSET(ump->um_flags, UDF_MNT_USES_META))
sector = ump->um_meta_start;
else
sector = fsd_offset;
udf_vat_map(ump, §or);
if ((error = RDSECTOR(devvp, sector, ump->um_bsize, &bp)) != 0) {
printf("Cannot read sector %d of FSD\n", sector);
goto bail;
}
fsd = (struct fileset_desc *)bp->b_data;
if (!udf_checktag(&fsd->tag, TAGID_FSD)) {
fsd_found = 1;
bcopy(&fsd->rootdir_icb, &ump->um_root_icb,
sizeof(struct long_ad));
if (ISSET(ump->um_flags, UDF_MNT_USES_META)) {
ump->um_root_icb.loc.lb_num += ump->um_meta_start;
ump->um_root_icb.loc.part_num = part_num;
}
}
brelse(bp);
bp = NULL;
if (!fsd_found) {
printf("Couldn't find the fsd\n");
error = EINVAL;
goto bail;
}
sector = letoh32(ump->um_root_icb.loc.lb_num);
size = letoh32(ump->um_root_icb.len);
udf_vat_map(ump, §or);
if ((error = udf_readlblks(ump, sector, size, &bp)) != 0) {
printf("Cannot read sector %d\n", sector);
goto bail;
}
xfentry = (struct extfile_entry *)bp->b_data;
fentry = (struct file_entry *)bp->b_data;
error = udf_checktag(&xfentry->tag, TAGID_EXTFENTRY);
if (error) {
error = udf_checktag(&fentry->tag, TAGID_FENTRY);
if (error) {
printf("Invalid root file entry!\n");
goto bail;
}
}
brelse(bp);
bp = NULL;
devvp->v_specmountpoint = mp;
return (0);
bail:
if (ump != NULL) {
hashfree(ump->um_hashtbl, UDF_HASHTBLSIZE, M_UDFMOUNT);
free(ump, M_UDFMOUNT, 0);
mp->mnt_data = NULL;
mp->mnt_flag &= ~MNT_LOCAL;
}
if (devvp->v_specinfo)
devvp->v_specmountpoint = NULL;
if (bp != NULL)
brelse(bp);
vn_lock(devvp, LK_EXCLUSIVE|LK_RETRY);
VOP_CLOSE(devvp, FREAD, FSCRED, p);
VOP_UNLOCK(devvp);
return (error);
}
int
udf_unmount(struct mount *mp, int mntflags, struct proc *p)
{
struct umount *ump;
struct vnode *devvp;
int error, flags = 0;
ump = VFSTOUDFFS(mp);
devvp = ump->um_devvp;
if (mntflags & MNT_FORCE)
flags |= FORCECLOSE;
if ((error = vflush(mp, NULL, flags)))
return (error);
vn_lock(devvp, LK_EXCLUSIVE | LK_RETRY);
vinvalbuf(devvp, V_SAVE, NOCRED, p, 0, INFSLP);
(void)VOP_CLOSE(devvp, FREAD, NOCRED, p);
VOP_UNLOCK(devvp);
devvp->v_specmountpoint = NULL;
vrele(devvp);
if (ump->um_flags & UDF_MNT_USES_VAT)
free(ump->um_vat, M_UDFMOUNT, 0);
if (ump->um_stbl != NULL)
free(ump->um_stbl, M_UDFMOUNT, 0);
hashfree(ump->um_hashtbl, UDF_HASHTBLSIZE, M_UDFMOUNT);
free(ump, M_UDFMOUNT, 0);
mp->mnt_data = NULL;
mp->mnt_flag &= ~MNT_LOCAL;
return (0);
}
int
udf_root(struct mount *mp, struct vnode **vpp)
{
struct umount *ump;
struct vnode *vp;
udfino_t id;
int error;
ump = VFSTOUDFFS(mp);
id = udf_getid(&ump->um_root_icb);
error = udf_vget(mp, id, vpp);
if (error)
return (error);
vp = *vpp;
vp->v_flag |= VROOT;
return (0);
}
int
udf_quotactl(struct mount *mp, int cmds, uid_t uid, caddr_t arg,
struct proc *p)
{
return (EOPNOTSUPP);
}
int
udf_statfs(struct mount *mp, struct statfs *sbp, struct proc *p)
{
struct umount *ump;
ump = VFSTOUDFFS(mp);
sbp->f_bsize = ump->um_bsize;
sbp->f_iosize = ump->um_bsize;
sbp->f_blocks = ump->um_len;
sbp->f_bfree = 0;
sbp->f_bavail = 0;
sbp->f_files = 0;
sbp->f_ffree = 0;
sbp->f_favail = 0;
copy_statfs_info(sbp, mp);
return (0);
}
int
udf_sync(struct mount *mp, int waitfor, int stall, struct ucred *cred, struct proc *p)
{
return (0);
}
int
udf_vget(struct mount *mp, ino_t ino, struct vnode **vpp)
{
struct buf *bp;
struct vnode *devvp;
struct umount *ump;
struct proc *p;
struct vnode *vp, *nvp;
struct unode *up;
struct extfile_entry *xfe;
struct file_entry *fe;
uint32_t sector;
int error, size;
if (ino > (udfino_t)-1)
panic("udf_vget: alien ino_t %llu", (unsigned long long)ino);
p = curproc;
bp = NULL;
*vpp = NULL;
ump = VFSTOUDFFS(mp);
if ((error = udf_hashlookup(ump, ino, LK_EXCLUSIVE, vpp)) != 0)
return (error);
if (*vpp != NULL)
return (0);
up = pool_get(&unode_pool, PR_WAITOK | PR_ZERO);
sector = ino;
devvp = ump->um_devvp;
udf_vat_map(ump, §or);
if ((error = RDSECTOR(devvp, sector, ump->um_bsize, &bp)) != 0) {
printf("Cannot read sector %d\n", sector);
pool_put(&unode_pool, up);
if (bp != NULL)
brelse(bp);
return (error);
}
xfe = (struct extfile_entry *)bp->b_data;
fe = (struct file_entry *)bp->b_data;
error = udf_checktag(&xfe->tag, TAGID_EXTFENTRY);
if (error == 0) {
size = letoh32(xfe->l_ea) + letoh32(xfe->l_ad);
} else {
error = udf_checktag(&fe->tag, TAGID_FENTRY);
if (error) {
printf("Invalid file entry!\n");
pool_put(&unode_pool, up);
if (bp != NULL)
brelse(bp);
return (ENOMEM);
} else
size = letoh32(fe->l_ea) + letoh32(fe->l_ad);
}
up->u_fentry = malloc(size + UDF_EXTFENTRY_SIZE, M_UDFFENTRY, M_NOWAIT | M_ZERO);
if (up->u_fentry == NULL) {
pool_put(&unode_pool, up);
if (bp != NULL)
brelse(bp);
return (ENOMEM);
}
if (udf_checktag(&xfe->tag, TAGID_EXTFENTRY) == 0)
bcopy(bp->b_data, up->u_fentry, size + UDF_EXTFENTRY_SIZE);
else
bcopy(bp->b_data, up->u_fentry, size + UDF_FENTRY_SIZE);
brelse(bp);
bp = NULL;
if ((error = udf_allocv(mp, &vp, p))) {
free(up->u_fentry, M_UDFFENTRY, 0);
pool_put(&unode_pool, up);
return (error);
}
up->u_vnode = vp;
up->u_ino = ino;
up->u_devvp = ump->um_devvp;
up->u_dev = ump->um_dev;
up->u_ump = ump;
vp->v_data = up;
vref(ump->um_devvp);
rrw_init_flags(&up->u_lock, "unode", RWL_DUPOK | RWL_IS_VNODE);
udf_hashins(up);
switch (up->u_fentry->icbtag.file_type) {
default:
printf("Unrecognized file type (%d)\n", vp->v_type);
vp->v_type = VREG;
break;
case UDF_ICB_FILETYPE_DIRECTORY:
vp->v_type = VDIR;
break;
case UDF_ICB_FILETYPE_BLOCKDEVICE:
vp->v_type = VBLK;
break;
case UDF_ICB_FILETYPE_CHARDEVICE:
vp->v_type = VCHR;
break;
case UDF_ICB_FILETYPE_FIFO:
vp->v_type = VFIFO;
break;
case UDF_ICB_FILETYPE_SOCKET:
vp->v_type = VSOCK;
break;
case UDF_ICB_FILETYPE_SYMLINK:
vp->v_type = VLNK;
break;
case UDF_ICB_FILETYPE_RANDOMACCESS:
case UDF_ICB_FILETYPE_REALTIME:
case UDF_ICB_FILETYPE_UNKNOWN:
vp->v_type = VREG;
break;
}
if ((nvp = checkalias(vp, up->u_dev, ump->um_mountp)) != NULL) {
printf("found a vnode alias\n");
nvp->v_data = vp->v_data;
vp->v_data = NULL;
vp->v_op = &spec_vops;
vrele(vp);
vgone(vp);
vp = nvp;
ump->um_devvp = vp;
}
*vpp = vp;
return (0);
}
struct ifid {
u_short ifid_len;
u_short ifid_pad;
int ifid_ino;
long ifid_start;
};
int
udf_fhtovp(struct mount *mp, struct fid *fhp, struct vnode **vpp)
{
struct ifid *ifhp;
struct vnode *nvp;
int error;
ifhp = (struct ifid *)fhp;
if ((error = VFS_VGET(mp, ifhp->ifid_ino, &nvp)) != 0) {
*vpp = NULL;
return (error);
}
*vpp = nvp;
return (0);
}
int
udf_vptofh(struct vnode *vp, struct fid *fhp)
{
struct unode *up;
struct ifid *ifhp;
up = VTOU(vp);
ifhp = (struct ifid *)fhp;
ifhp->ifid_len = sizeof(struct ifid);
ifhp->ifid_ino = up->u_ino;
return (0);
}
int
udf_checkexp(struct mount *mp, struct mbuf *nam, int *exflagsp,
struct ucred **credanonp)
{
return (EACCES);
}
int
udf_get_vpartmap(struct umount *ump, struct part_map_virt *pmv)
{
ump->um_flags |= UDF_MNT_FIND_VAT;
return (0);
}
int
udf_get_spartmap(struct umount *ump, struct part_map_spare *pms)
{
struct buf *bp;
int i, error;
ump->um_stbl = malloc(letoh32(pms->st_size), M_UDFMOUNT, M_NOWAIT);
if (ump->um_stbl == NULL)
return (ENOMEM);
bzero(ump->um_stbl, letoh32(pms->st_size));
ump->um_psecs = letoh16(pms->packet_len) / ump->um_bsize;
error = udf_readlblks(ump, letoh32(pms->st_loc[0]),
letoh32(pms->st_size), &bp);
if (error) {
if (bp != NULL)
brelse(bp);
free(ump->um_stbl, M_UDFMOUNT, 0);
return (error);
}
bcopy(bp->b_data, ump->um_stbl, letoh32(pms->st_size));
brelse(bp);
bp = NULL;
if (udf_checktag(&ump->um_stbl->tag, 0)) {
free(ump->um_stbl, M_UDFMOUNT, 0);
return (EINVAL);
}
for (i = 0; i < letoh16(ump->um_stbl->rt_l); i++) {
ump->um_stbl_len = i;
if (letoh32(ump->um_stbl->entries[i].org) >= 0xfffffff0)
break;
}
return (0);
}
int
udf_get_mpartmap(struct umount *ump, struct part_map_meta *pmm)
{
ump->um_flags |= UDF_MNT_USES_META;
ump->um_meta_start = pmm->meta_file_lbn;
return (0);
}
int
udf_find_partmaps(struct umount *ump, struct logvol_desc *lvd)
{
struct regid *pmap_id;
unsigned char regid_id[UDF_REGID_ID_SIZE + 1];
int i, ptype, psize, error;
uint8_t *pmap = (uint8_t *) &lvd->maps[0];
for (i = 0; i < letoh32(lvd->n_pm); i++) {
ptype = pmap[0];
psize = pmap[1];
if (ptype != 1 && ptype != 2)
return (EINVAL);
if (psize != sizeof(struct part_map_1) &&
psize != sizeof(struct part_map_2))
return (EINVAL);
if (ptype == 1) {
pmap += sizeof(struct part_map_1);
continue;
}
pmap_id = (struct regid *) &pmap[4];
regid_id[UDF_REGID_ID_SIZE] = '\0';
bcopy(&pmap_id->id[0], ®id_id[0], UDF_REGID_ID_SIZE);
if (!bcmp(®id_id[0], "*UDF Virtual Partition",
UDF_REGID_ID_SIZE))
error = udf_get_vpartmap(ump,
(struct part_map_virt *) pmap);
else if (!bcmp(®id_id[0], "*UDF Sparable Partition",
UDF_REGID_ID_SIZE))
error = udf_get_spartmap(ump,
(struct part_map_spare *) pmap);
else if (!bcmp(®id_id[0], "*UDF Metadata Partition",
UDF_REGID_ID_SIZE))
error = udf_get_mpartmap(ump,
(struct part_map_meta *) pmap);
else
return (EINVAL);
if (error)
return (error);
pmap += sizeof(struct part_map_2);
}
return (0);
}