hsfs_node.c revision d10b670251155449ab8d25790cb23de3995cca8e
2N/A/*
2N/A * CDDL HEADER START
2N/A *
2N/A * The contents of this file are subject to the terms of the
2N/A * Common Development and Distribution License (the "License").
2N/A * You may not use this file except in compliance with the License.
2N/A *
2N/A * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
2N/A * or http://www.opensolaris.org/os/licensing.
2N/A * See the License for the specific language governing permissions
2N/A * and limitations under the License.
2N/A *
2N/A * When distributing Covered Code, include this CDDL HEADER in each
2N/A * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
2N/A * If applicable, add the following below this CDDL HEADER, with the
2N/A * fields enclosed by brackets "[]" replaced with your own identifying
2N/A * information: Portions Copyright [yyyy] [name of copyright owner]
2N/A *
2N/A * CDDL HEADER END
2N/A */
2N/A/*
2N/A * Copyright 2007 Sun Microsystems, Inc. All rights reserved.
2N/A * Use is subject to license terms.
2N/A */
2N/A
2N/A#pragma ident "%Z%%M% %I% %E% SMI"
2N/A
2N/A/*
2N/A * Directory operations for High Sierra filesystem
2N/A */
2N/A
2N/A#include <sys/types.h>
2N/A#include <sys/t_lock.h>
2N/A#include <sys/param.h>
2N/A#include <sys/systm.h>
2N/A#include <sys/cred.h>
2N/A#include <sys/user.h>
2N/A#include <sys/vfs.h>
2N/A#include <sys/stat.h>
2N/A#include <sys/vnode.h>
2N/A#include <sys/mode.h>
2N/A#include <sys/dnlc.h>
2N/A#include <sys/cmn_err.h>
2N/A#include <sys/fbuf.h>
2N/A#include <sys/kmem.h>
2N/A#include <sys/policy.h>
2N/A#include <sys/sunddi.h>
2N/A#include <vm/hat.h>
2N/A#include <vm/as.h>
2N/A#include <vm/pvn.h>
2N/A#include <vm/seg.h>
2N/A#include <vm/seg_map.h>
2N/A#include <vm/seg_kmem.h>
2N/A#include <vm/page.h>
2N/A
2N/A#include <sys/fs/hsfs_spec.h>
2N/A#include <sys/fs/hsfs_isospec.h>
2N/A#include <sys/fs/hsfs_node.h>
2N/A#include <sys/fs/hsfs_impl.h>
2N/A#include <sys/fs/hsfs_susp.h>
2N/A#include <sys/fs/hsfs_rrip.h>
2N/A
2N/A#include <sys/sysinfo.h>
2N/A#include <sys/sysmacros.h>
2N/A#include <sys/errno.h>
2N/A#include <sys/debug.h>
2N/A#include <fs/fs_subr.h>
2N/A
2N/A/*
2N/A * This macro expects a name that ends in '.' and returns TRUE if the
2N/A * name is not "." or ".."
2N/A */
2N/A#define CAN_TRUNCATE_DOT(name, namelen) \
2N/A (namelen > 1 && (namelen > 2 || name[0] != '.'))
2N/A
2N/Aenum dirblock_result { FOUND_ENTRY, WENT_PAST, HIT_END };
2N/A
2N/A/*
2N/A * These values determine whether we will try to read a file or dir;
2N/A * they may be patched via /etc/system to allow users to read
2N/A * record-oriented files.
2N/A */
2N/Aint ide_prohibited = IDE_PROHIBITED;
2N/Aint hde_prohibited = HDE_PROHIBITED;
2N/A
2N/A/*
2N/A * This variable determines if the HSFS code will use the
2N/A * directory name lookup cache. The default is for the cache to be used.
2N/A */
2N/Astatic int hsfs_use_dnlc = 1;
2N/A
2N/A/*
2N/A * This variable determines whether strict ISO-9660 directory ordering
2N/A * is to be assumed. If false (which it is by default), then when
2N/A * searching a directory of an ISO-9660 disk, we do not expect the
2N/A * entries to be sorted (as the spec requires), and so cannot terminate
2N/A * the search early. Unfortunately, some vendors are producing
2N/A * non-compliant disks. This variable exists to revert to the old
2N/A * behavior in case someone relies on this. This option is expected to be
2N/A * removed at some point in the future.
2N/A *
2N/A * Use "set hsfs:strict_iso9660_ordering = 1" in /etc/system to override.
2N/A */
2N/Astatic int strict_iso9660_ordering = 0;
2N/A
2N/A/*
2N/A * This tunable allows us to ignore inode numbers from rrip-1.12.
2N/A * In this case, we fall back to our default inode algorithm.
2N/A */
2N/Aint use_rrip_inodes = 1;
2N/A
2N/Astatic void hs_hsnode_cache_reclaim(void *unused);
2N/Astatic void hs_addfreeb(struct hsfs *fsp, struct hsnode *hp);
2N/Astatic enum dirblock_result process_dirblock(struct fbuf *fbp, uint_t *offset,
2N/A uint_t last_offset, char *nm, int nmlen, struct hsfs *fsp,
2N/A struct hsnode *dhp, struct vnode *dvp, struct vnode **vpp,
2N/A int *error);
2N/Astatic int strip_trailing(struct hsfs *fsp, char *nm, int len);
2N/Astatic int hs_namelen(struct hsfs *fsp, char *nm, int len);
2N/Astatic int uppercase_cp(char *from, char *to, int size);
2N/Astatic void hs_log_bogus_joliet_warning(void);
2N/Astatic int hs_iso_copy(char *from, char *to, int size);
2N/Astatic int32_t hs_ucs2_2_utf8(uint16_t c_16, uint8_t *s_8);
2N/Astatic int hs_utf8_trunc(uint8_t *str, int len);
2N/A
2N/A/*
2N/A * hs_access
2N/A * Return 0 if the desired access may be granted.
2N/A * Otherwise return error code.
2N/A */
2N/Aint
2N/Ahs_access(struct vnode *vp, mode_t m, struct cred *cred)
2N/A{
2N/A struct hsnode *hp;
2N/A int shift = 0;
2N/A
2N/A /*
2N/A * Write access cannot be granted for a read-only medium
2N/A */
2N/A if ((m & VWRITE) && !IS_DEVVP(vp))
2N/A return (EROFS);
2N/A
2N/A hp = VTOH(vp);
2N/A
2N/A /*
2N/A * XXX - For now, use volume protections.
2N/A * Also, always grant EXEC access for directories
2N/A * if READ access is granted.
2N/A */
2N/A if ((vp->v_type == VDIR) && (m & VEXEC)) {
2N/A m &= ~VEXEC;
2N/A m |= VREAD;
2N/A }
2N/A
2N/A if (crgetuid(cred) != hp->hs_dirent.uid) {
2N/A shift += 3;
2N/A if (!groupmember((uid_t)hp->hs_dirent.gid, cred))
2N/A shift += 3;
2N/A }
2N/A m &= ~(hp->hs_dirent.mode << shift);
2N/A if (m != 0)
2N/A return (secpolicy_vnode_access(cred, vp, hp->hs_dirent.uid, m));
2N/A return (0);
2N/A}
2N/A
2N/A#if ((HS_HASHSIZE & (HS_HASHSIZE - 1)) == 0)
2N/A#define HS_HASH(l) ((uint_t)(l) & (HS_HASHSIZE - 1))
2N/A#else
2N/A#define HS_HASH(l) ((uint_t)(l) % HS_HASHSIZE)
2N/A#endif
2N/A#define HS_HPASH(hp) HS_HASH((hp)->hs_nodeid)
2N/A
2N/A/*
2N/A * The tunable nhsnode is now a threshold for a dynamically allocated
2N/A * pool of hsnodes, not the size of a statically allocated table.
2N/A * When the number of hsnodes for a particular file system exceeds
2N/A * nhsnode, the allocate and free logic will try to reduce the number
2N/A * of allocated nodes by returning unreferenced nodes to the kmem_cache
2N/A * instead of putting them on the file system's private free list.
2N/A */
2N/Aint nhsnode = HS_HSNODESPACE / sizeof (struct hsnode);
2N/A
2N/Astruct kmem_cache *hsnode_cache; /* free hsnode cache */
2N/A
2N/A/*
2N/A * Initialize the cache of free hsnodes.
2N/A */
2N/Avoid
2N/Ahs_init_hsnode_cache(void)
2N/A{
2N/A /*
2N/A * A kmem_cache is used for the hsnodes
2N/A * No constructor because hsnodes are initialised by bzeroing.
2N/A */
2N/A hsnode_cache = kmem_cache_create("hsfs_hsnode_cache",
2N/A sizeof (struct hsnode), 0, NULL,
2N/A NULL, hs_hsnode_cache_reclaim, NULL, NULL, 0);
2N/A}
2N/A
2N/A/*
2N/A * Destroy the cache of free hsnodes.
2N/A */
2N/Avoid
2N/Ahs_fini_hsnode_cache(void)
2N/A{
2N/A kmem_cache_destroy(hsnode_cache);
2N/A}
2N/A
2N/A/*
2N/A * System is short on memory, free up as much as possible
2N/A */
2N/A/*ARGSUSED*/
2N/Astatic void
2N/Ahs_hsnode_cache_reclaim(void *unused)
2N/A{
2N/A struct hsfs *fsp;
2N/A struct hsnode *hp;
2N/A
2N/A /*
2N/A * For each vfs in the hs_mounttab list
2N/A */
2N/A mutex_enter(&hs_mounttab_lock);
2N/A for (fsp = hs_mounttab; fsp != NULL; fsp = fsp->hsfs_next) {
2N/A /*
2N/A * Purge the dnlc of all hsfs entries
2N/A */
2N/A (void) dnlc_purge_vfsp(fsp->hsfs_vfs, 0);
2N/A
2N/A /*
2N/A * For each entry in the free chain
2N/A */
2N/A rw_enter(&fsp->hsfs_hash_lock, RW_WRITER);
2N/A mutex_enter(&fsp->hsfs_free_lock);
2N/A for (hp = fsp->hsfs_free_f; hp != NULL; hp = fsp->hsfs_free_f) {
2N/A /*
2N/A * Remove from chain
2N/A */
2N/A fsp->hsfs_free_f = hp->hs_freef;
2N/A if (fsp->hsfs_free_f != NULL) {
2N/A fsp->hsfs_free_f->hs_freeb = NULL;
2N/A } else {
2N/A fsp->hsfs_free_b = NULL;
2N/A }
2N/A /*
2N/A * Free the node. Force it to be fully freed
2N/A * by setting the 3rd arg (nopage) to 1.
2N/A */
2N/A hs_freenode(HTOV(hp), fsp, 1);
2N/A }
2N/A mutex_exit(&fsp->hsfs_free_lock);
2N/A rw_exit(&fsp->hsfs_hash_lock);
2N/A }
2N/A mutex_exit(&hs_mounttab_lock);
2N/A}
2N/A
2N/A/*
2N/A * Add an hsnode to the end of the free list.
2N/A */
2N/Astatic void
2N/Ahs_addfreeb(struct hsfs *fsp, struct hsnode *hp)
2N/A{
2N/A struct hsnode *ep;
2N/A
2N/A vn_invalid(HTOV(hp));
2N/A mutex_enter(&fsp->hsfs_free_lock);
2N/A ep = fsp->hsfs_free_b;
2N/A fsp->hsfs_free_b = hp; /* hp is the last entry in free list */
2N/A hp->hs_freef = NULL;
2N/A hp->hs_freeb = ep; /* point at previous last entry */
2N/A if (ep == NULL)
2N/A fsp->hsfs_free_f = hp; /* hp is only entry in free list */
2N/A else
2N/A ep->hs_freef = hp; /* point previous last entry at hp */
2N/A
2N/A mutex_exit(&fsp->hsfs_free_lock);
2N/A}
2N/A
2N/A/*
2N/A * Get an hsnode from the front of the free list.
2N/A * Must be called with write hsfs_hash_lock held.
2N/A */
2N/Astatic struct hsnode *
2N/Ahs_getfree(struct hsfs *fsp)
2N/A{
2N/A struct hsnode *hp, **tp;
2N/A
2N/A ASSERT(RW_WRITE_HELD(&fsp->hsfs_hash_lock));
2N/A
2N/A /*
2N/A * If the number of currently-allocated hsnodes is less than
2N/A * the hsnode count threshold (nhsnode), or if there are no
2N/A * nodes on the file system's local free list (which acts as a
2N/A * cache), call kmem_cache_alloc to get a new hsnode from
2N/A * kernel memory.
2N/A */
2N/A mutex_enter(&fsp->hsfs_free_lock);
2N/A if ((fsp->hsfs_nohsnode < nhsnode) || (fsp->hsfs_free_f == NULL)) {
2N/A mutex_exit(&fsp->hsfs_free_lock);
2N/A hp = kmem_cache_alloc(hsnode_cache, KM_SLEEP);
2N/A fsp->hsfs_nohsnode++;
2N/A bzero((caddr_t)hp, sizeof (*hp));
2N/A hp->hs_vnode = vn_alloc(KM_SLEEP);
2N/A return (hp);
2N/A }
2N/A hp = fsp->hsfs_free_f;
2N/A /* hp cannot be NULL, since we already checked this above */
2N/A fsp->hsfs_free_f = hp->hs_freef;
2N/A if (fsp->hsfs_free_f != NULL)
2N/A fsp->hsfs_free_f->hs_freeb = NULL;
2N/A else
2N/A fsp->hsfs_free_b = NULL;
2N/A mutex_exit(&fsp->hsfs_free_lock);
2N/A
2N/A for (tp = &fsp->hsfs_hash[HS_HPASH(hp)]; *tp != NULL;
2N/A tp = &(*tp)->hs_hash) {
2N/A if (*tp == hp) {
2N/A struct vnode *vp;
2N/A
2N/A vp = HTOV(hp);
2N/A
2N/A /*
2N/A * file is no longer referenced, destroy all old pages
2N/A */
2N/A if (vn_has_cached_data(vp))
2N/A /*
2N/A * pvn_vplist_dirty will abort all old pages
2N/A */
2N/A (void) pvn_vplist_dirty(vp, (u_offset_t)0,
2N/A hsfs_putapage, B_INVAL,
2N/A (struct cred *)NULL);
2N/A *tp = hp->hs_hash;
2N/A break;
2N/A }
2N/A }
2N/A if (hp->hs_dirent.sym_link != (char *)NULL) {
2N/A kmem_free(hp->hs_dirent.sym_link,
2N/A (size_t)(hp->hs_dirent.ext_size + 1));
2N/A }
2N/A
2N/A mutex_destroy(&hp->hs_contents_lock);
2N/A {
2N/A vnode_t *vp;
2N/A
2N/A vp = hp->hs_vnode;
2N/A bzero((caddr_t)hp, sizeof (*hp));
2N/A hp->hs_vnode = vp;
2N/A vn_reinit(vp);
2N/A }
2N/A return (hp);
2N/A}
2N/A
2N/A/*
2N/A * Remove an hsnode from the free list.
2N/A */
2N/Astatic void
2N/Ahs_remfree(struct hsfs *fsp, struct hsnode *hp)
2N/A{
2N/A mutex_enter(&fsp->hsfs_free_lock);
2N/A if (hp->hs_freef != NULL)
2N/A hp->hs_freef->hs_freeb = hp->hs_freeb;
2N/A else
2N/A fsp->hsfs_free_b = hp->hs_freeb;
2N/A if (hp->hs_freeb != NULL)
2N/A hp->hs_freeb->hs_freef = hp->hs_freef;
2N/A else
2N/A fsp->hsfs_free_f = hp->hs_freef;
2N/A mutex_exit(&fsp->hsfs_free_lock);
2N/A}
2N/A
2N/A/*
2N/A * Look for hsnode in hash list.
2N/A * If the inode number is != HS_DUMMY_INO (16), then only the inode
2N/A * number is used for the check.
2N/A * If the inode number is == HS_DUMMY_INO, we additionally always
2N/A * check the directory offset for the file to avoid caching the
2N/A * meta data for all zero sized to the first zero sized file that
2N/A * was touched.
2N/A *
2N/A * If found, reactivate it if inactive.
2N/A *
2N/A * Must be entered with hsfs_hash_lock held.
2N/A */
2N/Astruct vnode *
2N/Ahs_findhash(ino64_t nodeid, uint_t lbn, uint_t off, struct vfs *vfsp)
2N/A{
2N/A struct hsnode *tp;
2N/A struct hsfs *fsp;
2N/A
2N/A fsp = VFS_TO_HSFS(vfsp);
2N/A
2N/A ASSERT(RW_LOCK_HELD(&fsp->hsfs_hash_lock));
2N/A
2N/A for (tp = fsp->hsfs_hash[HS_HASH(nodeid)]; tp != NULL;
2N/A tp = tp->hs_hash) {
2N/A if (tp->hs_nodeid == nodeid) {
2N/A struct vnode *vp;
2N/A
2N/A if (nodeid == HS_DUMMY_INO) {
2N/A /*
2N/A * If this is the dummy inode number, look for
2N/A * matching dir_lbn and dir_off.
2N/A */
2N/A for (; tp != NULL; tp = tp->hs_hash) {
2N/A if (tp->hs_nodeid == nodeid &&
2N/A tp->hs_dir_lbn == lbn &&
2N/A tp->hs_dir_off == off)
2N/A break;
2N/A }
2N/A if (tp == NULL)
2N/A return (NULL);
2N/A }
2N/A
2N/A mutex_enter(&tp->hs_contents_lock);
2N/A vp = HTOV(tp);
2N/A VN_HOLD(vp);
2N/A if ((tp->hs_flags & HREF) == 0) {
2N/A tp->hs_flags |= HREF;
2N/A /*
2N/A * reactivating a free hsnode:
2N/A * remove from free list
2N/A */
2N/A hs_remfree(fsp, tp);
2N/A }
2N/A mutex_exit(&tp->hs_contents_lock);
2N/A return (vp);
2N/A }
2N/A }
2N/A return (NULL);
2N/A}
2N/A
2N/Astatic void
2N/Ahs_addhash(struct hsfs *fsp, struct hsnode *hp)
2N/A{
2N/A ulong_t hashno;
2N/A
2N/A ASSERT(RW_WRITE_HELD(&fsp->hsfs_hash_lock));
2N/A
2N/A hashno = HS_HPASH(hp);
2N/A hp->hs_hash = fsp->hsfs_hash[hashno];
2N/A fsp->hsfs_hash[hashno] = hp;
2N/A}
2N/A
2N/A/*
2N/A * Destroy all old pages and free the hsnodes
2N/A * Return 1 if busy (a hsnode is still referenced).
2N/A */
2N/Aint
2N/Ahs_synchash(struct vfs *vfsp)
2N/A{
2N/A struct hsfs *fsp;
2N/A int i;
2N/A struct hsnode *hp, *nhp;
2N/A int busy = 0;
2N/A struct vnode *vp, *rvp;
2N/A
2N/A fsp = VFS_TO_HSFS(vfsp);
2N/A rvp = fsp->hsfs_rootvp;
2N/A /* make sure no one can come in */
2N/A rw_enter(&fsp->hsfs_hash_lock, RW_WRITER);
2N/A for (i = 0; i < HS_HASHSIZE; i++) {
2N/A for (hp = fsp->hsfs_hash[i]; hp != NULL; hp = hp->hs_hash) {
2N/A vp = HTOV(hp);
2N/A if ((hp->hs_flags & HREF) && (vp != rvp ||
2N/A (vp == rvp && vp->v_count > 1))) {
2N/A busy = 1;
2N/A continue;
2N/A }
2N/A if (vn_has_cached_data(vp))
2N/A (void) pvn_vplist_dirty(vp, (u_offset_t)0,
2N/A hsfs_putapage, B_INVAL,
2N/A (struct cred *)NULL);
2N/A }
2N/A }
2N/A if (busy) {
2N/A rw_exit(&fsp->hsfs_hash_lock);
2N/A return (1);
2N/A }
2N/A
2N/A /* now free the hsnodes */
2N/A for (i = 0; i < HS_HASHSIZE; i++) {
2N/A for (hp = fsp->hsfs_hash[i]; hp != NULL; hp = nhp) {
2N/A nhp = hp->hs_hash;
2N/A /*
2N/A * We know there are no pages associated with
2N/A * all the hsnodes (they've all been released
2N/A * above). So remove from free list and
2N/A * free the entry with nopage set.
2N/A */
2N/A vp = HTOV(hp);
2N/A if (vp != rvp) {
2N/A hs_remfree(fsp, hp);
2N/A hs_freenode(vp, fsp, 1);
2N/A }
2N/A }
2N/A }
2N/A
2N/A ASSERT(fsp->hsfs_nohsnode == 1);
2N/A rw_exit(&fsp->hsfs_hash_lock);
2N/A /* release the root hsnode, this should free the final hsnode */
2N/A VN_RELE(rvp);
2N/A
2N/A return (0);
2N/A}
2N/A
2N/A/*
2N/A * hs_makenode
2N/A *
2N/A * Construct an hsnode.
2N/A * Caller specifies the directory entry, the block number and offset
2N/A * of the directory entry, and the vfs pointer.
2N/A * note: off is the sector offset, not lbn offset
2N/A * if NULL is returned implies file system hsnode table full
2N/A */
2N/Astruct vnode *
2N/Ahs_makenode(
2N/A struct hs_direntry *dp,
2N/A uint_t lbn,
2N/A uint_t off,
2N/A struct vfs *vfsp)
2N/A{
2N/A struct hsnode *hp;
2N/A struct vnode *vp;
2N/A struct hs_volume *hvp;
2N/A struct vnode *newvp;
2N/A struct hsfs *fsp;
2N/A ino64_t nodeid;
2N/A
2N/A fsp = VFS_TO_HSFS(vfsp);
2N/A
2N/A /*
2N/A * Construct the data that allows us to re-read the meta data without
2N/A * knowing the name of the file: in the case of a directory
2N/A * entry, this should point to the canonical dirent, the "."
2N/A * directory entry for the directory. This dirent is pointed
2N/A * to by all directory entries for that dir (including the ".")
2N/A * entry itself.
2N/A * In the case of a file, simply point to the dirent for that
2N/A * file (there are hard links in Rock Ridge, so we need to use
2N/A * different data to contruct the node id).
2N/A */
2N/A if (dp->type == VDIR) {
2N/A lbn = dp->ext_lbn;
2N/A off = 0;
2N/A }
2N/A
2N/A /*
2N/A * Normalize lbn and off before creating a nodeid
2N/A * and before storing them in a hs_node structure
2N/A */
2N/A hvp = &fsp->hsfs_vol;
2N/A lbn += off >> hvp->lbn_shift;
2N/A off &= hvp->lbn_maxoffset;
2N/A /*
2N/A * If the media carries rrip-v1.12 or newer, and we trust the inodes
2N/A * from the rrip data (use_rrip_inodes != 0), use that data. If the
2N/A * media has been created by a recent mkisofs version, we may trust
2N/A * all numbers in the starting extent number; otherwise, we cannot
2N/A * do this for zero sized files and symlinks, because if we did we'd
2N/A * end up mapping all of them to the same node.
2N/A * We use HS_DUMMY_INO in this case and make sure that we will not
2N/A * map all files to the same meta data.
2N/A */
2N/A if (dp->inode != 0 && use_rrip_inodes) {
2N/A nodeid = dp->inode;
2N/A } else if ((dp->ext_size == 0 || dp->sym_link != (char *)NULL) &&
2N/A (fsp->hsfs_flags & HSFSMNT_INODE) == 0) {
2N/A nodeid = HS_DUMMY_INO;
2N/A } else {
2N/A nodeid = dp->ext_lbn;
2N/A }
2N/A
2N/A /* look for hsnode in cache first */
2N/A
2N/A rw_enter(&fsp->hsfs_hash_lock, RW_READER);
2N/A
2N/A if ((vp = hs_findhash(nodeid, lbn, off, vfsp)) == NULL) {
2N/A
2N/A /*
2N/A * Not in cache. However, someone else may have come
2N/A * to the same conclusion and just put one in. Upgrade
2N/A * our lock to a write lock and look again.
2N/A */
2N/A rw_exit(&fsp->hsfs_hash_lock);
2N/A rw_enter(&fsp->hsfs_hash_lock, RW_WRITER);
2N/A
2N/A if ((vp = hs_findhash(nodeid, lbn, off, vfsp)) == NULL) {
2N/A /*
2N/A * Now we are really sure that the hsnode is not
2N/A * in the cache. Get one off freelist or else
2N/A * allocate one. Either way get a bzeroed hsnode.
2N/A */
2N/A hp = hs_getfree(fsp);
2N/A
2N/A bcopy((caddr_t)dp, (caddr_t)&hp->hs_dirent,
2N/A sizeof (*dp));
2N/A /*
2N/A * We've just copied this pointer into hs_dirent,
2N/A * and don't want 2 references to same symlink.
2N/A */
2N/A dp->sym_link = (char *)NULL;
2N/A
2N/A /*
2N/A * No need to hold any lock because hsnode is not
2N/A * yet in the hash chain.
2N/A */
2N/A mutex_init(&hp->hs_contents_lock, NULL, MUTEX_DEFAULT,
2N/A NULL);
2N/A hp->hs_dir_lbn = lbn;
2N/A hp->hs_dir_off = off;
2N/A hp->hs_nodeid = nodeid;
2N/A hp->hs_seq = 0;
2N/A hp->hs_flags = HREF;
2N/A if (off > HS_SECTOR_SIZE)
2N/A cmn_err(CE_WARN, "hs_makenode: bad offset");
2N/A
2N/A vp = HTOV(hp);
2N/A vp->v_vfsp = vfsp;
2N/A vp->v_type = dp->type;
2N/A vp->v_rdev = dp->r_dev;
2N/A vn_setops(vp, hsfs_vnodeops);
2N/A vp->v_data = (caddr_t)hp;
2N/A vn_exists(vp);
2N/A /*
2N/A * if it's a device, call specvp
2N/A */
2N/A if (IS_DEVVP(vp)) {
2N/A rw_exit(&fsp->hsfs_hash_lock);
2N/A newvp = specvp(vp, vp->v_rdev, vp->v_type,
2N/A CRED());
2N/A if (newvp == NULL)
2N/A cmn_err(CE_NOTE,
2N/A "hs_makenode: specvp failed");
2N/A VN_RELE(vp);
2N/A return (newvp);
2N/A }
2N/A
2N/A hs_addhash(fsp, hp);
2N/A
2N/A }
2N/A }
2N/A
2N/A if (dp->sym_link != (char *)NULL) {
2N/A kmem_free(dp->sym_link, (size_t)(dp->ext_size + 1));
2N/A dp->sym_link = (char *)NULL;
2N/A }
2N/A
2N/A rw_exit(&fsp->hsfs_hash_lock);
2N/A return (vp);
2N/A}
2N/A
2N/A/*
2N/A * hs_freenode
2N/A *
2N/A * Deactivate an hsnode.
2N/A * Leave it on the hash list but put it on the free list.
2N/A * If the vnode does not have any pages, release the hsnode to the
2N/A * kmem_cache using kmem_cache_free, else put in back of the free list.
2N/A *
2N/A * This function can be called with the hsfs_free_lock held, but only
2N/A * when the code is guaranteed to go through the path where the
2N/A * node is freed entirely, and not the path where the node could go back
2N/A * on the free list (and where the free lock would need to be acquired).
2N/A */
2N/Avoid
2N/Ahs_freenode(vnode_t *vp, struct hsfs *fsp, int nopage)
2N/A{
2N/A struct hsnode **tp;
2N/A struct hsnode *hp = VTOH(vp);
2N/A
2N/A ASSERT(RW_LOCK_HELD(&fsp->hsfs_hash_lock));
2N/A
2N/A if (nopage || (fsp->hsfs_nohsnode >= nhsnode)) {
2N/A /* remove this node from the hash list, if it's there */
2N/A for (tp = &fsp->hsfs_hash[HS_HPASH(hp)]; *tp != NULL;
2N/A tp = &(*tp)->hs_hash) {
2N/A
2N/A if (*tp == hp) {
2N/A *tp = hp->hs_hash;
2N/A break;
2N/A }
2N/A }
2N/A
2N/A 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);
}