#include <sys/types.h>
#include <sys/t_lock.h>
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/cred.h>
#include <sys/user.h>
#include <sys/vfs.h>
#include <sys/stat.h>
#include <sys/vnode.h>
#include <sys/mode.h>
#include <sys/dnlc.h>
#include <sys/cmn_err.h>
#include <sys/fbuf.h>
#include <sys/kmem.h>
#include <sys/policy.h>
#include <sys/sunddi.h>
#include <vm/hat.h>
#include <vm/as.h>
#include <vm/pvn.h>
#include <vm/seg.h>
#include <vm/seg_map.h>
#include <vm/seg_kmem.h>
#include <vm/page.h>
#include <sys/fs/hsfs_spec.h>
#include <sys/fs/hsfs_isospec.h>
#include <sys/fs/hsfs_node.h>
#include <sys/fs/hsfs_impl.h>
#include <sys/fs/hsfs_susp.h>
#include <sys/fs/hsfs_rrip.h>
#include <sys/sysinfo.h>
#include <sys/sysmacros.h>
#include <sys/errno.h>
#include <sys/debug.h>
#include <fs/fs_subr.h>
#define CAN_TRUNCATE_DOT(name, namelen) \
(namelen > 1 && (namelen > 2 || name[0] != '.'))
enum dirblock_result { FOUND_ENTRY, WENT_PAST, HIT_END };
int ide_prohibited = IDE_PROHIBITED;
int hde_prohibited = HDE_PROHIBITED;
static int hsfs_use_dnlc = 1;
static int strict_iso9660_ordering = 0;
int use_rrip_inodes = 1;
static void hs_hsnode_cache_reclaim(void *unused);
static void hs_addfreeb(struct hsfs *fsp, struct hsnode *hp);
static enum dirblock_result process_dirblock(struct fbuf *fbp, uint_t *offset,
uint_t last_offset, char *nm, int nmlen, struct hsfs *fsp,
struct hsnode *dhp, struct vnode *dvp, struct vnode **vpp,
int *error);
static int strip_trailing(struct hsfs *fsp, char *nm, int len);
static int hs_namelen(struct hsfs *fsp, char *nm, int len);
static int uppercase_cp(char *from, char *to, int size);
static void hs_log_bogus_joliet_warning(void);
static int hs_iso_copy(char *from, char *to, int size);
static int32_t hs_ucs2_2_utf8(uint16_t c_16, uint8_t *s_8);
static int hs_utf8_trunc(uint8_t *str, int len);
int
hs_access(struct vnode *vp, mode_t m, struct cred *cred)
{
struct hsnode *hp;
int shift = 0;
if ((m & VWRITE) && !IS_DEVVP(vp))
return (EROFS);
hp = VTOH(vp);
if ((vp->v_type == VDIR) && (m & VEXEC)) {
m &= ~VEXEC;
m |= VREAD;
}
if (crgetuid(cred) != hp->hs_dirent.uid) {
shift += 3;
if (!groupmember((uid_t)hp->hs_dirent.gid, cred))
shift += 3;
}
return (secpolicy_vnode_access2(cred, vp, hp->hs_dirent.uid,
hp->hs_dirent.mode << shift, m));
}
#if ((HS_HASHSIZE & (HS_HASHSIZE - 1)) == 0)
#define HS_HASH(l) ((uint_t)(l) & (HS_HASHSIZE - 1))
#else
#define HS_HASH(l) ((uint_t)(l) % HS_HASHSIZE)
#endif
#define HS_HPASH(hp) HS_HASH((hp)->hs_nodeid)
int nhsnode = HS_HSNODESPACE / sizeof (struct hsnode);
struct kmem_cache *hsnode_cache;
void
hs_init_hsnode_cache(void)
{
hsnode_cache = kmem_cache_create("hsfs_hsnode_cache",
sizeof (struct hsnode), 0, NULL,
NULL, hs_hsnode_cache_reclaim, NULL, NULL, 0);
}
void
hs_fini_hsnode_cache(void)
{
kmem_cache_destroy(hsnode_cache);
}
static void
hs_hsnode_cache_reclaim(void *unused)
{
struct hsfs *fsp;
struct hsnode *hp;
mutex_enter(&hs_mounttab_lock);
for (fsp = hs_mounttab; fsp != NULL; fsp = fsp->hsfs_next) {
(void) dnlc_purge_vfsp(fsp->hsfs_vfs, 0);
rw_enter(&fsp->hsfs_hash_lock, RW_WRITER);
mutex_enter(&fsp->hsfs_free_lock);
for (hp = fsp->hsfs_free_f; hp != NULL; hp = fsp->hsfs_free_f) {
fsp->hsfs_free_f = hp->hs_freef;
if (fsp->hsfs_free_f != NULL) {
fsp->hsfs_free_f->hs_freeb = NULL;
} else {
fsp->hsfs_free_b = NULL;
}
hs_freenode(HTOV(hp), fsp, 1);
}
mutex_exit(&fsp->hsfs_free_lock);
rw_exit(&fsp->hsfs_hash_lock);
}
mutex_exit(&hs_mounttab_lock);
}
static void
hs_addfreeb(struct hsfs *fsp, struct hsnode *hp)
{
struct hsnode *ep;
vn_invalid(HTOV(hp));
mutex_enter(&fsp->hsfs_free_lock);
ep = fsp->hsfs_free_b;
fsp->hsfs_free_b = hp;
hp->hs_freef = NULL;
hp->hs_freeb = ep;
if (ep == NULL)
fsp->hsfs_free_f = hp;
else
ep->hs_freef = hp;
mutex_exit(&fsp->hsfs_free_lock);
}
static struct hsnode *
hs_getfree(struct hsfs *fsp)
{
struct hsnode *hp, **tp;
ASSERT(RW_WRITE_HELD(&fsp->hsfs_hash_lock));
mutex_enter(&fsp->hsfs_free_lock);
if ((fsp->hsfs_nohsnode < nhsnode) || (fsp->hsfs_free_f == NULL)) {
mutex_exit(&fsp->hsfs_free_lock);
hp = kmem_cache_alloc(hsnode_cache, KM_SLEEP);
fsp->hsfs_nohsnode++;
bzero((caddr_t)hp, sizeof (*hp));
hp->hs_vnode = vn_alloc(KM_SLEEP);
return (hp);
}
hp = fsp->hsfs_free_f;
fsp->hsfs_free_f = hp->hs_freef;
if (fsp->hsfs_free_f != NULL)
fsp->hsfs_free_f->hs_freeb = NULL;
else
fsp->hsfs_free_b = NULL;
mutex_exit(&fsp->hsfs_free_lock);
for (tp = &fsp->hsfs_hash[HS_HPASH(hp)]; *tp != NULL;
tp = &(*tp)->hs_hash) {
if (*tp == hp) {
struct vnode *vp;
vp = HTOV(hp);
if (vn_has_cached_data(vp))
(void) pvn_vplist_dirty(vp, (u_offset_t)0,
hsfs_putapage, B_INVAL,
(struct cred *)NULL);
*tp = hp->hs_hash;
break;
}
}
if (hp->hs_dirent.sym_link != (char *)NULL) {
kmem_free(hp->hs_dirent.sym_link,
(size_t)(hp->hs_dirent.ext_size + 1));
}
mutex_destroy(&hp->hs_contents_lock);
{
vnode_t *vp;
vp = hp->hs_vnode;
bzero((caddr_t)hp, sizeof (*hp));
hp->hs_vnode = vp;
vn_reinit(vp);
}
return (hp);
}
static void
hs_remfree(struct hsfs *fsp, struct hsnode *hp)
{
mutex_enter(&fsp->hsfs_free_lock);
if (hp->hs_freef != NULL)
hp->hs_freef->hs_freeb = hp->hs_freeb;
else
fsp->hsfs_free_b = hp->hs_freeb;
if (hp->hs_freeb != NULL)
hp->hs_freeb->hs_freef = hp->hs_freef;
else
fsp->hsfs_free_f = hp->hs_freef;
mutex_exit(&fsp->hsfs_free_lock);
}
struct vnode *
hs_findhash(ino64_t nodeid, uint_t lbn, uint_t off, struct vfs *vfsp)
{
struct hsnode *tp;
struct hsfs *fsp;
fsp = VFS_TO_HSFS(vfsp);
ASSERT(RW_LOCK_HELD(&fsp->hsfs_hash_lock));
for (tp = fsp->hsfs_hash[HS_HASH(nodeid)]; tp != NULL;
tp = tp->hs_hash) {
if (tp->hs_nodeid == nodeid) {
struct vnode *vp;
if (nodeid == HS_DUMMY_INO) {
for (; tp != NULL; tp = tp->hs_hash) {
if (tp->hs_nodeid == nodeid &&
tp->hs_dir_lbn == lbn &&
tp->hs_dir_off == off)
break;
}
if (tp == NULL)
return (NULL);
}
mutex_enter(&tp->hs_contents_lock);
vp = HTOV(tp);
VN_HOLD(vp);
if ((tp->hs_flags & HREF) == 0) {
tp->hs_flags |= HREF;
hs_remfree(fsp, tp);
}
mutex_exit(&tp->hs_contents_lock);
return (vp);
}
}
return (NULL);
}
static void
hs_addhash(struct hsfs *fsp, struct hsnode *hp)
{
ulong_t hashno;
ASSERT(RW_WRITE_HELD(&fsp->hsfs_hash_lock));
hashno = HS_HPASH(hp);
hp->hs_hash = fsp->hsfs_hash[hashno];
fsp->hsfs_hash[hashno] = hp;
}
int
hs_synchash(struct vfs *vfsp)
{
struct hsfs *fsp;
int i;
struct hsnode *hp, *nhp;
int busy = 0;
struct vnode *vp, *rvp;
fsp = VFS_TO_HSFS(vfsp);
rvp = fsp->hsfs_rootvp;
rw_enter(&fsp->hsfs_hash_lock, RW_WRITER);
for (i = 0; i < HS_HASHSIZE; i++) {
for (hp = fsp->hsfs_hash[i]; hp != NULL; hp = hp->hs_hash) {
vp = HTOV(hp);
if ((hp->hs_flags & HREF) && (vp != rvp ||
(vp == rvp && vp->v_count > 1))) {
busy = 1;
continue;
}
if (vn_has_cached_data(vp))
(void) pvn_vplist_dirty(vp, (u_offset_t)0,
hsfs_putapage, B_INVAL,
(struct cred *)NULL);
}
}
if (busy) {
rw_exit(&fsp->hsfs_hash_lock);
return (1);
}
for (i = 0; i < HS_HASHSIZE; i++) {
for (hp = fsp->hsfs_hash[i]; hp != NULL; hp = nhp) {
nhp = hp->hs_hash;
vp = HTOV(hp);
if (vp != rvp) {
hs_remfree(fsp, hp);
hs_freenode(vp, fsp, 1);
}
}
}
ASSERT(fsp->hsfs_nohsnode == 1);
rw_exit(&fsp->hsfs_hash_lock);
VN_RELE(rvp);
return (0);
}
struct vnode *
hs_makenode(
struct hs_direntry *dp,
uint_t lbn,
uint_t off,
struct vfs *vfsp)
{
struct hsnode *hp;
struct vnode *vp;
struct hs_volume *hvp;
struct vnode *newvp;
struct hsfs *fsp;
ino64_t nodeid;
fsp = VFS_TO_HSFS(vfsp);
if (dp->type == VDIR) {
lbn = dp->ext_lbn;
off = 0;
}
hvp = &fsp->hsfs_vol;
lbn += off >> hvp->lbn_shift;
off &= hvp->lbn_maxoffset;
if (dp->inode != 0 && use_rrip_inodes) {
nodeid = dp->inode;
} else if ((dp->ext_size == 0 || dp->sym_link != (char *)NULL) &&
(fsp->hsfs_flags & HSFSMNT_INODE) == 0) {
nodeid = HS_DUMMY_INO;
} else {
nodeid = dp->ext_lbn;
}
rw_enter(&fsp->hsfs_hash_lock, RW_READER);
if ((vp = hs_findhash(nodeid, lbn, off, vfsp)) == NULL) {
rw_exit(&fsp->hsfs_hash_lock);
rw_enter(&fsp->hsfs_hash_lock, RW_WRITER);
if ((vp = hs_findhash(nodeid, lbn, off, vfsp)) == NULL) {
hp = hs_getfree(fsp);
bcopy((caddr_t)dp, (caddr_t)&hp->hs_dirent,
sizeof (*dp));
dp->sym_link = (char *)NULL;
mutex_init(&hp->hs_contents_lock, NULL, MUTEX_DEFAULT,
NULL);
hp->hs_dir_lbn = lbn;
hp->hs_dir_off = off;
hp->hs_nodeid = nodeid;
hp->hs_seq = 0;
hp->hs_prev_offset = 0;
hp->hs_num_contig = 0;
hp->hs_ra_bytes = 0;
hp->hs_flags = HREF;
if (off > HS_SECTOR_SIZE)
cmn_err(CE_WARN, "hs_makenode: bad offset");
vp = HTOV(hp);
vp->v_vfsp = vfsp;
vp->v_type = dp->type;
vp->v_rdev = dp->r_dev;
vn_setops(vp, hsfs_vnodeops);
vp->v_data = (caddr_t)hp;
vn_exists(vp);
if (IS_DEVVP(vp)) {
rw_exit(&fsp->hsfs_hash_lock);
newvp = specvp(vp, vp->v_rdev, vp->v_type,
CRED());
if (newvp == NULL)
cmn_err(CE_NOTE,
"hs_makenode: specvp failed");
VN_RELE(vp);
return (newvp);
}
hs_addhash(fsp, hp);
}
}
if (dp->sym_link != (char *)NULL) {
kmem_free(dp->sym_link, (size_t)(dp->ext_size + 1));
dp->sym_link = (char *)NULL;
}
rw_exit(&fsp->hsfs_hash_lock);
return (vp);
}
void
hs_freenode(vnode_t *vp, struct hsfs *fsp, int nopage)
{
struct hsnode **tp;
struct hsnode *hp = VTOH(vp);
ASSERT(RW_LOCK_HELD(&fsp->hsfs_hash_lock));
if (nopage || (fsp->hsfs_nohsnode >= nhsnode)) {
for (tp = &fsp->hsfs_hash[HS_HPASH(hp)]; *tp != NULL;
tp = &(*tp)->hs_hash) {
if (*tp == hp) {
*tp = hp->hs_hash;
break;
}
}
if (hp->hs_dirent.sym_link != (char *)NULL) {
kmem_free(hp->hs_dirent.sym_link,
(size_t)(hp->hs_dirent.ext_size + 1));
hp->hs_dirent.sym_link = NULL;
}
if (vn_has_cached_data(vp)) {
(void) pvn_vplist_dirty(vp, (u_offset_t)0,
hsfs_putapage, B_INVAL, (struct cred *)NULL);
vp->v_pages = NULL;
}
mutex_destroy(&hp->hs_contents_lock);
vn_invalid(vp);
vn_free(vp);
kmem_cache_free(hsnode_cache, hp);
fsp->hsfs_nohsnode--;
return;
}
hs_addfreeb(fsp, hp);
}
int
hs_remakenode(uint_t lbn, uint_t off, struct vfs *vfsp,
struct vnode **vpp)
{
struct buf *secbp;
struct hsfs *fsp;
uint_t secno;
uchar_t *dirp;
struct hs_direntry hd;
int error;
fsp = VFS_TO_HSFS(vfsp);
if (off > HS_SECTOR_SIZE) {
cmn_err(CE_WARN, "hs_remakenode: bad offset");
error = EINVAL;
goto end;
}
secno = LBN_TO_SEC(lbn, vfsp);
secbp = bread(fsp->hsfs_devvp->v_rdev, secno * 4, HS_SECTOR_SIZE);
error = geterror(secbp);
if (error != 0) {
cmn_err(CE_NOTE, "hs_remakenode: bread: error=(%d)", error);
goto end;
}
dirp = (uchar_t *)secbp->b_un.b_addr;
error = hs_parsedir(fsp, &dirp[off], &hd, (char *)NULL, (int *)NULL,
HS_SECTOR_SIZE - off);
if (!error) {
*vpp = hs_makenode(&hd, lbn, off, vfsp);
if (*vpp == NULL)
error = ENFILE;
}
end:
brelse(secbp);
return (error);
}
int
hs_dirlook(
struct vnode *dvp,
char *name,
int namlen,
struct vnode **vpp,
struct cred *cred)
{
struct hsnode *dhp;
struct hsfs *fsp;
int error = 0;
uint_t offset;
uint_t last_offset;
char *cmpname;
int cmpname_size;
int cmpnamelen;
int adhoc_search;
int end;
uint_t hsoffset;
struct fbuf *fbp;
int bytes_wanted;
int dirsiz;
int is_rrip;
if (dvp->v_type != VDIR)
return (ENOTDIR);
if (error = hs_access(dvp, (mode_t)VEXEC, cred))
return (error);
if (hsfs_use_dnlc && (*vpp = dnlc_lookup(dvp, name)))
return (0);
dhp = VTOH(dvp);
fsp = VFS_TO_HSFS(dvp->v_vfsp);
is_rrip = IS_RRIP_IMPLEMENTED(fsp);
if (!is_rrip && *name == '\1' && namlen == 1)
return (EINVAL);
cmpname_size = (int)(fsp->hsfs_namemax + 1);
cmpname = kmem_alloc((size_t)cmpname_size, KM_SLEEP);
if (namlen >= cmpname_size)
namlen = cmpname_size - 1;
if (is_rrip) {
(void) strlcpy(cmpname, name, cmpname_size);
cmpnamelen = namlen;
} else {
if ((fsp->hsfs_flags & HSFSMNT_NOTRAILDOT) &&
name[namlen-1] == '.' &&
CAN_TRUNCATE_DOT(name, namlen))
name[--namlen] = '\0';
if (fsp->hsfs_vol_type == HS_VOL_TYPE_ISO_V2 ||
fsp->hsfs_vol_type == HS_VOL_TYPE_JOLIET) {
cmpnamelen = hs_iso_copy(name, cmpname, namlen);
} else {
cmpnamelen = hs_uppercase_copy(name, cmpname, namlen);
}
}
if (dhp->hs_dirent.ext_size == 0)
hs_filldirent(dvp, &dhp->hs_dirent);
offset = dhp->hs_offset;
hsoffset = offset;
adhoc_search = (offset != 0);
end = dhp->hs_dirent.ext_size;
dirsiz = end;
tryagain:
while (offset < end) {
bytes_wanted = MIN(MAXBSIZE, dirsiz - (offset & MAXBMASK));
error = fbread(dvp, (offset_t)(offset & MAXBMASK),
(unsigned int)bytes_wanted, S_READ, &fbp);
if (error)
goto done;
last_offset = (offset & MAXBMASK) + fbp->fb_count;
switch (process_dirblock(fbp, &offset, last_offset,
cmpname, cmpnamelen, fsp, dhp, dvp, vpp, &error)) {
case FOUND_ENTRY:
goto done;
case WENT_PAST:
if (adhoc_search) {
offset = 0;
end = hsoffset;
adhoc_search = 0;
goto tryagain;
}
error = ENOENT;
goto done;
case HIT_END:
goto tryagain;
}
}
if (adhoc_search) {
offset = 0;
end = hsoffset;
adhoc_search = 0;
goto tryagain;
}
error = ENOENT;
done:
if (hsfs_use_dnlc && !error)
dnlc_enter(dvp, name, *vpp);
kmem_free(cmpname, (size_t)cmpname_size);
return (error);
}
int
hs_parsedir(
struct hsfs *fsp,
uchar_t *dirp,
struct hs_direntry *hdp,
char *dnp,
int *dnlen,
int last_offset)
{
char *on_disk_name;
int on_disk_namelen;
int on_disk_dirlen;
uchar_t flags;
int namelen;
int error;
int name_change_flag = 0;
hdp->ext_lbn = HDE_EXT_LBN(dirp);
hdp->ext_size = HDE_EXT_SIZE(dirp);
hdp->xar_len = HDE_XAR_LEN(dirp);
hdp->intlf_sz = HDE_INTRLV_SIZE(dirp);
hdp->intlf_sk = HDE_INTRLV_SKIP(dirp);
hdp->sym_link = (char *)NULL;
if (fsp->hsfs_vol_type == HS_VOL_TYPE_HS) {
flags = HDE_FLAGS(dirp);
hs_parse_dirdate(HDE_cdate(dirp), &hdp->cdate);
hs_parse_dirdate(HDE_cdate(dirp), &hdp->adate);
hs_parse_dirdate(HDE_cdate(dirp), &hdp->mdate);
if ((flags & hde_prohibited) == 0) {
if (flags & HDE_ASSOCIATED)
return (EAGAIN);
hdp->type = VREG;
hdp->mode = HFREG;
hdp->nlink = 1;
} else if ((flags & hde_prohibited) == HDE_DIRECTORY) {
hdp->type = VDIR;
hdp->mode = HFDIR;
hdp->nlink = 2;
} else {
hs_log_bogus_disk_warning(fsp,
HSFS_ERR_UNSUP_TYPE, flags);
return (EINVAL);
}
hdp->uid = fsp -> hsfs_vol.vol_uid;
hdp->gid = fsp -> hsfs_vol.vol_gid;
hdp->mode = hdp-> mode | (fsp -> hsfs_vol.vol_prot & 0777);
} else if ((fsp->hsfs_vol_type == HS_VOL_TYPE_ISO) ||
(fsp->hsfs_vol_type == HS_VOL_TYPE_ISO_V2) ||
(fsp->hsfs_vol_type == HS_VOL_TYPE_JOLIET)) {
flags = IDE_FLAGS(dirp);
hs_parse_dirdate(IDE_cdate(dirp), &hdp->cdate);
hs_parse_dirdate(IDE_cdate(dirp), &hdp->adate);
hs_parse_dirdate(IDE_cdate(dirp), &hdp->mdate);
if ((flags & ide_prohibited) == 0) {
if (flags & IDE_ASSOCIATED)
return (EAGAIN);
hdp->type = VREG;
hdp->mode = HFREG;
hdp->nlink = 1;
} else if ((flags & ide_prohibited) == IDE_DIRECTORY) {
hdp->type = VDIR;
hdp->mode = HFDIR;
hdp->nlink = 2;
} else {
hs_log_bogus_disk_warning(fsp,
HSFS_ERR_UNSUP_TYPE, flags);
return (EINVAL);
}
hdp->uid = fsp -> hsfs_vol.vol_uid;
hdp->gid = fsp -> hsfs_vol.vol_gid;
hdp->mode = hdp-> mode | (fsp -> hsfs_vol.vol_prot & 0777);
hdp->inode = 0;
if (IS_SUSP_IMPLEMENTED(fsp)) {
error = parse_sua((uchar_t *)dnp, dnlen,
&name_change_flag, dirp, last_offset,
hdp, fsp, NULL, 0);
if (error) {
if (hdp->sym_link) {
kmem_free(hdp->sym_link,
(size_t)(hdp->ext_size + 1));
hdp->sym_link = (char *)NULL;
}
return (error);
}
}
}
hdp->xar_prot = (HDE_PROTECTION & flags) != 0;
#if dontskip
if (hdp->xar_len > 0) {
cmn_err(CE_NOTE, "hsfs: extended attributes not supported");
return (EINVAL);
}
#endif
if (hdp->intlf_sz + hdp->intlf_sk) {
if ((hdp->intlf_sz == 0) || (hdp->intlf_sk == 0)) {
cmn_err(CE_NOTE,
"hsfs: interleaf size or skip factor error");
return (EINVAL);
}
if (hdp->ext_size == 0) {
cmn_err(CE_NOTE,
"hsfs: interleaving specified on zero length file");
return (EINVAL);
}
}
if (HDE_VOL_SET(dirp) != 1) {
if (fsp->hsfs_vol.vol_set_size != 1 &&
fsp->hsfs_vol.vol_set_size != HDE_VOL_SET(dirp)) {
cmn_err(CE_NOTE, "hsfs: multivolume file?");
return (EINVAL);
}
}
if (NAME_HAS_CHANGED(name_change_flag))
return (0);
on_disk_name = (char *)HDE_name(dirp);
on_disk_namelen = (int)HDE_NAME_LEN(dirp);
on_disk_dirlen = (int)HDE_DIR_LEN(dirp);
if (on_disk_dirlen < HDE_ROOT_DIR_REC_SIZE ||
((on_disk_dirlen > last_offset) ||
((HDE_FDESIZE + on_disk_namelen) > on_disk_dirlen))) {
hs_log_bogus_disk_warning(fsp,
HSFS_ERR_BAD_DIR_ENTRY, 0);
return (EINVAL);
}
if (on_disk_namelen > fsp->hsfs_namelen &&
hs_namelen(fsp, on_disk_name, on_disk_namelen) >
fsp->hsfs_namelen) {
hs_log_bogus_disk_warning(fsp,
fsp->hsfs_vol_type == HS_VOL_TYPE_JOLIET ?
HSFS_ERR_BAD_JOLIET_FILE_LEN :
HSFS_ERR_BAD_FILE_LEN, 0);
}
if (on_disk_namelen > ISO_NAMELEN_V2_MAX)
on_disk_namelen = fsp->hsfs_namemax;
if (dnp != NULL) {
if (fsp->hsfs_vol_type == HS_VOL_TYPE_JOLIET) {
namelen = hs_jnamecopy(on_disk_name, dnp,
on_disk_namelen, fsp->hsfs_namemax,
fsp->hsfs_flags);
if (namelen < 0) {
namelen = -namelen;
hs_log_bogus_disk_warning(fsp,
HSFS_ERR_TRUNC_JOLIET_FILE_LEN, 0);
}
} else {
namelen = hs_namecopy(on_disk_name, dnp,
on_disk_namelen, fsp->hsfs_flags);
}
if (namelen == 0)
return (EINVAL);
if ((fsp->hsfs_flags & HSFSMNT_NOTRAILDOT) &&
dnp[ namelen-1 ] == '.' && CAN_TRUNCATE_DOT(dnp, namelen))
dnp[ --namelen ] = '\0';
} else
namelen = on_disk_namelen;
if (dnlen != NULL)
*dnlen = namelen;
return (0);
}
int
hs_namecopy(char *from, char *to, int size, ulong_t flags)
{
uint_t i;
uchar_t c;
int lastspace;
int maplc;
int trailspace;
int version;
if (size == 1) {
if (*from == '\0') {
*to++ = '.';
*to = '\0';
return (1);
} else if (*from == '\1') {
*to++ = '.';
*to++ = '.';
*to = '\0';
return (2);
}
}
maplc = (flags & HSFSMNT_NOMAPLCASE) == 0;
trailspace = (flags & HSFSMNT_NOTRAILSPACE) == 0;
version = (flags & HSFSMNT_NOVERSION) == 0;
for (i = 0, lastspace = -1; i < size; i++) {
c = from[i];
if (c == ';' && version)
break;
if (c <= ' ' && !trailspace) {
if (lastspace == -1)
lastspace = i;
} else
lastspace = -1;
if (maplc && (c >= 'A') && (c <= 'Z'))
c += 'a' - 'A';
to[i] = c;
}
if (lastspace != -1)
i = lastspace;
to[i] = '\0';
return (i);
}
int
hs_jnamecopy(char *from, char *to, int size, int maxsize, ulong_t flags)
{
uint_t i;
uint_t len;
uint16_t c;
int amt;
int version;
if (size == 1) {
if (*from == '\0') {
*to++ = '.';
*to = '\0';
return (1);
} else if (*from == '\1') {
*to++ = '.';
*to++ = '.';
*to = '\0';
return (2);
}
}
version = (flags & HSFSMNT_NOVERSION) == 0;
for (i = 0, len = 0; i < size; i++) {
c = (from[i++] & 0xFF) << 8;
c |= from[i] & 0xFF;
if (c == ';' && version)
break;
if (len > (maxsize-3)) {
if (c < 0x80)
amt = 1;
else if (c < 0x800)
amt = 2;
else
amt = 3;
if ((len+amt) > maxsize) {
to[len] = '\0';
return (-len);
}
}
amt = hs_ucs2_2_utf8(c, (uint8_t *)&to[len]);
if (amt == 0) {
hs_log_bogus_joliet_warning();
return (0);
}
len += amt;
}
to[len] = '\0';
return (len);
}
static int
uppercase_cp(char *from, char *to, int size)
{
uint_t i;
uchar_t c;
uchar_t had_lc = 0;
for (i = 0; i < size; i++) {
c = *from++;
if ((c >= 'a') && (c <= 'z')) {
c -= ('a' - 'A');
had_lc = 1;
}
*to++ = c;
}
return (had_lc);
}
int
hs_joliet_cp(char *from, char *to, int size)
{
uint_t i;
uint16_t c;
int len = 0;
int amt;
if (size == 1) {
*to = *from;
return (1);
}
for (i = 0; i < size; i += 2) {
c = (*from++ & 0xFF) << 8;
c |= *from++ & 0xFF;
amt = hs_ucs2_2_utf8(c, (uint8_t *)to);
if (amt == 0) {
hs_log_bogus_joliet_warning();
return (0);
}
to += amt;
len += amt;
}
return (len);
}
static void
hs_log_bogus_joliet_warning(void)
{
static int warned = 0;
if (warned)
return;
warned = 1;
cmn_err(CE_CONT, "hsfs: Warning: "
"file name contains bad UCS-2 chacarter\n");
}
int
hs_uppercase_copy(char *from, char *to, int size)
{
uint_t i;
uchar_t c;
if (size == 1 && *from == '.') {
*to = '\0';
return (1);
} else if (size == 2 && *from == '.' && *(from+1) == '.') {
*to = '\1';
return (1);
}
for (i = 0; i < size; i++) {
c = *from++;
if ((c >= 'a') && (c <= 'z'))
c = c - 'a' + 'A';
*to++ = c;
}
return (size);
}
static int
hs_iso_copy(char *from, char *to, int size)
{
uint_t i;
uchar_t c;
if (size == 1 && *from == '.') {
*to = '\0';
return (1);
} else if (size == 2 && *from == '.' && *(from+1) == '.') {
*to = '\1';
return (1);
}
for (i = 0; i < size; i++) {
c = *from++;
*to++ = c;
}
return (size);
}
void
hs_filldirent(struct vnode *vp, struct hs_direntry *hdp)
{
struct buf *secbp;
uint_t secno;
offset_t secoff;
struct hsfs *fsp;
uchar_t *secp;
int error;
if (vp->v_type != VDIR) {
cmn_err(CE_WARN, "hsfs_filldirent: vp (0x%p) not a directory",
(void *)vp);
return;
}
fsp = VFS_TO_HSFS(vp ->v_vfsp);
secno = LBN_TO_SEC(hdp->ext_lbn+hdp->xar_len, vp->v_vfsp);
secoff = LBN_TO_BYTE(hdp->ext_lbn+hdp->xar_len, vp->v_vfsp) &
MAXHSOFFSET;
secbp = bread(fsp->hsfs_devvp->v_rdev, secno * 4, HS_SECTOR_SIZE);
error = geterror(secbp);
if (error != 0) {
cmn_err(CE_NOTE, "hs_filldirent: bread: error=(%d)", error);
goto end;
}
secp = (uchar_t *)secbp->b_un.b_addr;
if (hdp->ext_lbn != HDE_EXT_LBN(&secp[secoff])) {
cmn_err(CE_NOTE, "hsfs_filldirent: dirent not match");
}
(void) hs_parsedir(fsp, &secp[secoff], hdp, (char *)NULL,
(int *)NULL, HS_SECTOR_SIZE - secoff);
end:
brelse(secbp);
}
static enum dirblock_result
process_dirblock(
struct fbuf *fbp,
uint_t *offset,
uint_t last_offset,
char *nm,
int nmlen,
struct hsfs *fsp,
struct hsnode *dhp,
struct vnode *dvp,
struct vnode **vpp,
int *error)
{
uchar_t *blkp = (uchar_t *)fbp->fb_addr;
char *dname;
int dnamelen;
struct hs_direntry hd;
int hdlen;
uchar_t *dirp;
int res;
int parsedir_res;
int is_rrip;
size_t rrip_name_size;
int rr_namelen = 0;
char *rrip_name_str = NULL;
char *rrip_tmp_name = NULL;
enum dirblock_result err = 0;
int did_fbrelse = 0;
char uppercase_name[JOLIET_NAMELEN_MAX*3 + 1];
#define PD_return(retval) \
{ err = retval; goto do_ret; }
#define rel_offset(offset) \
((offset) & MAXBOFFSET)
#define RESTORE_NM(tmp, orig) \
if (is_rrip && *(tmp) != '\0') \
(void) strcpy((orig), (tmp))
is_rrip = IS_RRIP_IMPLEMENTED(fsp);
if (is_rrip) {
rrip_name_size = RRIP_FILE_NAMELEN + 1;
rrip_name_str = kmem_alloc(rrip_name_size, KM_SLEEP);
rrip_tmp_name = kmem_alloc(rrip_name_size, KM_SLEEP);
rrip_name_str[0] = '\0';
rrip_tmp_name[0] = '\0';
}
while (*offset < last_offset) {
hdlen = (int)((uchar_t)
HDE_DIR_LEN(&blkp[rel_offset(*offset)]));
if (hdlen < HDE_ROOT_DIR_REC_SIZE ||
*offset + hdlen > last_offset) {
*offset = roundup(*offset + 1, HS_SECTOR_SIZE);
if (hdlen)
hs_log_bogus_disk_warning(fsp,
HSFS_ERR_TRAILING_JUNK, 0);
continue;
}
bzero(&hd, sizeof (hd));
dirp = &blkp[rel_offset(*offset)];
dname = (char *)HDE_name(dirp);
dnamelen = (int)((uchar_t)HDE_NAME_LEN(dirp));
if (dnamelen > hdlen - HDE_FDESIZE) {
hs_log_bogus_disk_warning(fsp,
HSFS_ERR_BAD_DIR_ENTRY, 0);
goto skip_rec;
} else if (dnamelen > fsp->hsfs_namelen &&
hs_namelen(fsp, dname, dnamelen) > fsp->hsfs_namelen) {
hs_log_bogus_disk_warning(fsp,
fsp->hsfs_vol_type == HS_VOL_TYPE_JOLIET ?
HSFS_ERR_BAD_JOLIET_FILE_LEN :
HSFS_ERR_BAD_FILE_LEN, 0);
}
if (dnamelen > ISO_NAMELEN_V2_MAX)
dnamelen = fsp->hsfs_namemax;
if (is_rrip) {
rrip_name_str[0] = '\0';
rr_namelen = rrip_namecopy(nm, &rrip_name_str[0],
&rrip_tmp_name[0], dirp, last_offset - *offset,
fsp, &hd);
if (hd.sym_link) {
kmem_free(hd.sym_link,
(size_t)(hd.ext_size+1));
hd.sym_link = (char *)NULL;
}
if (rr_namelen != -1) {
dname = (char *)&rrip_name_str[0];
dnamelen = rr_namelen;
}
}
if (!is_rrip || rr_namelen == -1) {
int i = -1;
if ((fsp->hsfs_flags & HSFSMNT_NOVERSION) == 0) {
if (fsp->hsfs_vol_type == HS_VOL_TYPE_JOLIET) {
for (i = dnamelen - 1; i > 0; i -= 2) {
if (dname[i] == ';' &&
dname[i-1] == '\0') {
--i;
break;
}
}
} else {
for (i = dnamelen - 1; i > 0; i--) {
if (dname[i] == ';')
break;
}
}
}
if (i > 0) {
dnamelen = i;
} else if (fsp->hsfs_vol_type != HS_VOL_TYPE_ISO_V2 &&
fsp->hsfs_vol_type != HS_VOL_TYPE_JOLIET) {
dnamelen = strip_trailing(fsp, dname, dnamelen);
}
ASSERT(dnamelen < sizeof (uppercase_name));
if (fsp->hsfs_vol_type == HS_VOL_TYPE_ISO_V2) {
(void) strncpy(uppercase_name, dname, dnamelen);
} else if (fsp->hsfs_vol_type == HS_VOL_TYPE_JOLIET) {
dnamelen = hs_joliet_cp(dname, uppercase_name,
dnamelen);
} else if (uppercase_cp(dname, uppercase_name,
dnamelen)) {
hs_log_bogus_disk_warning(fsp,
HSFS_ERR_LOWER_CASE_NM, 0);
}
dname = uppercase_name;
if (!is_rrip &&
(fsp->hsfs_flags & HSFSMNT_NOTRAILDOT) &&
dname[dnamelen - 1] == '.' &&
CAN_TRUNCATE_DOT(dname, dnamelen))
dname[--dnamelen] = '\0';
}
if (strict_iso9660_ordering && !is_rrip &&
!HSFS_HAVE_LOWER_CASE(fsp) && *nm < *dname) {
RESTORE_NM(rrip_tmp_name, nm);
PD_return(WENT_PAST)
}
if (*nm != *dname || nmlen != dnamelen)
goto skip_rec;
if ((res = bcmp(dname, nm, nmlen)) == 0) {
parsedir_res = hs_parsedir(fsp, dirp, &hd,
(char *)NULL, (int *)NULL,
last_offset - *offset);
if (!parsedir_res) {
uint_t lbn;
lbn = dhp->hs_dirent.ext_lbn +
dhp->hs_dirent.xar_len;
fbrelse(fbp, S_READ);
did_fbrelse = 1;
*vpp = hs_makenode(&hd, lbn, *offset,
dvp->v_vfsp);
if (*vpp == NULL) {
*error = ENFILE;
RESTORE_NM(rrip_tmp_name, nm);
PD_return(FOUND_ENTRY)
}
dhp->hs_offset = *offset;
RESTORE_NM(rrip_tmp_name, nm);
PD_return(FOUND_ENTRY)
} else if (parsedir_res != EAGAIN) {
*error = parsedir_res;
RESTORE_NM(rrip_tmp_name, nm);
PD_return(FOUND_ENTRY)
}
} else if (strict_iso9660_ordering && !is_rrip &&
!HSFS_HAVE_LOWER_CASE(fsp) && res < 0) {
RESTORE_NM(rrip_tmp_name, nm);
PD_return(WENT_PAST)
}
skip_rec:
*offset += hdlen;
RESTORE_NM(rrip_tmp_name, nm);
}
PD_return(HIT_END)
do_ret:
if (rrip_name_str)
kmem_free(rrip_name_str, rrip_name_size);
if (rrip_tmp_name)
kmem_free(rrip_tmp_name, rrip_name_size);
if (!did_fbrelse)
fbrelse(fbp, S_READ);
return (err);
#undef PD_return
#undef RESTORE_NM
}
static int
strip_trailing(struct hsfs *fsp, char *nm, int len)
{
char *c;
int trailing_junk = 0;
for (c = nm + len - 1; c > nm; c--) {
if (*c == ' ' || *c == '\0')
trailing_junk = 1;
else
break;
}
if (trailing_junk)
hs_log_bogus_disk_warning(fsp, HSFS_ERR_TRAILING_JUNK, 0);
return ((int)(c - nm + 1));
}
static int
hs_namelen(struct hsfs *fsp, char *nm, int len)
{
char *p = nm + len;
if (fsp->hsfs_vol_type == HS_VOL_TYPE_ISO_V2) {
return (len);
} else if (fsp->hsfs_vol_type == HS_VOL_TYPE_JOLIET) {
uint16_t c;
while (--p > &nm[1]) {
c = *p;
c |= *--p * 256;
if (c == ';')
return (p - nm);
if (c < '0' || c > '9') {
p++;
return (p - nm);
}
}
} else {
char c;
while (--p > nm) {
c = *p;
if (c == ';')
return (p - nm);
if (c < '0' || c > '9') {
p++;
return (p - nm);
}
}
}
return (len);
}
static uint8_t hs_first_byte_mark[7] =
{ 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC };
static int32_t
hs_ucs2_2_utf8(uint16_t c_16, uint8_t *s_8)
{
int32_t nc;
uint32_t c_32;
uint32_t byte_mask = 0xBF;
uint32_t byte_mark = 0x80;
c_32 = c_16;
if (c_32 < 0x80) {
nc = 1;
} else if (c_32 < 0x800) {
nc = 2;
} else if (c_32 < 0x10000) {
nc = 3;
} else if (c_32 < 0x200000) {
nc = 4;
} else if (c_32 < 0x4000000) {
nc = 5;
} else if (c_32 <= 0x7FFFFFFF) {
nc = 6;
} else {
nc = 0;
}
s_8 += nc;
switch (nc) {
case 6 :
*(--s_8) = (c_32 | byte_mark) & byte_mask;
c_32 >>= 6;
case 5 :
*(--s_8) = (c_32 | byte_mark) & byte_mask;
c_32 >>= 6;
case 4 :
*(--s_8) = (c_32 | byte_mark) & byte_mask;
c_32 >>= 6;
case 3 :
*(--s_8) = (c_32 | byte_mark) & byte_mask;
c_32 >>= 6;
case 2 :
*(--s_8) = (c_32 | byte_mark) & byte_mask;
c_32 >>= 6;
case 1 :
*(--s_8) = c_32 | hs_first_byte_mark[nc];
}
return (nc);
}