1180 lines
26 KiB
C
1180 lines
26 KiB
C
#ifndef lint
|
|
#ident "@(#)hsfs_node.c 1.8 93/11/18"
|
|
#endif
|
|
|
|
/*
|
|
* Directory operations for High Sierra filesystem
|
|
* Copyright (c) 1989 by Sun Microsystem, Inc.
|
|
*/
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/ucred.h>
|
|
#include <sys/time.h>
|
|
#include <sys/buf.h>
|
|
#include <sys/vfs.h>
|
|
#include <sys/vnode.h>
|
|
#include <ufs/inode.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/errno.h>
|
|
#include <sys/dirent.h>
|
|
|
|
#include <hsfs/hsfs_spec.h>
|
|
#include <hsfs/hsfs_isospec.h>
|
|
#include <hsfs/hsfs_node.h>
|
|
#include <hsfs/hsfs_private.h>
|
|
#include <hsfs/hsfs_susp.h>
|
|
#include <hsfs/hsfs_rrip.h>
|
|
|
|
#include <vm/pvn.h>
|
|
#include <vm/seg.h>
|
|
#include <vm/seg_map.h>
|
|
#include <vm/seg_vn.h>
|
|
#include <vm/rm.h>
|
|
#include <vm/swap.h>
|
|
#include <machine/seg_kmem.h>
|
|
|
|
|
|
enum dirblock_result { FOUND_ENTRY, WENT_PAST, HIT_END };
|
|
static enum dirblock_result process_dirblock();
|
|
|
|
extern struct vnodeops hsfs_vnodeops;
|
|
extern char *strncpy();
|
|
|
|
static void hs_remhash();
|
|
static void hs_addfreeb();
|
|
static void hs_addfreef();
|
|
|
|
/*
|
|
* hs_access
|
|
* Return 0 if the desired access may be granted.
|
|
* Otherwise return error code.
|
|
*/
|
|
int
|
|
hs_access(vp, m, cred)
|
|
register struct vnode *vp;
|
|
register int m;
|
|
register struct ucred *cred;
|
|
{
|
|
register int *gp;
|
|
register struct hsnode *hp;
|
|
|
|
if (m & VWRITE) {
|
|
if (vp->v_vfsp->vfs_flag & VFS_RDONLY)
|
|
return (EROFS);
|
|
}
|
|
if (cred->cr_uid == 0)
|
|
return (0); /* super-user always gets access */
|
|
|
|
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 (cred->cr_uid != hp->hs_dirent.uid) {
|
|
m >>= 3;
|
|
/*
|
|
* Have to check groups manually since groupmember()
|
|
* checks uarea instead of a credentials list.
|
|
*/
|
|
if (cred->cr_gid == hp->hs_dirent.gid)
|
|
goto found;
|
|
for (gp = cred->cr_groups;
|
|
gp < &(cred->cr_groups[NGROUPS]) && *gp != NOGROUP;
|
|
gp++)
|
|
if (*gp == hp->hs_dirent.gid)
|
|
goto found;
|
|
m >>= 3;
|
|
}
|
|
found:
|
|
if ((m & hp->hs_dirent.mode) == m)
|
|
return (0);
|
|
return (EACCES);
|
|
}
|
|
|
|
#if ((HS_HASHSIZE & (HS_HASHSIZE - 1)) == 0)
|
|
#define HS_HASH(l) ((u_long)(l) & (HS_HASHSIZE - 1))
|
|
#else
|
|
#define HS_HASH(l) ((u_long)(l) % HS_HASHSIZE)
|
|
#endif
|
|
#define HS_HPASH(hp) HS_HASH((hp)->hs_nodeid)
|
|
|
|
extern int physmem;
|
|
|
|
/*
|
|
* initialize incore hsnode table size
|
|
*
|
|
*/
|
|
struct hstable *
|
|
hs_inithstbl(vfsp)
|
|
struct vfs *vfsp;
|
|
{
|
|
register struct hstable *htp;
|
|
int size;
|
|
int i;
|
|
int nohsnode;
|
|
|
|
/* allocate incore hsnode space based on memory size */
|
|
/* minimum 16 K for 4M machine, 64K for others */
|
|
size = (physmem <= 512) ? HS_HSTABLESIZE : 4 * HS_HSTABLESIZE;
|
|
|
|
htp = (struct hstable *)new_kmem_alloc((u_int)size, KMEM_SLEEP);
|
|
htp -> hs_vfsp = vfsp;
|
|
htp -> hs_tablesize = size;
|
|
for (i = 0; i < HS_HASHSIZE; i++)
|
|
htp->hshash[i] = NULL;
|
|
htp->hsfree_f = NULL;
|
|
htp->hsfree_b = NULL;
|
|
htp->hs_refct = 0;
|
|
htp->hs_nohsnode = nohsnode
|
|
= (size - sizeof (struct hstable) + sizeof (struct hsnode))/
|
|
sizeof (struct hsnode);
|
|
|
|
for (i = 0; i < nohsnode; i++) {
|
|
htp->hs_node[i].hs_dirent.ext_lbn = 0;
|
|
htp->hs_node[i].hs_hash = NULL;
|
|
htp->hs_node[i].hs_freef = NULL;
|
|
htp->hs_node[i].hs_freeb = NULL;
|
|
htp->hs_node[i].hs_dirent.sym_link = (char *)NULL;
|
|
(void) hs_addfreeb(htp, &htp->hs_node[i]);
|
|
}
|
|
|
|
/*
|
|
* could initialize more stuff in this routine (e.g. vnode)
|
|
* do it next time...
|
|
*/
|
|
|
|
return (htp);
|
|
}
|
|
|
|
/*
|
|
* initialize incore hsnode table size
|
|
*
|
|
*/
|
|
void
|
|
hs_freehstbl(vfsp)
|
|
struct vfs *vfsp;
|
|
{
|
|
register struct hstable *htp;
|
|
|
|
htp = ((struct hsfs *)VFS_TO_HSFS(vfsp))->hsfs_hstbl;
|
|
kmem_free((caddr_t) htp, (u_int) htp->hs_tablesize);
|
|
}
|
|
|
|
/*
|
|
* Add an hsnode to the end of the free list.
|
|
*/
|
|
static void
|
|
hs_addfreeb(htp, hp)
|
|
register struct hstable *htp;
|
|
register struct hsnode *hp;
|
|
{
|
|
register struct hsnode *ep;
|
|
|
|
ep = htp->hsfree_b;
|
|
htp->hsfree_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)
|
|
htp->hsfree_f = hp; /* hp is only entry in free list */
|
|
else
|
|
ep->hs_freef = hp; /* point previous last entry at hp */
|
|
}
|
|
|
|
/*
|
|
* Add an hsnode to the front of the free list.
|
|
*/
|
|
static void
|
|
hs_addfreef(htp, hp)
|
|
register struct hstable *htp;
|
|
register struct hsnode *hp;
|
|
{
|
|
register struct hsnode *ep;
|
|
|
|
ep = htp->hsfree_f;
|
|
htp->hsfree_f = hp; /* hp is the first entry in free list */
|
|
hp->hs_freeb = NULL;
|
|
hp->hs_freef = ep; /* point at previous last entry */
|
|
if (ep == NULL)
|
|
htp->hsfree_b = hp; /* hp is only entry in free list */
|
|
else
|
|
ep->hs_freeb = hp; /* point previous last entry at hp */
|
|
}
|
|
|
|
/*
|
|
* Get an hsnode from the front of the free list.
|
|
*/
|
|
static struct hsnode *
|
|
hs_getfree(htp)
|
|
register struct hstable *htp;
|
|
{
|
|
register struct hsnode *hp;
|
|
|
|
hp = htp->hsfree_f;
|
|
if (hp != NULL) {
|
|
htp->hsfree_f = hp->hs_freef;
|
|
if (htp->hsfree_f != NULL)
|
|
htp->hsfree_f->hs_freeb = NULL;
|
|
else
|
|
htp->hsfree_b = NULL;
|
|
} else
|
|
printf("hsfs: hsnode table full\n");
|
|
return (hp);
|
|
}
|
|
|
|
/*
|
|
* Remove an hsnode from the free list.
|
|
*/
|
|
static void
|
|
hs_remfree(htp, hp)
|
|
register struct hstable *htp;
|
|
register struct hsnode *hp;
|
|
{
|
|
|
|
if (hp->hs_freef != NULL)
|
|
hp->hs_freef->hs_freeb = hp->hs_freeb;
|
|
else
|
|
htp->hsfree_b = hp->hs_freeb;
|
|
if (hp->hs_freeb != NULL)
|
|
hp->hs_freeb->hs_freef = hp->hs_freef;
|
|
else
|
|
htp->hsfree_f = hp->hs_freef;
|
|
}
|
|
|
|
/*
|
|
* Look for hsnode in hash list.
|
|
* Check equality of fsid and starting block of data extent.
|
|
* If found, reactivate it if inactive.
|
|
*/
|
|
struct vnode *
|
|
hs_findhash(nodeid, vfsp)
|
|
u_long nodeid;
|
|
struct vfs *vfsp;
|
|
{
|
|
register struct hsnode *tp;
|
|
register struct hstable *htp;
|
|
|
|
htp = ((struct hsfs *)VFS_TO_HSFS(vfsp))->hsfs_hstbl;
|
|
for (tp = htp->hshash[HS_HASH(nodeid)]; tp != NULL; tp = tp->hs_hash) {
|
|
if (tp->hs_nodeid == nodeid) {
|
|
if ((HTOV(tp)->v_count)++ == 0) {
|
|
/*
|
|
* reactivating a free hsnode:
|
|
* remove from free list
|
|
*/
|
|
hs_remfree(htp, tp);
|
|
(htp->hs_refct)++;
|
|
}
|
|
return (HTOV(tp));
|
|
}
|
|
}
|
|
return (NULL);
|
|
}
|
|
|
|
static void
|
|
hs_addhash(htp, hp)
|
|
register struct hstable *htp;
|
|
struct hsnode *hp;
|
|
{
|
|
register u_int hashno;
|
|
|
|
hashno = (u_int)HS_HPASH(hp);
|
|
hp->hs_hash = htp->hshash[hashno];
|
|
htp->hshash[hashno] = hp;
|
|
}
|
|
|
|
static void
|
|
hs_remhash(htp, hp)
|
|
register struct hstable *htp;
|
|
struct hsnode *hp;
|
|
{
|
|
register struct hsnode **tp;
|
|
|
|
for (tp = &htp->hshash[HS_HPASH(hp)];
|
|
*tp != NULL; tp = &(*tp)->hs_hash) {
|
|
if (*tp == hp) {
|
|
struct vnode *vp;
|
|
|
|
vp = HTOV(hp);
|
|
/* file is no longer reference, destroy all old pages */
|
|
if (vp->v_pages != NULL)
|
|
/* pvn_vplist_dirty will abort all old pages */
|
|
(void) pvn_vplist_dirty(vp, 0, B_INVAL);
|
|
*tp = hp->hs_hash;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* destroy all old pages */
|
|
void
|
|
hs_synchash(vfsp)
|
|
struct vfs *vfsp;
|
|
{
|
|
register struct hstable *htp;
|
|
register int i;
|
|
register struct hsnode *hp;
|
|
|
|
htp = ((struct hsfs *)VFS_TO_HSFS(vfsp))->hsfs_hstbl;
|
|
for (i = 0; i < HS_HASHSIZE; i++) {
|
|
for (hp = htp->hshash[i]; hp != NULL; hp = hp->hs_hash) {
|
|
if ((HTOV(hp))->v_pages != NULL)
|
|
(void) pvn_vplist_dirty((HTOV(hp)), 0, B_INVAL);
|
|
}
|
|
}
|
|
|
|
/* now destroy all sym_link buffers */
|
|
for (i = 0; i < HS_HASHSIZE; i++) {
|
|
for (hp = htp->hshash[i]; hp != NULL; hp = hp->hs_hash) {
|
|
if (hp->hs_dirent.sym_link != (char *)NULL) {
|
|
kmem_free((caddr_t)hp->hs_dirent.sym_link,
|
|
(u_int) SYM_LINK_LEN(hp->hs_dirent.sym_link));
|
|
hp->hs_dirent.sym_link = (char *)NULL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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(dp, lbn, off, vfsp)
|
|
struct hs_direntry *dp;
|
|
u_int lbn;
|
|
u_int off;
|
|
struct vfs *vfsp;
|
|
{
|
|
register struct hsnode *hp;
|
|
register struct vnode *vp;
|
|
register struct hstable *htp;
|
|
struct vnode *newvp;
|
|
u_long nodeid;
|
|
|
|
|
|
htp = ((struct hsfs *)VFS_TO_HSFS(vfsp))->hsfs_hstbl;
|
|
|
|
/*
|
|
* Construct the nodeid: 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 no hard links in Rock Ridge, so there's no
|
|
* need to determine what the canonical dirent is.
|
|
*/
|
|
if (dp->type == VDIR) {
|
|
lbn = dp->ext_lbn;
|
|
off = 0;
|
|
}
|
|
nodeid = MAKE_NODEID(lbn, off, vfsp);
|
|
|
|
/* look for hsnode in cache first */
|
|
if ((vp = hs_findhash(nodeid, vfsp)) == NULL) {
|
|
/*
|
|
* not in cache, get one off freelist or else allocate one
|
|
*/
|
|
if ((hp = hs_getfree(htp)) != NULL) {
|
|
hs_remhash(htp, hp);
|
|
} else {
|
|
return (NULL);
|
|
}
|
|
|
|
if (hp->hs_dirent.sym_link != (char *)NULL)
|
|
kmem_free(hp->hs_dirent.sym_link,
|
|
(u_int) SYM_LINK_LEN(hp->hs_dirent.sym_link));
|
|
|
|
bzero((caddr_t)hp, sizeof (*hp));
|
|
bcopy((caddr_t)dp, (caddr_t)&hp->hs_dirent, sizeof (*dp));
|
|
dp->sym_link = (char *)NULL;
|
|
hp->hs_dir_lbn = lbn;
|
|
hp->hs_dir_off = off;
|
|
hp->hs_nodeid = nodeid;
|
|
hp->hs_offset = 0;
|
|
if (off > HS_SECTOR_SIZE)
|
|
printf("hs_makenode: bad offset\n");
|
|
|
|
/* initialize for VDIR */
|
|
hp->hs_ptbl_idx = NULL;
|
|
vp = HTOV(hp);
|
|
VN_INIT(vp, vfsp, dp->type, dp->r_dev);
|
|
vp->v_op = &hsfs_vnodeops;
|
|
vp->v_data = (caddr_t)hp;
|
|
|
|
|
|
/*
|
|
* if it's a device, call specvp
|
|
*/
|
|
if (ISVDEV(vp->v_type)) {
|
|
newvp = specvp(vp, vp->v_rdev, vp->v_type);
|
|
VN_RELE(vp);
|
|
vp = newvp;
|
|
goto end;
|
|
}
|
|
|
|
hs_addhash(htp, hp);
|
|
(htp->hs_refct)++;
|
|
}
|
|
|
|
end:
|
|
if (dp->sym_link != (char *)NULL) {
|
|
kmem_free(dp->sym_link,
|
|
(u_int) SYM_LINK_LEN(dp->sym_link));
|
|
dp->sym_link = (char *)NULL;
|
|
}
|
|
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, put in front of free list
|
|
* else put in back of the free list
|
|
*
|
|
*/
|
|
void
|
|
hs_freenode(hp, vfsp, nopage)
|
|
struct hsnode *hp;
|
|
struct vfs *vfsp;
|
|
int nopage; /* 1 if no page, 0 otherwise */
|
|
{
|
|
register struct hstable *htp;
|
|
|
|
htp = ((struct hsfs *)VFS_TO_HSFS(vfsp))->hsfs_hstbl;
|
|
if (nopage)
|
|
hs_addfreef(htp, hp); /* add to front of free list */
|
|
else
|
|
hs_addfreeb(htp, hp); /* add to back of free list */
|
|
(htp->hs_refct)--;
|
|
}
|
|
|
|
/*
|
|
* 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(lbn, off, vfsp, vpp)
|
|
u_int lbn;
|
|
u_int off;
|
|
struct vfs *vfsp;
|
|
struct vnode **vpp;
|
|
{
|
|
register struct hsfs *fsp;
|
|
register u_int secno;
|
|
u_char *dirp;
|
|
struct hs_direntry hd;
|
|
int error;
|
|
u_char *buffer;
|
|
|
|
buffer = (u_char *)new_kmem_alloc((size_t)HS_SECTOR_SIZE, KMEM_SLEEP);
|
|
|
|
if (buffer == (u_char *)NULL)
|
|
return (ENOMEM);
|
|
|
|
bzero ((caddr_t) &hd, sizeof (hd));
|
|
|
|
/* Convert to sector and offset */
|
|
fsp = VFS_TO_HSFS(vfsp);
|
|
if (off > HS_SECTOR_SIZE) {
|
|
printf("hs_remakenode: bad offset\n");
|
|
error = EINVAL;
|
|
goto end;
|
|
}
|
|
secno = LBN_TO_SEC(lbn, vfsp);
|
|
dirp = buffer;
|
|
|
|
/* Read sector and parse directory entry */
|
|
if (error = hs_readsector(fsp->hsfs_devvp, secno, dirp))
|
|
goto end;
|
|
|
|
error = hs_parsedir(fsp, &dirp[off], &hd, (char *)NULL, (int *)NULL);
|
|
|
|
if (!error) {
|
|
*vpp = hs_makenode(&hd, lbn, off, vfsp);
|
|
if (*vpp == NULL)
|
|
error = ENFILE;
|
|
}
|
|
|
|
end:
|
|
(void) kmem_free((caddr_t)buffer, (size_t)HS_SECTOR_SIZE);
|
|
return (error);
|
|
}
|
|
|
|
|
|
/*
|
|
* hs_dirlook
|
|
*
|
|
* Look for a given name in a given directory.
|
|
* If found, construct an hsnode for it.
|
|
*/
|
|
int
|
|
hs_dirlook(dvp, name, namlen, vpp, cred)
|
|
struct vnode *dvp;
|
|
register char *name;
|
|
int namlen; /* length of 'name' */
|
|
struct vnode **vpp;
|
|
struct ucred *cred;
|
|
{
|
|
register struct hsnode *dhp;
|
|
struct hsfs *fsp;
|
|
int error = 0;
|
|
u_int offset; /* real offset in directory */
|
|
u_int last_offset; /* last index into current dir block */
|
|
char *cmpname; /* case-folded name */
|
|
int cmpnamelen;
|
|
int adhoc_search; /* did we start at begin of dir? */
|
|
int end;
|
|
u_int hsoffset;
|
|
struct fbuf *fbp;
|
|
int bytes_wanted;
|
|
int dirsiz;
|
|
int is_rrip;
|
|
|
|
if (error = hs_access(dvp, (int) VEXEC, cred))
|
|
return (error);
|
|
|
|
cmpname = (char *)new_kmem_alloc(MAXNAMELEN + 1, KMEM_SLEEP);
|
|
if (cmpname == (char *)NULL)
|
|
return (ENOMEM);
|
|
|
|
dhp = VTOH(dvp);
|
|
fsp = VFS_TO_HSFS(dvp->v_vfsp);
|
|
|
|
is_rrip = IS_RRIP_IMPLEMENTED(fsp);
|
|
|
|
/*
|
|
* For the purposes of comparing the name against dir entries,
|
|
* fold it to upper case.
|
|
*/
|
|
if (is_rrip) {
|
|
(void) strcpy(cmpname, name);
|
|
cmpnamelen = 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) {
|
|
|
|
if ((offset & MAXBMASK) + MAXBSIZE > dirsiz)
|
|
bytes_wanted = dirsiz - (offset & MAXBMASK);
|
|
else
|
|
bytes_wanted = MAXBSIZE;
|
|
|
|
error = fbread(dvp, (offset & MAXBMASK),
|
|
(unsigned int) bytes_wanted, S_READ, &fbp);
|
|
if (error) {
|
|
fbrelse(fbp, S_READ);
|
|
goto done;
|
|
}
|
|
last_offset = (offset & MAXBMASK) + fbp->fb_count - 1;
|
|
|
|
#define rel_offset(offset) ((offset) & MAXBOFFSET) /* index into cur blk */
|
|
|
|
switch (process_dirblock((u_char *)fbp->fb_addr, &offset,
|
|
last_offset, cmpname,
|
|
cmpnamelen, fsp, dhp, dvp,
|
|
vpp, &error, is_rrip)) {
|
|
case FOUND_ENTRY:
|
|
/* found an entry, either correct or not */
|
|
fbrelse(fbp, S_READ);
|
|
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.
|
|
*/
|
|
fbrelse(fbp, S_READ);
|
|
if (adhoc_search) {
|
|
offset = 0;
|
|
end = hsoffset;
|
|
adhoc_search = 0;
|
|
goto tryagain;
|
|
} else {
|
|
error = ENOENT;
|
|
goto done;
|
|
}
|
|
/* NOTREACHED */
|
|
break;
|
|
|
|
case HIT_END:
|
|
fbrelse(fbp, S_READ);
|
|
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:
|
|
kmem_free(cmpname, MAXNAMELEN + 1);
|
|
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(fsp, dirp, hdp, dnp, dnlen)
|
|
struct hsfs *fsp;
|
|
u_char *dirp;
|
|
struct hs_direntry *hdp;
|
|
char *dnp;
|
|
int *dnlen;
|
|
{
|
|
u_char flags;
|
|
int namelen, 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);
|
|
if (fsp->hsfs_vol_type == HS_VOL_TYPE_HS) {
|
|
flags = HDE_FLAGS(dirp);
|
|
(void) hs_parse_dirdate(HS_VOL_TYPE_HS,
|
|
HDE_cdate(dirp), &hdp->cdate);
|
|
(void) hs_parse_dirdate(HS_VOL_TYPE_HS,
|
|
HDE_cdate(dirp), &hdp->adate);
|
|
(void) hs_parse_dirdate(HS_VOL_TYPE_HS,
|
|
HDE_cdate(dirp), &hdp->mdate);
|
|
if (HDE_REGULAR_FILE(flags)) {
|
|
hdp->type = VREG;
|
|
hdp->mode = IFREG;
|
|
hdp->nlink = 1;
|
|
} else if (HDE_REGULAR_DIR(flags)) {
|
|
hdp->type = VDIR;
|
|
hdp->mode = IFDIR;
|
|
hdp->nlink = 2;
|
|
} else {
|
|
printf("hsfs: filetype (0x%x) not supported\n", 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) {
|
|
flags = IDE_FLAGS(dirp);
|
|
(void) hs_parse_dirdate(HS_VOL_TYPE_ISO,
|
|
IDE_cdate(dirp), &hdp->cdate);
|
|
(void) hs_parse_dirdate(HS_VOL_TYPE_ISO,
|
|
IDE_cdate(dirp), &hdp->adate);
|
|
(void) hs_parse_dirdate(HS_VOL_TYPE_ISO,
|
|
IDE_cdate(dirp), &hdp->mdate);
|
|
if (HDE_REGULAR_FILE(flags)) {
|
|
hdp->type = VREG;
|
|
hdp->mode = IFREG;
|
|
hdp->nlink = 1;
|
|
} else if (HDE_REGULAR_DIR(flags)) {
|
|
hdp->type = VDIR;
|
|
hdp->mode = IFDIR;
|
|
hdp->nlink = 2;
|
|
} else {
|
|
printf("hsfs: filetype (0x%x) not supported\n", 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);
|
|
/*
|
|
* 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((u_char *)dnp, dnlen,
|
|
&name_change_flag, dirp, hdp, fsp,
|
|
(u_char *)NULL, NULL);
|
|
if (error) {
|
|
if (hdp->sym_link) {
|
|
kmem_free((caddr_t)hdp->sym_link,
|
|
(u_int) SYM_LINK_LEN(hdp->sym_link));
|
|
hdp->sym_link = (char *)NULL;
|
|
}
|
|
return (error);
|
|
}
|
|
}
|
|
|
|
}
|
|
hdp->xar_prot = (HDE_PROTECTION & flags) != 0;
|
|
|
|
#if dontskip
|
|
if (hdp->xar_len > 0) {
|
|
printf("hsfs: extended attributes not supported\n");
|
|
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)) {
|
|
printf("hsfs: interleaf size or skip factor error\n");
|
|
return (EINVAL);
|
|
}
|
|
if (hdp->ext_size == 0) {
|
|
printf("hsfs:");
|
|
printf(" interleaving specified on zero length file\n");
|
|
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)) {
|
|
printf("hsfs: multivolume file?\n");
|
|
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);
|
|
|
|
|
|
|
|
/* return the pointer to the directory name and its length */
|
|
if (dnp != NULL)
|
|
namelen = hs_namecopy((char *) HDE_name(dirp), dnp,
|
|
(int) HDE_NAME_LEN(dirp));
|
|
else
|
|
namelen = (int) HDE_NAME_LEN(dirp);
|
|
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.
|
|
*/
|
|
int
|
|
hs_namecopy(from, to, size)
|
|
char *from;
|
|
char *to;
|
|
int size;
|
|
{
|
|
register u_int i;
|
|
register u_char c;
|
|
register int lastspace;
|
|
|
|
/* 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);
|
|
}
|
|
}
|
|
|
|
for (i = 0, lastspace = -1; i < size; i++) {
|
|
c = from[i] & 0x7f;
|
|
if (c == ';')
|
|
break;
|
|
if (c <= ' ') {
|
|
if (lastspace == -1)
|
|
lastspace = i;
|
|
} else
|
|
lastspace = -1;
|
|
if ((c >= 'A') && (c <= 'Z'))
|
|
c += 'a' - 'A';
|
|
to[i] = c;
|
|
}
|
|
if (lastspace != -1)
|
|
i = lastspace;
|
|
to[i] = '\0';
|
|
return (i);
|
|
}
|
|
|
|
/*
|
|
* hs_uppercase_copy
|
|
*
|
|
* Convert a UNIX-style name into its HSFS equivalent.
|
|
* Map to upper case.
|
|
* Returns the (possibly new) length.
|
|
*/
|
|
hs_uppercase_copy(from, to, size)
|
|
char *from;
|
|
char *to;
|
|
int size;
|
|
{
|
|
register u_int i;
|
|
register u_char 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);
|
|
}
|
|
|
|
void
|
|
hs_filldirent(vp, hdp)
|
|
struct vnode *vp;
|
|
struct hs_direntry *hdp;
|
|
{
|
|
u_int secno;
|
|
u_int secoff;
|
|
struct vnode *realvp;
|
|
struct hsfs *fsp;
|
|
u_char *secp;
|
|
u_char *buffer;
|
|
|
|
if (vp->v_type != VDIR) {
|
|
printf("hsfs_filldirent: vp (0x%x) not a directory", vp);
|
|
return;
|
|
}
|
|
|
|
buffer = (u_char *)new_kmem_alloc((size_t)HS_SECTOR_SIZE, KMEM_SLEEP);
|
|
|
|
if (buffer == (u_char *)NULL)
|
|
return;
|
|
|
|
secp = buffer;
|
|
fsp = VFS_TO_HSFS(vp ->v_vfsp);
|
|
realvp = fsp -> hsfs_devvp;
|
|
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;
|
|
if (hs_readsector(realvp, secno, secp))
|
|
goto end;
|
|
|
|
/* quick check */
|
|
if (hdp->ext_lbn != HDE_EXT_LBN(&secp[secoff])) {
|
|
printf("hsfs_filldirent: dirent not match\n");
|
|
/* keep on going */
|
|
}
|
|
(void) hs_parsedir(fsp, &secp[secoff], hdp, (char *)NULL, (int *)NULL);
|
|
|
|
end:
|
|
(void) kmem_free((caddr_t)buffer, (size_t)HS_SECTOR_SIZE);
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* Look through a directory block for a matching entry.
|
|
*/
|
|
static enum dirblock_result
|
|
process_dirblock(blkp, offset, last_offset, nm, nmlen, fsp, dhp, dvp, vpp,
|
|
error, is_rrip)
|
|
u_char *blkp; /* dir block */
|
|
u_int *offset; /* lower index */
|
|
u_int last_offset; /* upper index */
|
|
char *nm; /* name 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 */
|
|
int is_rrip; /* 1 if rock ridge is implemented */
|
|
{
|
|
char *dname; /* name in directory entry */
|
|
char *cmpname; /* name for comparison */
|
|
int dnamelen; /* length of name */
|
|
int cmpnamelen; /* length of name for comparison */
|
|
struct hs_direntry hd;
|
|
int hdlen;
|
|
register u_char *dirp; /* the directory entry */
|
|
int res, parsedir_res;
|
|
char *rrip_name_str;
|
|
char *rrip_tmp_name;
|
|
enum dirblock_result err;
|
|
|
|
if (is_rrip) {
|
|
rrip_name_str = (char *)new_kmem_alloc(MAXNAMELEN + 1,
|
|
KMEM_SLEEP);
|
|
if (rrip_name_str == (char *)NULL) {
|
|
/*
|
|
* XXX we really should return an error indication
|
|
* here
|
|
*/
|
|
return (HIT_END);
|
|
}
|
|
rrip_tmp_name = (char *)new_kmem_alloc(MAXNAMELEN + 1,
|
|
KMEM_SLEEP);
|
|
if (rrip_tmp_name == (char *)NULL) {
|
|
/*
|
|
* XXX we really should return an error indication
|
|
* here
|
|
*/
|
|
kmem_free(rrip_name_str, MAXNAMELEN + 1);
|
|
return (HIT_END);
|
|
}
|
|
|
|
rrip_name_str[0] = '\0';
|
|
rrip_tmp_name[0] = '\0';
|
|
}
|
|
|
|
|
|
cmpname = (char *)new_kmem_alloc(MAXNAMELEN + 1, KMEM_SLEEP);
|
|
if (cmpname == (char *)NULL) {
|
|
/*
|
|
* XXX we really should return an error indication
|
|
* here
|
|
*/
|
|
err = HIT_END;
|
|
goto do_ret;
|
|
}
|
|
|
|
while (*offset < last_offset) {
|
|
|
|
/*
|
|
* Directory Entries cannot span sectors.
|
|
* Unused bytes at the end of each sector are zeroed.
|
|
* Therefore, detect this condition when the size
|
|
* field of the directory entry is zero.
|
|
*/
|
|
hdlen = (int)((u_char)
|
|
HDE_DIR_LEN(&blkp[rel_offset(*offset)]));
|
|
if (hdlen == 0) {
|
|
/* advance to next sector boundary */
|
|
*offset = (*offset & MAXHSMASK) + HS_SECTOR_SIZE;
|
|
|
|
if (*offset > last_offset) {
|
|
err = HIT_END; /* end of block */
|
|
goto do_ret;
|
|
} else
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* Zero out the hd to read new directory
|
|
*/
|
|
bzero((caddr_t)&hd, sizeof (hd));
|
|
|
|
/*
|
|
* Just ignore invalid directory entries.
|
|
* XXX - maybe hs_parsedir() will detect EXISTENCE bit
|
|
*/
|
|
dirp = &blkp[rel_offset(*offset)];
|
|
dname = (char *)HDE_name(dirp);
|
|
dnamelen = (int) ((u_char) HDE_NAME_LEN(dirp));
|
|
|
|
/*
|
|
* 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) {
|
|
int rr_namelen;
|
|
|
|
rrip_name_str[0] = '\0';
|
|
rr_namelen = rrip_namecopy(nm, &rrip_name_str[0],
|
|
&rrip_tmp_name[0], dirp, fsp, &hd);
|
|
if (hd.sym_link != (char *)NULL) {
|
|
kmem_free(hd.sym_link,
|
|
(u_int) SYM_LINK_LEN(hd.sym_link));
|
|
hd.sym_link = (char *)NULL;
|
|
}
|
|
if (rr_namelen == -1){ /* use iso name instead */
|
|
register int i;
|
|
for (i = dnamelen; (dname[i] != ';') &&
|
|
(i >= 0); i--);
|
|
if (dname[i] == ';')
|
|
dnamelen = i;
|
|
} else{
|
|
dname = (char *)&rrip_name_str[0];
|
|
dnamelen = rr_namelen;
|
|
}
|
|
} else {
|
|
register int i;
|
|
/*
|
|
* make sure that we get rid of ';' in the dname of
|
|
* an iso direntry, as we should have no knowledge
|
|
* of file versions.
|
|
*/
|
|
for (i = dnamelen; (dname[i] != ';') && (i >= 0); i--)
|
|
;
|
|
|
|
if (dname[i] == ';')
|
|
dnamelen = i;
|
|
}
|
|
|
|
|
|
/*
|
|
* For the purposes of comparing the name against dir entries,
|
|
* fold it to upper case.
|
|
*/
|
|
if (is_rrip) {
|
|
(void) strcpy(cmpname, dname);
|
|
cmpnamelen = dnamelen;
|
|
} else {
|
|
cmpnamelen = hs_uppercase_copy(dname, cmpname, dnamelen);
|
|
}
|
|
|
|
#ifdef MAB
|
|
/*
|
|
* Quickly screen for a non-matching entry, but not for RRIP.
|
|
*/
|
|
if (!is_rrip && *nm < *cmpname) {
|
|
err = WENT_PAST;
|
|
goto do_ret;
|
|
}
|
|
#endif MAB
|
|
|
|
if (*nm != *cmpname || nmlen != cmpnamelen) {
|
|
/* look at next dir entry */
|
|
RESTORE_NM(rrip_tmp_name, nm);
|
|
*offset += hdlen;
|
|
continue;
|
|
}
|
|
|
|
if ((res = nmcmp(cmpname, nm, nmlen)) == 0) {
|
|
/* name matches */
|
|
parsedir_res =
|
|
hs_parsedir(fsp, dirp, &hd, (char *)NULL,
|
|
(int *) NULL);
|
|
if (!parsedir_res) {
|
|
u_int lbn; /* logical block number */
|
|
u_int secoff;
|
|
|
|
lbn = dhp->hs_dirent.ext_lbn +
|
|
dhp->hs_dirent.xar_len +
|
|
(*offset/
|
|
fsp->hsfs_vol.lbn_size);
|
|
secoff = *offset & MAXHSOFFSET;
|
|
*vpp = hs_makenode(&hd, lbn,
|
|
secoff, dvp->v_vfsp);
|
|
if (*vpp == NULL) {
|
|
*error = ENFILE;
|
|
RESTORE_NM(rrip_tmp_name, nm);
|
|
err = FOUND_ENTRY;
|
|
goto do_ret;
|
|
}
|
|
dhp->hs_offset = *offset;
|
|
RESTORE_NM(rrip_tmp_name, nm);
|
|
err = FOUND_ENTRY;
|
|
goto do_ret;
|
|
} else {
|
|
/* improper dir entry */
|
|
*error = parsedir_res;
|
|
RESTORE_NM(rrip_tmp_name, nm);
|
|
err = FOUND_ENTRY;
|
|
goto do_ret;
|
|
}
|
|
} else if (!is_rrip && res < 0) {
|
|
/* name < dir entry */
|
|
err = WENT_PAST;
|
|
goto do_ret;
|
|
}
|
|
/*
|
|
* name > dir entry,
|
|
* look at next one.
|
|
*/
|
|
*offset += hdlen;
|
|
}
|
|
RESTORE_NM(rrip_tmp_name, nm);
|
|
err = HIT_END;
|
|
|
|
do_ret:
|
|
kmem_free(cmpname, MAXNAMELEN + 1);
|
|
if (is_rrip) {
|
|
kmem_free(rrip_name_str, MAXNAMELEN + 1);
|
|
kmem_free(rrip_tmp_name, MAXNAMELEN + 1);
|
|
}
|
|
return (err);
|
|
}
|
|
|
|
|
|
/*
|
|
* Compare the names, returning < 0 if a < b,
|
|
* 0 if a == b, and > 0 if a > b.
|
|
*/
|
|
static int
|
|
nmcmp(a, b, len)
|
|
register char *a;
|
|
register char *b;
|
|
register int len;
|
|
{
|
|
while (len--) {
|
|
if (*a == *b) {
|
|
b++; a++;
|
|
} else {
|
|
/* if file version, stop */
|
|
if ((*a == ';') && (*b == '\0'))
|
|
return (0);
|
|
return ((u_char)*b - (u_char)*a);
|
|
}
|
|
}
|
|
return (0);
|
|
}
|