hsfs_node.c revision 134a1f4e3289b54e0f980e9cf05352e419a60bee
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright (c) 1990, 2010, Oracle and/or its affiliates. All rights reserved.
*/
/*
* Directory operations for High Sierra filesystem
*/
#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>
/*
* This macro expects a name that ends in '.' and returns TRUE if the
* name is not "." or ".."
*/
#define CAN_TRUNCATE_DOT(name, namelen) \
(namelen > 1 && (namelen > 2 || name[0] != '.'))
enum dirblock_result { FOUND_ENTRY, WENT_PAST, HIT_END };
/*
* These values determine whether we will try to read a file or dir;
* they may be patched via /etc/system to allow users to read
* record-oriented files.
*/
int ide_prohibited = IDE_PROHIBITED;
int hde_prohibited = HDE_PROHIBITED;
/*
* This variable determines if the HSFS code will use the
* directory name lookup cache. The default is for the cache to be used.
*/
static int hsfs_use_dnlc = 1;
/*
* This variable determines whether strict ISO-9660 directory ordering
* is to be assumed. If false (which it is by default), then when
* searching a directory of an ISO-9660 disk, we do not expect the
* entries to be sorted (as the spec requires), and so cannot terminate
* the search early. Unfortunately, some vendors are producing
* non-compliant disks. This variable exists to revert to the old
* behavior in case someone relies on this. This option is expected to be
* removed at some point in the future.
*
* Use "set hsfs:strict_iso9660_ordering = 1" in /etc/system to override.
*/
static int strict_iso9660_ordering = 0;
/*
* This tunable allows us to ignore inode numbers from rrip-1.12.
* In this case, we fall back to our default inode algorithm.
*/
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);
/*
* hs_access
* Return 0 if the desired access may be granted.
* Otherwise return error code.
*/
int
hs_access(struct vnode *vp, mode_t m, struct cred *cred)
{
struct hsnode *hp;
int shift = 0;
/*
* Write access cannot be granted for a read-only medium
*/
if ((m & VWRITE) && !IS_DEVVP(vp))
return (EROFS);
hp = VTOH(vp);
/*
* XXX - For now, use volume protections.
* Also, always grant EXEC access for directories
* if READ access is granted.
*/
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)
/*
* The tunable nhsnode is now a threshold for a dynamically allocated
* pool of hsnodes, not the size of a statically allocated table.
* When the number of hsnodes for a particular file system exceeds
* nhsnode, the allocate and free logic will try to reduce the number
* of allocated nodes by returning unreferenced nodes to the kmem_cache
* instead of putting them on the file system's private free list.
*/
int nhsnode = HS_HSNODESPACE / sizeof (struct hsnode);
struct kmem_cache *hsnode_cache; /* free hsnode cache */
/*
* Initialize the cache of free hsnodes.
*/
void
hs_init_hsnode_cache(void)
{
/*
* A kmem_cache is used for the hsnodes
* No constructor because hsnodes are initialised by bzeroing.
*/
hsnode_cache = kmem_cache_create("hsfs_hsnode_cache",
sizeof (struct hsnode), 0, NULL,
NULL, hs_hsnode_cache_reclaim, NULL, NULL, 0);
}
/*
* Destroy the cache of free hsnodes.
*/
void
hs_fini_hsnode_cache(void)
{
kmem_cache_destroy(hsnode_cache);
}
/*
* System is short on memory, free up as much as possible
*/
/*ARGSUSED*/
static void
hs_hsnode_cache_reclaim(void *unused)
{
struct hsfs *fsp;
struct hsnode *hp;
/*
* For each vfs in the hs_mounttab list
*/
mutex_enter(&hs_mounttab_lock);
for (fsp = hs_mounttab; fsp != NULL; fsp = fsp->hsfs_next) {
/*
* Purge the dnlc of all hsfs entries
*/
(void) dnlc_purge_vfsp(fsp->hsfs_vfs, 0);
/*
* For each entry in the free chain
*/
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) {
/*
* Remove from chain
*/
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;
}
/*
* Free the node. Force it to be fully freed
* by setting the 3rd arg (nopage) to 1.
*/
hs_freenode(HTOV(hp), fsp, 1);
}
mutex_exit(&fsp->hsfs_free_lock);
rw_exit(&fsp->hsfs_hash_lock);
}
mutex_exit(&hs_mounttab_lock);
}
/*
* Add an hsnode to the end of the free list.
*/
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 is the last entry in free list */
hp->hs_freef = NULL;
hp->hs_freeb = ep; /* point at previous last entry */
if (ep == NULL)
fsp->hsfs_free_f = hp; /* hp is only entry in free list */
else
ep->hs_freef = hp; /* point previous last entry at hp */
mutex_exit(&fsp->hsfs_free_lock);
}
/*
* Get an hsnode from the front of the free list.
* Must be called with write hsfs_hash_lock held.
*/
static struct hsnode *
hs_getfree(struct hsfs *fsp)
{
struct hsnode *hp, **tp;
ASSERT(RW_WRITE_HELD(&fsp->hsfs_hash_lock));
/*
* If the number of currently-allocated hsnodes is less than
* the hsnode count threshold (nhsnode), or if there are no
* nodes on the file system's local free list (which acts as a
* cache), call kmem_cache_alloc to get a new hsnode from
* kernel memory.
*/
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;
/* hp cannot be NULL, since we already checked this above */
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);
/*
* file is no longer referenced, destroy all old pages
*/
if (vn_has_cached_data(vp))
/*
* pvn_vplist_dirty will abort all old pages
*/
(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);
}
/*
* Remove an hsnode from the free list.
*/
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);
}
/*
* Look for hsnode in hash list.
* If the inode number is != HS_DUMMY_INO (16), then only the inode
* number is used for the check.
* If the inode number is == HS_DUMMY_INO, we additionally always
* check the directory offset for the file to avoid caching the
* meta data for all zero sized to the first zero sized file that
* was touched.
*
* If found, reactivate it if inactive.
*
* Must be entered with hsfs_hash_lock held.
*/
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) {
/*
* If this is the dummy inode number, look for
* matching dir_lbn and dir_off.
*/
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;
/*
* reactivating a free hsnode:
* remove from free list
*/
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;
}
/*
* Destroy all old pages and free the hsnodes
* Return 1 if busy (a hsnode is still referenced).
*/
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;
/* make sure no one can come in */
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);
}
/* now free the hsnodes */
for (i = 0; i < HS_HASHSIZE; i++) {
for (hp = fsp->hsfs_hash[i]; hp != NULL; hp = nhp) {
nhp = hp->hs_hash;
/*
* We know there are no pages associated with
* all the hsnodes (they've all been released
* above). So remove from free list and
* free the entry with nopage set.
*/
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);
/* release the root hsnode, this should free the final hsnode */
VN_RELE(rvp);
return (0);
}
/*
* hs_makenode
*
* Construct an hsnode.
* Caller specifies the directory entry, the block number and offset
* of the directory entry, and the vfs pointer.
* note: off is the sector offset, not lbn offset
* if NULL is returned implies file system hsnode table full
*/
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);
/*
* Construct the data that allows us to re-read the meta data without
* knowing the name of the file: in the case of a directory
* entry, this should point to the canonical dirent, the "."
* directory entry for the directory. This dirent is pointed
* to by all directory entries for that dir (including the ".")
* entry itself.
* In the case of a file, simply point to the dirent for that
* file (there are hard links in Rock Ridge, so we need to use
* different data to contruct the node id).
*/
if (dp->type == VDIR) {
lbn = dp->ext_lbn;
off = 0;
}
/*
* Normalize lbn and off before creating a nodeid
* and before storing them in a hs_node structure
*/
hvp = &fsp->hsfs_vol;
lbn += off >> hvp->lbn_shift;
off &= hvp->lbn_maxoffset;
/*
* If the media carries rrip-v1.12 or newer, and we trust the inodes
* from the rrip data (use_rrip_inodes != 0), use that data. If the
* media has been created by a recent mkisofs version, we may trust
* all numbers in the starting extent number; otherwise, we cannot
* do this for zero sized files and symlinks, because if we did we'd
* end up mapping all of them to the same node.
* We use HS_DUMMY_INO in this case and make sure that we will not
* map all files to the same meta data.
*/
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;
}
/* look for hsnode in cache first */
rw_enter(&fsp->hsfs_hash_lock, RW_READER);
if ((vp = hs_findhash(nodeid, lbn, off, vfsp)) == NULL) {
/*
* Not in cache. However, someone else may have come
* to the same conclusion and just put one in. Upgrade
* our lock to a write lock and look again.
*/
rw_exit(&fsp->hsfs_hash_lock);
rw_enter(&fsp->hsfs_hash_lock, RW_WRITER);
if ((vp = hs_findhash(nodeid, lbn, off, vfsp)) == NULL) {
/*
* Now we are really sure that the hsnode is not
* in the cache. Get one off freelist or else
* allocate one. Either way get a bzeroed hsnode.
*/
hp = hs_getfree(fsp);
bcopy((caddr_t)dp, (caddr_t)&hp->hs_dirent,
sizeof (*dp));
/*
* We've just copied this pointer into hs_dirent,
* and don't want 2 references to same symlink.
*/
dp->sym_link = (char *)NULL;
/*
* No need to hold any lock because hsnode is not
* yet in the hash chain.
*/
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 it's a device, call specvp
*/
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);
}
/*
* hs_freenode
*
* Deactivate an hsnode.
* Leave it on the hash list but put it on the free list.
* If the vnode does not have any pages, release the hsnode to the
* kmem_cache using kmem_cache_free, else put in back of the free list.
*
* This function can be called with the hsfs_free_lock held, but only
* when the code is guaranteed to go through the path where the
* node is freed entirely, and not the path where the node could go back
* on the free list (and where the free lock would need to be acquired).
*/
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)) {
/* remove this node from the hash list, if it's there */
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)) {
/* clean all old pages */
(void) pvn_vplist_dirty(vp, (u_offset_t)0,
hsfs_putapage, B_INVAL, (struct cred *)NULL);
/* XXX - can we remove pages by fiat like this??? */
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); /* add to back of free list */
}
/*
* hs_remakenode
*
* Reconstruct a vnode given the location of its directory entry.
* Caller specifies the the block number and offset
* of the directory entry, and the vfs pointer.
* Returns an error code or 0.
*/
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;
/* Convert to sector and offset */
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);
}
/*
* hs_dirlook
*
* Look for a given name in a given directory.
* If found, construct an hsnode for it.
*/
int
hs_dirlook(
struct vnode *dvp,
char *name,
int namlen, /* length of 'name' */
struct vnode **vpp,
struct cred *cred)
{
struct hsnode *dhp;
struct hsfs *fsp;
int error = 0;
uint_t offset; /* real offset in directory */
uint_t last_offset; /* last index in directory */
char *cmpname; /* case-folded name */
int cmpname_size; /* how much memory we allocate for it */
int cmpnamelen;
int adhoc_search; /* did we start at begin of dir? */
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);
/*
* name == "^A" is illegal for ISO-9660 and Joliet as '..' is '\1' on
* disk. It is no problem for Rock Ridge as RR uses '.' and '..'.
* XXX It could be OK for Joliet also (because namelen == 1 is
* XXX impossible for UCS-2) but then we need a better compare algorith.
*/
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;
/*
* For the purposes of comparing the name against dir entries,
* fold it to upper case.
*/
if (is_rrip) {
(void) strlcpy(cmpname, name, cmpname_size);
cmpnamelen = namlen;
} else {
/*
* If we don't consider a trailing dot as part of the filename,
* remove it from the specified name
*/
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);
}
}
/* make sure dirent is filled up with all info */
if (dhp->hs_dirent.ext_size == 0)
hs_filldirent(dvp, &dhp->hs_dirent);
/*
* No lock is needed - hs_offset is used as starting
* point for searching the directory.
*/
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:
/* found an entry, either correct or not */
goto done;
case WENT_PAST:
/*
* If we get here we know we didn't find it on the
* first pass. If adhoc_search, then we started a
* bit into the dir, and need to wrap around and
* search the first entries. If not, then we started
* at the beginning and didn't find it.
*/
if (adhoc_search) {
offset = 0;
end = hsoffset;
adhoc_search = 0;
goto tryagain;
}
error = ENOENT;
goto done;
case HIT_END:
goto tryagain;
}
}
/*
* End of all dir blocks, didn't find entry.
*/
if (adhoc_search) {
offset = 0;
end = hsoffset;
adhoc_search = 0;
goto tryagain;
}
error = ENOENT;
done:
/*
* If we found the entry, add it to the DNLC
* If the entry is a device file (assuming we support Rock Ridge),
* we enter the device vnode to the cache since that is what
* is in *vpp.
* That is ok since the CD-ROM is read-only, so (dvp,name) will
* always point to the same device.
*/
if (hsfs_use_dnlc && !error)
dnlc_enter(dvp, name, *vpp);
kmem_free(cmpname, (size_t)cmpname_size);
return (error);
}
/*
* hs_parsedir
*
* Parse a Directory Record into an hs_direntry structure.
* High Sierra and ISO directory are almost the same
* except the flag and date
*/
int
hs_parsedir(
struct hsfs *fsp,
uchar_t *dirp,
struct hs_direntry *hdp,
char *dnp,
int *dnlen,
int last_offset) /* last offset in dirp */
{
char *on_disk_name;
int on_disk_namelen;
int on_disk_dirlen;
uchar_t flags;
int namelen;
int error;
int name_change_flag = 0; /* set if name was gotten in SUA */
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) {
/*
* Skip files with the associated bit set.
*/
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) {
/*
* Skip files with the associated bit set.
*/
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; /* initialize with 0, then check rrip */
/*
* Having this all filled in, let's see if we have any
* SUA susp to look at.
*/
if (IS_SUSP_IMPLEMENTED(fsp)) {
error = parse_sua((uchar_t *)dnp, dnlen,
&name_change_flag, dirp, last_offset,
hdp, fsp,
(uchar_t *)NULL, NULL);
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
/* check interleaf size and skip factor */
/* must both be zero or non-zero */
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 the name changed, then the NM field for RRIP was hit and
* we should not copy the name again, just return.
*/
if (NAME_HAS_CHANGED(name_change_flag))
return (0);
/*
* Fall back to the ISO name. Note that as in process_dirblock,
* the on-disk filename length must be validated against ISO
* limits - which, in case of RR present but no RR name found,
* are NOT identical to fsp->hsfs_namemax on this filesystem.
*/
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; /* Paranoia */
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);
/*
* A negative return value means that the file name
* has been truncated to fsp->hsfs_namemax.
*/
if (namelen < 0) {
namelen = -namelen;
hs_log_bogus_disk_warning(fsp,
HSFS_ERR_TRUNC_JOLIET_FILE_LEN, 0);
}
} else {
/*
* HS_VOL_TYPE_ISO && HS_VOL_TYPE_ISO_V2
*/
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);
}
/*
* hs_namecopy
*
* Parse a file/directory name into UNIX form.
* Delete trailing blanks, upper-to-lower case, add NULL terminator.
* Returns the (possibly new) length.
*
* Called from hsfs_readdir() via hs_parsedir()
*/
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;
/* special handling for '.' and '..' */
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);
}
/*
* hs_jnamecopy
*
* This is the Joliet variant of hs_namecopy()
*
* Parse a UCS-2 Joliet file/directory name into UNIX form.
* Add NULL terminator.
* Returns the new length.
*
* Called from hsfs_readdir() via hs_parsedir()
*/
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;
/* special handling for '.' and '..' */
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(); /* should never happen */
return (0);
}
len += amt;
}
to[len] = '\0';
return (len);
}
/*
* map a filename to upper case;
* return 1 if found lowercase character
*
* Called from process_dirblock()
* via hsfs_lookup() -> hs_dirlook() -> process_dirblock()
* to create an intermedia name from on disk file names for
* comparing names.
*/
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);
}
/*
* This is the Joliet variant of uppercase_cp()
*
* map a UCS-2 filename to UTF-8;
* return new length
*
* Called from process_dirblock()
* via hsfs_lookup() -> hs_dirlook() -> process_dirblock()
* to create an intermedia name from on disk file names for
* comparing names.
*/
int
hs_joliet_cp(char *from, char *to, int size)
{
uint_t i;
uint16_t c;
int len = 0;
int amt;
/* special handling for '\0' and '\1' */
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(); /* should never happen */
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");
}
/*
* hs_uppercase_copy
*
* Convert a UNIX-style name into its HSFS equivalent
* replacing '.' and '..' with '\0' and '\1'.
* Map to upper case.
* Returns the (possibly new) length.
*
* Called from hs_dirlook() and rrip_namecopy()
* to create an intermediate name from the callers name from hsfs_lookup()
* XXX Is the call from rrip_namecopy() OK?
*/
int
hs_uppercase_copy(char *from, char *to, int size)
{
uint_t i;
uchar_t c;
/* special handling for '.' and '..' */
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);
}
/*
* hs_iso_copy
*
* This is the Joliet/ISO-9660:1999 variant of hs_uppercase_copy()
*
* Convert a UTF-8 UNIX-style name into its UTF-8 Joliet/ISO equivalent
* replacing '.' and '..' with '\0' and '\1'.
* Returns the (possibly new) length.
*
* Called from hs_dirlook()
* to create an intermediate name from the callers name from hsfs_lookup()
*/
static int
hs_iso_copy(char *from, char *to, int size)
{
uint_t i;
uchar_t c;
/* special handling for '.' and '..' */
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;
/* quick check */
if (hdp->ext_lbn != HDE_EXT_LBN(&secp[secoff])) {
cmn_err(CE_NOTE, "hsfs_filldirent: dirent not match");
/* keep on going */
}
(void) hs_parsedir(fsp, &secp[secoff], hdp, (char *)NULL,
(int *)NULL, HS_SECTOR_SIZE - secoff);
end:
brelse(secbp);
}
/*
* Look through a directory block for a matching entry.
* Note: this routine does an fbrelse() on the buffer passed in.
*/
static enum dirblock_result
process_dirblock(
struct fbuf *fbp, /* buffer containing dirblk */
uint_t *offset, /* lower index */
uint_t last_offset, /* upper index */
char *nm, /* upcase nm to compare against */
int nmlen, /* length of name */
struct hsfs *fsp,
struct hsnode *dhp,
struct vnode *dvp,
struct vnode **vpp,
int *error) /* return value: errno */
{
uchar_t *blkp = (uchar_t *)fbp->fb_addr; /* dir block */
char *dname; /* name in directory entry */
int dnamelen; /* length of name */
struct hs_direntry hd;
int hdlen;
uchar_t *dirp; /* the directory entry */
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]; /* 331 */
#define PD_return(retval) \
{ err = retval; goto do_ret; } /* return after cleanup */
#define rel_offset(offset) \
((offset) & MAXBOFFSET) /* index into cur blk */
#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) {
/*
* Directory Entries cannot span sectors.
*
* Unused bytes at the end of each sector are zeroed
* according to ISO9660, but we cannot rely on this
* since both media failures and maliciously corrupted
* media may return arbitrary values.
* We therefore have to check for consistency:
* The size of a directory entry must be at least
* 34 bytes (the size of the directory entry metadata),
* or zero (indicating the end-of-sector condition).
* For a non-zero directory entry size of less than
* 34 Bytes, log a warning.
* In any case, skip the rest of this sector and
* continue with the next.
*/
hdlen = (int)((uchar_t)
HDE_DIR_LEN(&blkp[rel_offset(*offset)]));
if (hdlen < HDE_ROOT_DIR_REC_SIZE ||
*offset + hdlen > last_offset) {
/*
* Advance to the next sector boundary
*/
*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));
/*
* Check the filename length in the ISO record for
* plausibility and reset it to a safe value, in case
* the name length byte is out of range. Since the ISO
* name will be used as fallback if the rockridge name
* is invalid/nonexistant, we must make sure not to
* blow the bounds and initialize dnamelen to a sensible
* value within the limits of ISO9660.
* In addition to that, the ISO filename is part of the
* directory entry. If the filename length is too large
* to fit, the record is invalid and we'll advance to
* the next.
*/
dirp = &blkp[rel_offset(*offset)];
dname = (char *)HDE_name(dirp);
dnamelen = (int)((uchar_t)HDE_NAME_LEN(dirp));
/*
* If the directory entry extends beyond the end of the
* block, it must be invalid. Skip it.
*/
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; /* Paranoia */
/*
* If the rock ridge is implemented, then we copy the name
* from the SUA area to rrip_name_str. If no Alternate
* name is found, then use the uppercase NM in the
* rrip_name_str char array.
*/
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) {
/* use iso name instead */
int i = -1;
/*
* make sure that we get rid of ';' in the dname of
* an iso direntry, as we should have no knowledge
* of file versions.
*
* XXX This is done the wrong way: it does not take
* XXX care of the fact that the version string is
* XXX a decimal number in the range 1 to 32767.
*/
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';
}
/*
* Quickly screen for a non-matching entry, but not for RRIP.
* This test doesn't work for lowercase vs. uppercase names.
*/
/* if we saw a lower case name we can't do this test either */
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) {
/* name matches */
parsedir_res = hs_parsedir(fsp, dirp, &hd,
(char *)NULL, (int *)NULL,
last_offset - *offset);
if (!parsedir_res) {
uint_t lbn; /* logical block number */
lbn = dhp->hs_dirent.ext_lbn +
dhp->hs_dirent.xar_len;
/*
* Need to do an fbrelse() on the buffer,
* as hs_makenode() may try to acquire
* hs_hashlock, which may not be required
* while a page is locked.
*/
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) {
/* improper dir entry */
*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) {
/* name < dir entry */
RESTORE_NM(rrip_tmp_name, nm);
PD_return(WENT_PAST)
}
/*
* name > dir entry,
* look at next one.
*/
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
}
/*
* Strip trailing nulls or spaces from the name;
* return adjusted length. If we find such junk,
* log a non-conformant disk message.
*/
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);
}
/*
* Take a UCS-2 character and convert
* it into a utf8 character.
* A 0 will be returned if the conversion fails
*
* See http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
*
* The code has been taken from udfs/udf_subr.c
*/
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;
/*
* Convert the 16-bit character to a 32-bit character
*/
c_32 = c_16;
/*
* By here the 16-bit character is converted
* to a 32-bit wide character
*/
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) { /* avoid signed overflow */
nc = 6;
} else {
nc = 0;
}
s_8 += nc;
switch (nc) {
case 6 :
*(--s_8) = (c_32 | byte_mark) & byte_mask;
c_32 >>= 6;
/* FALLTHROUGH */
case 5 :
*(--s_8) = (c_32 | byte_mark) & byte_mask;
c_32 >>= 6;
/* FALLTHROUGH */
case 4 :
*(--s_8) = (c_32 | byte_mark) & byte_mask;
c_32 >>= 6;
/* FALLTHROUGH */
case 3 :
*(--s_8) = (c_32 | byte_mark) & byte_mask;
c_32 >>= 6;
/* FALLTHROUGH */
case 2 :
*(--s_8) = (c_32 | byte_mark) & byte_mask;
c_32 >>= 6;
/* FALLTHROUGH */
case 1 :
*(--s_8) = c_32 | hs_first_byte_mark[nc];
}
return (nc);
}