hsfs_node.c revision 7c478bd95313f5f23a4c958a745db2134aa03244
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (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
* 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 2004 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* Directory operations for High Sierra filesystem
*/
#include <vm/seg_kmem.h>
#include <sys/sysmacros.h>
/*
* This macro expects a name that ends in '.' and returns TRUE if the
* name is not "." or ".."
*/
/*
* These values determine whether we will try to read a file or dir;
* 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.
*
*/
static int strict_iso9660_ordering = 0;
static void hs_hsnode_cache_reclaim(void *unused);
/*
* hs_access
* Return 0 if the desired access may be granted.
* Otherwise return error code.
*/
int
{
int shift = 0;
/*
* Write access cannot be granted for a read-only medium
*/
return (EROFS);
/*
* XXX - For now, use volume protections.
* Also, always grant EXEC access for directories
* if READ access is granted.
*/
m &= ~VEXEC;
m |= VREAD;
}
shift += 3;
shift += 3;
}
if (m != 0)
return (0);
}
#else
#endif
/*
* 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.
*/
/*
* 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.
*/
}
/*
* System is short on memory, free up as much as possible
*/
/*ARGSUSED*/
static void
hs_hsnode_cache_reclaim(void *unused)
{
/*
* For each vfs in the hs_mounttab list
*/
/*
* Purge the dnlc of all hsfs entries
*/
/*
* For each entry in the free chain
*/
/*
* Remove from chain
*/
} else {
}
/*
* Free the node. Force it to be fully freed
* by setting the 3rd arg (nopage) to 1.
*/
}
}
}
/*
* Add an hsnode to the end of the free list.
*/
static void
{
else
}
/*
* Get an hsnode from the front of the free list.
* Must be called with write hsfs_hash_lock held.
*/
static struct hsnode *
{
/*
* 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.
*/
fsp->hsfs_nohsnode++;
return (hp);
}
/* hp cannot be NULL, since we already checked this above */
else
/*
* file is no longer referenced, destroy all old pages
*/
if (vn_has_cached_data(vp))
/*
* pvn_vplist_dirty will abort all old pages
*/
break;
}
}
}
{
}
return (hp);
}
/*
* Remove an hsnode from the free list.
*/
static void
{
else
else
}
/*
* Look for hsnode in hash list.
* Check equality of fsid and nodeid.
* If found, reactivate it if inactive.
* Must be entered with hsfs_hash_lock held.
*/
struct vnode *
{
/*
* reactivating a free hsnode:
* remove from free list
*/
}
return (vp);
}
}
return (NULL);
}
static void
{
}
/*
* Destroy all old pages and free the hsnodes
* Return 1 if busy (a hsnode is still referenced).
*/
int
{
int i;
int busy = 0;
/* make sure no one can come in */
for (i = 0; i < HS_HASHSIZE; i++) {
busy = 1;
continue;
}
if (vn_has_cached_data(vp))
}
}
if (busy) {
return (1);
}
/* now free the hsnodes */
for (i = 0; i < HS_HASHSIZE; i++) {
/*
* 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.
*/
}
}
}
/* release the root hsnode, this should free the final hsnode */
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 *
struct hs_direntry *dp,
{
/*
* 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.
*/
off = 0;
}
/*
* Normalize lbn and off before creating a nodeid
* and before storing them in a hs_node structure
*/
/* look for hsnode in cache first */
/*
* 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.
*/
/*
* 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.
*/
sizeof (*dp));
/*
* We've just copied this pointer into hs_dirent,
* and don't want 2 references to same symlink.
*/
/*
* No need to hold any lock because hsnode is not
* yet in the hash chain.
*/
NULL);
if (off > HS_SECTOR_SIZE)
/*
* if it's a device, call specvp
*/
CRED());
"hs_makenode: specvp failed");
return (newvp);
}
}
}
}
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
{
/* remove this node from the hash list, if it's there */
break;
}
}
}
if (vn_has_cached_data(vp)) {
/* clean all old pages */
/* XXX - can we remove pages by fiat like this??? */
}
vn_invalid(vp);
fsp->hsfs_nohsnode--;
return;
}
}
/*
* 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
{
struct hs_direntry hd;
int error;
/* Convert to sector and offset */
if (off > HS_SECTOR_SIZE) {
goto end;
}
if (error != 0) {
goto end;
}
if (!error) {
}
end:
return (error);
}
/*
* hs_dirlook
*
* Look for a given name in a given directory.
* If found, construct an hsnode for it.
*/
int
char *name,
int namlen, /* length of 'name' */
{
int error = 0;
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;
int bytes_wanted;
int dirsiz;
int is_rrip;
return (ENOTDIR);
return (error);
return (0);
if (namlen >= cmpname_size)
/*
* For the purposes of comparing the name against dir entries,
* fold it to upper case.
*/
if (is_rrip) {
cmpnamelen = namlen;
} else {
/*
* If we don't consider a trailing dot as part of the filename,
* remove it from the specified name
*/
}
/* make sure dirent is filled up with all info */
/*
* No lock is needed - hs_offset is used as starting
* point for searching the directory.
*/
adhoc_search = (offset != 0);
else
if (error)
goto done;
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;
adhoc_search = 0;
goto tryagain;
}
goto done;
case HIT_END:
goto tryagain;
}
}
/*
* End of all dir blocks, didn't find entry.
*/
if (adhoc_search) {
offset = 0;
adhoc_search = 0;
goto tryagain;
}
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)
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
struct hs_direntry *hdp,
char *dnp,
int *dnlen)
{
char *on_disk_name;
int on_disk_namelen;
int namelen;
int error;
int name_change_flag = 0; /* set if name was gotten in SUA */
if ((flags & hde_prohibited) == 0) {
/*
* Skip files with the associated bit set.
*/
if (flags & HDE_ASSOCIATED)
return (EAGAIN);
} else {
return (EINVAL);
}
if ((flags & ide_prohibited) == 0) {
/*
* Skip files with the associated bit set.
*/
if (flags & IDE_ASSOCIATED)
return (EAGAIN);
} else {
return (EINVAL);
}
/*
* Having this all filled in, let's see if we have any
* SUA susp to look at.
*/
if (IS_SUSP_IMPLEMENTED(fsp)) {
if (error) {
}
return (error);
}
}
}
#if dontskip
return (EINVAL);
}
#endif
/* check interleaf size and skip factor */
/* must both be zero or non-zero */
"hsfs: interleaf size or skip factor error");
return (EINVAL);
}
"hsfs: interleaving specified on zero length file");
return (EINVAL);
}
}
return (EINVAL);
}
}
/*
* If the name changed, then the NM field for RRIP was hit and
* we should not copy the name again, just return.
*/
return (0);
/* return the pointer to the directory name and its length */
if (((int)(fsp->hsfs_namemax) > 0) &&
}
fsp->hsfs_flags);
} else
return (0);
}
/*
* hs_namecopy
*
* Delete trailing blanks, upper-to-lower case, add NULL terminator.
* Returns the (possibly new) length.
*/
int
{
uint_t i;
uchar_t c;
int lastspace;
int maplc;
/* 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);
}
}
c = from[i];
if (c == ';')
break;
if (c <= ' ') {
if (lastspace == -1)
lastspace = i;
} else
lastspace = -1;
c += 'a' - 'A';
to[i] = c;
}
if (lastspace != -1)
i = lastspace;
to[i] = '\0';
return (i);
}
/*
* map a filename to upper case;
* return 1 if found lowercase character
*/
static int
{
uint_t i;
uchar_t c;
for (i = 0; i < size; i++) {
c = *from++;
if ((c >= 'a') && (c <= 'z')) {
c -= ('a' - 'A');
had_lc = 1;
}
*to++ = c;
}
return (had_lc);
}
/*
* hs_uppercase_copy
*
* Convert a UNIX-style name into its HSFS equivalent.
* Map to upper case.
* Returns the (possibly new) length.
*/
int
{
uint_t i;
uchar_t c;
/* special handling for '.' and '..' */
*to = '\0';
return (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
{
int error;
(void *)vp);
return;
}
if (error != 0) {
goto end;
}
/* quick check */
/* keep on going */
}
end:
}
/*
* Look through a directory block for a matching entry.
* Note: this routine does an fbrelse() on the buffer passed in.
*/
static enum dirblock_result
char *nm, /* upcase nm to compare against */
int nmlen, /* length of name */
int *error, /* return value: errno */
int is_rrip) /* 1 if rock ridge is implemented */
{
char *dname; /* name in directory entry */
int dnamelen; /* length of name */
struct hs_direntry hd;
int hdlen;
int res;
int parsedir_res;
int rr_namelen;
char *rrip_name_str = NULL;
char *rrip_tmp_name = NULL;
enum dirblock_result err = 0;
int did_fbrelse = 0;
char uppercase_name[ISO_FILE_NAMELEN];
/* return after performing cleanup-on-exit */
if (is_rrip) {
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.
* Therefore, detect this condition when the size
* field of the directory entry is zero.
*/
if (hdlen == 0) {
/* advance to next sector boundary */
if (*offset > last_offset) {
/* end of block */
} else
continue;
}
/*
* Zero out the hd to read new directory
*/
/*
* Just ignore invalid directory entries.
* XXX - maybe hs_parsedir() will detect EXISTENCE bit
*/
}
/*
* 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';
}
if (rr_namelen != -1) {
dname = (char *)&rrip_name_str[0];
}
}
/* use iso name instead */
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 - 1;
(dname[i] != ';') && (i > 0);
i--)
continue;
if (dname[i] == ';')
dnamelen = i;
else
if (! is_rrip &&
}
/*
* 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 &&
}
/* look at next dir entry */
continue;
}
/* name matches */
(int *)NULL);
if (!parsedir_res) {
/*
* 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.
*/
did_fbrelse = 1;
}
} else if (parsedir_res != EAGAIN) {
/* improper dir entry */
*error = parsedir_res;
}
} else if (strict_iso9660_ordering && !is_rrip &&
/* name < dir entry */
}
/*
* name > dir entry,
* look at next one.
*/
}
if (rrip_name_str)
if (rrip_tmp_name)
if (! did_fbrelse)
return (err);
}
/*
* Compare the names, returning < 0 if a < b,
* 0 if a == b, and > 0 if a > b.
*/
static int
{
while (len--) {
if (*a == *b) {
b++; a++;
} else {
/* if file version, stop */
return (0);
}
}
return (0);
}
/*
* Strip trailing nulls or spaces from the name;
* return adjusted length. If we find such junk,
* log a non-conformant disk message.
*/
static int
{
char *c;
int trailing_junk = 0;
if (*c == ' ' || *c == '\0')
trailing_junk = 1;
else
break;
}
if (trailing_junk)
return ((int)(c - nm + 1));
}