/*
* 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
* 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
*/
/*
*/
/*
* 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.
*/
/*
* This variable determines if the HSFS code will use the
* directory name lookup cache. The default is for the cache to be used.
*/
/*
* 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;
/*
* This tunable allows us to ignore inode numbers from rrip-1.12.
* In this case, we fall back to our default inode algorithm.
*/
static void hs_hsnode_cache_reclaim(void *unused);
int *error);
static void hs_log_bogus_joliet_warning(void);
/*
* 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;
}
}
#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.
*/
}
/*
* Destroy the cache of free hsnodes.
*/
void
hs_fini_hsnode_cache(void)
{
}
/*
* System is short on memory, free up as much as possible
*/
/*ARGSUSED*/
static void
{
/*
* 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.
* 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 *
{
if (nodeid == HS_DUMMY_INO) {
/*
* If this is the dummy inode number, look for
* matching dir_lbn and dir_off.
*/
break;
}
return (NULL);
}
/*
* 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 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).
*/
off = 0;
}
/*
* Normalize lbn and off before creating a nodeid
* and before storing them in a hs_node structure
*/
/*
* 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.
*/
} else {
}
/* 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);
hp->hs_prev_offset = 0;
hp->hs_num_contig = 0;
hp->hs_ra_bytes = 0;
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
{
int error;
/* Convert to sector and offset */
if (off > HS_SECTOR_SIZE) {
goto end;
}
if (error != 0) {
goto end;
}
HS_SECTOR_SIZE - off);
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;
int cmpnamelen;
int end;
int bytes_wanted;
int dirsiz;
int is_rrip;
return (ENOTDIR);
return (error);
return (0);
/*
* 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.
*/
return (EINVAL);
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
*/
} else {
}
}
/* 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);
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,
int last_offset) /* last offset in dirp */
{
char *on_disk_name;
int on_disk_namelen;
int on_disk_dirlen;
int namelen;
int error;
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);
/*
* 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.
*/
if (on_disk_dirlen < HDE_ROOT_DIR_REC_SIZE ||
((on_disk_dirlen > last_offset) ||
return (EINVAL);
}
fsp->hsfs_namelen) {
}
fsp->hsfs_flags);
/*
* A negative return value means that the file name
* has been truncated to fsp->hsfs_namemax.
*/
if (namelen < 0) {
}
} else {
/*
* HS_VOL_TYPE_ISO && HS_VOL_TYPE_ISO_V2
*/
}
if (namelen == 0)
return (EINVAL);
} else
return (0);
}
/*
* hs_namecopy
*
* Delete trailing blanks, upper-to-lower case, add NULL terminator.
* Returns the (possibly new) length.
*
* Called from hsfs_readdir() via hs_parsedir()
*/
int
{
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);
}
}
c = from[i];
if (c == ';' && version)
break;
if (c <= ' ' && !trailspace) {
if (lastspace == -1)
lastspace = i;
} else
lastspace = -1;
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()
*
* Add NULL terminator.
* Returns the new length.
*
* Called from hsfs_readdir() via hs_parsedir()
*/
int
{
uint_t i;
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);
}
}
c |= from[i] & 0xFF;
if (c == ';' && version)
break;
if (c < 0x80)
amt = 1;
else if (c < 0x800)
amt = 2;
else
amt = 3;
return (-len);
}
}
if (amt == 0) {
hs_log_bogus_joliet_warning(); /* should never happen */
return (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
{
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);
}
/*
* 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
{
uint_t i;
uint16_t c;
int len = 0;
int amt;
/* special handling for '\0' and '\1' */
if (size == 1) {
return (1);
}
for (i = 0; i < size; i += 2) {
c |= *from++ & 0xFF;
if (amt == 0) {
hs_log_bogus_joliet_warning(); /* should never happen */
return (0);
}
}
return (len);
}
static void
{
static int warned = 0;
if (warned)
return;
warned = 1;
"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
{
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);
}
/*
* hs_iso_copy
*
*
* 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
{
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++;
*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 hdlen;
int res;
int parsedir_res;
int is_rrip;
int rr_namelen = 0;
int did_fbrelse = 0;
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
* 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.
*/
if (hdlen < HDE_ROOT_DIR_REC_SIZE ||
/*
* Advance to the next sector boundary
*/
if (hdlen)
continue;
}
/*
* 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.
*/
/*
* If the directory entry extends beyond the end of the
* block, it must be invalid. Skip it.
*/
goto skip_rec;
}
if (dnamelen > ISO_NAMELEN_V2_MAX)
/*
* 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 = -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 (dname[i] == ';' &&
--i;
break;
}
}
} else {
for (i = dnamelen - 1; i > 0; i--) {
if (dname[i] == ';')
break;
}
}
}
if (i > 0) {
dnamelen = i;
}
dnamelen);
dnamelen)) {
}
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 &&
}
goto skip_rec;
/* name matches */
last_offset - *offset);
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);
}
/*
* 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));
}
static int
{
return (len);
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
*/
{ 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC };
static int32_t
{
/*
* Convert the 16-bit character to a 32-bit character
*/
/*
* 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;
nc = 6;
} else {
nc = 0;
}
switch (nc) {
case 6 :
c_32 >>= 6;
/* FALLTHROUGH */
case 5 :
c_32 >>= 6;
/* FALLTHROUGH */
case 4 :
c_32 >>= 6;
/* FALLTHROUGH */
case 3 :
c_32 >>= 6;
/* FALLTHROUGH */
case 2 :
c_32 >>= 6;
/* FALLTHROUGH */
case 1 :
}
return (nc);
}