NetBSD/sys/fs/hfs/hfs_subr.c

450 lines
10 KiB
C

/* $NetBSD: hfs_subr.c,v 1.3 2007/03/22 13:21:28 dillo Exp $ */
/*-
* Copyright (c) 2005, 2007 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Yevgeny Binder and Dieter Baron.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: hfs_subr.c,v 1.3 2007/03/22 13:21:28 dillo Exp $");
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/time.h>
#include <sys/kernel.h>
#include <sys/proc.h>
#include <sys/vnode.h>
#include <sys/malloc.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <sys/filedesc.h>
#include <sys/mount.h>
#include <sys/disklabel.h>
#include <sys/conf.h>
#include <sys/kauth.h>
#include <fs/hfs/hfs.h>
/*
* Initialize the vnode associated with a new hfsnode.
*/
void
hfs_vinit(struct mount *mp, int (**specops)(void *), int (**fifoops)(void *),
struct vnode **vpp)
{
struct hfsnode *hp;
struct vnode *vp;
struct vnode *nvp;
vp = *vpp;
hp = VTOH(vp);
vp->v_type = hfs_catalog_keyed_record_vtype(
(hfs_catalog_keyed_record_t *)&hp->h_rec);
switch(vp->v_type) {
case VCHR:
case VBLK:
vp->v_op = specops;
if ((nvp = checkalias(vp,
HFS_CONVERT_RDEV(hp->h_rec.file.bsd.special.raw_device),
mp)) != NULL) {
/*
* Discard unneeded vnode, but save its inode.
*/
nvp->v_data = vp->v_data;
vp->v_data = NULL;
/* XXX spec_vnodeops has no locking,
do it explicitly */
VOP_UNLOCK(vp, 0);
vp->v_op = specops;
vp->v_flag &= ~VLOCKSWORK;
vrele(vp);
vgone(vp);
lockmgr(&nvp->v_lock, LK_EXCLUSIVE,
&nvp->v_interlock);
/*
* Reinitialize aliased inode.
*/
vp = nvp;
hp->h_vnode = vp;
}
break;
case VFIFO:
vp->v_op = fifoops;
break;
case VNON:
case VBAD:
case VSOCK:
case VDIR:
case VREG:
case VLNK:
break;
}
if (hp->h_rec.cnid == HFS_CNID_ROOT_FOLDER)
vp->v_flag |= VROOT;
*vpp = vp;
}
/*
* Callbacks for libhfs
*/
void
hfs_libcb_error(
const char* format,
const char* file,
int line,
va_list args)
{
#ifdef HFS_DEBUG
if (file != NULL)
printf("%s:%i: ", file, line);
else
printf("hfs: ");
#else
printf("hfs: ");
#endif
/* XXX Should we really display this if debugging is off? */
vprintf(format, args);
printf("\n");
}
/* XXX change malloc/realloc/free to use pools */
void*
hfs_libcb_malloc(size_t size, hfs_callback_args* cbargs)
{
return malloc(size, /*M_HFSMNT*/ M_TEMP, M_WAITOK);
}
void*
hfs_libcb_realloc(void* ptr, size_t size, hfs_callback_args* cbargs)
{
return realloc(ptr, size, /*M_HFSMNT*/ M_TEMP, M_WAITOK);
}
void
hfs_libcb_free(void* ptr, hfs_callback_args* cbargs)
{
free(ptr, /*M_HFSMNT*/ M_TEMP);
}
/*
* hfs_libcb_opendev()
*
* hfslib uses this callback to open a volume's device node by name. However,
* by the time this is called here, the device node has already been opened by
* VFS. So we are passed the vnode to this volume's block device and use that
* instead of the device's name.
*/
int
hfs_libcb_opendev(
hfs_volume* vol,
const char* devname,
hfs_callback_args* cbargs)
{
hfs_libcb_data* cbdata = NULL;
hfs_libcb_argsopen* args;
struct partinfo dpart;
int result;
result = 0;
args = (hfs_libcb_argsopen*)(cbargs->openvol);
if (vol == NULL || devname == NULL) {
result = EINVAL;
goto error;
}
cbdata = malloc(sizeof(hfs_libcb_data), M_HFSMNT, M_WAITOK);
if (cbdata == NULL) {
result = ENOMEM;
goto error;
}
vol->cbdata = cbdata;
cbdata->devvp = NULL;
/* Open the device node. */
if ((result = VOP_OPEN(args->devvp, vol->readonly? FREAD : FREAD|FWRITE,
FSCRED, args->l)) != 0)
goto error;
/* Flush out any old buffers remaining from a previous use. */
vn_lock(args->devvp, LK_EXCLUSIVE | LK_RETRY);
result = vinvalbuf(args->devvp, V_SAVE, args->cred, args->l, 0, 0);
VOP_UNLOCK(args->devvp, 0);
if (result != 0)
goto error;
cbdata->devvp = args->devvp;
/* Determine the device's block size. Default to DEV_BSIZE if unavailable.*/
if (VOP_IOCTL(args->devvp, DIOCGPART, &dpart, FREAD, args->cred, args->l)
!= 0)
cbdata->devblksz = DEV_BSIZE;
else
cbdata->devblksz = dpart.disklab->d_secsize;
return 0;
error:
if (cbdata != NULL) {
if (cbdata->devvp != NULL) {
vn_lock(cbdata->devvp, LK_EXCLUSIVE | LK_RETRY);
(void)VOP_CLOSE(cbdata->devvp, vol->readonly ? FREAD :
FREAD | FWRITE, NOCRED, args->l);
VOP_UNLOCK(cbdata->devvp, 0);
}
free(cbdata, M_HFSMNT);
vol->cbdata = NULL;
}
return result;
}
void
hfs_libcb_closedev(hfs_volume* in_vol, hfs_callback_args* cbargs)
{
struct vnode *devvp;
if (in_vol == NULL)
return;
if (in_vol->cbdata != NULL) {
devvp = ((hfs_libcb_data*)in_vol->cbdata)->devvp;
if (devvp != NULL) {
vn_lock(devvp, LK_EXCLUSIVE | LK_RETRY);
(void)VOP_CLOSE(devvp, in_vol->readonly ? FREAD : FREAD | FWRITE,
NOCRED, ((hfs_libcb_argsclose*)cbargs->closevol)->l);
/* XXX do we need a VOP_UNLOCK() here? */
}
free(in_vol->cbdata, M_HFSMNT);
in_vol->cbdata = NULL;
}
}
int
hfs_libcb_read(
hfs_volume* vol,
void* outbytes,
uint64_t length,
uint64_t offset,
hfs_callback_args* cbargs)
{
hfs_libcb_data *cbdata;
hfs_libcb_argsread* argsread;
kauth_cred_t cred;
uint64_t physoffset; /* physical offset from start of device(?) */
if (vol == NULL || outbytes == NULL)
return -1;
cbdata = (hfs_libcb_data*)vol->cbdata;
if (cbargs != NULL
&& (argsread = (hfs_libcb_argsread*)cbargs->read) != NULL
&& argsread->cred != NULL)
cred = argsread->cred;
else
cred = NOCRED;
/*
* Since bread() only reads data in terms of integral blocks, it may have
* read some data before and/or after our desired offset & length. So when
* copying that data into the outgoing buffer, start at the actual desired
* offset and only copy the desired length.
*/
physoffset = offset + vol->offset;
return hfs_pread(cbdata->devvp, outbytes, cbdata->devblksz, physoffset,
length, cred);
}
/*
* So it turns out that bread() is pretty shoddy. It not only requires the size
* parameter to be an integral multiple of the device's block size, but also
* requires the block number to be on a boundary of that same block size -- and
* yet be given as an integral multiple of DEV_BSIZE! So after much toil and
* bloodshed, hfs_pread() was written as a convenience (and a model of how sane
* people take their bread()). Returns 0 on success.
*/
int
hfs_pread(struct vnode *vp, void *buf, size_t secsz, uint64_t off,
uint64_t len, kauth_cred_t cred)
{
struct buf *bp;
uint64_t curoff; /* relative to 'start' variable */
uint64_t start;
int error;
if (vp == NULL || buf == NULL)
return EINVAL;
if (len == 0)
return 0;
curoff = 0;
error = 0;
/* align offset to highest preceding sector boundary */
#define ABSZ(x, bsz) (((x)/(bsz))*(bsz))
/* round size up to integral # of block sizes */
#define RBSZ(x, bsz) (((x) + (bsz) - 1) & ~((bsz) - 1))
start = ABSZ(off, secsz);
while (start + curoff < off + len)
{
bp = NULL;
/* XXX Does the algorithm always do what's intended here when
* XXX start != off? Need to test this. */
error = bread(vp, (start + curoff) / DEV_BSIZE,/* no rounding involved*/
RBSZ(min(len - curoff + (off - start), MAXBSIZE), secsz), cred, &bp);
if (error == 0)
memcpy((uint8_t*)buf + curoff, (uint8_t*)bp->b_data +
(off - start), min(len - curoff, MAXBSIZE - (off - start)));
if (bp != NULL)
brelse(bp);
if (error != 0)
return error;
curoff += MAXBSIZE;
}
#undef ABSZ
#undef RBSZ
return 0;
}
/* XXX Provide a routine to take a catalog record and return its proper BSD file
* XXX or directory mode value */
/* Convert from HFS+ time representation to UNIX time since epoch. */
void
hfs_time_to_timespec(uint32_t hfstime, struct timespec *unixtime)
{
/*
* HFS+ time is calculated in seconds since midnight, Jan 1st, 1904.
* struct timespec counts from midnight, Jan 1st, 1970. Thus, there is
* precisely a 66 year difference between them, which is equal to
* 2,082,844,800 seconds. No, I didn't count them by hand.
*/
if (hfstime < 2082844800)
unixtime->tv_sec = 0; /* dates before 1970 are bs anyway, so use epoch*/
else
unixtime->tv_sec = hfstime - 2082844800;
unixtime->tv_nsec = 0; /* we don't have nanosecond resolution */
}
/*
* Endian conversion with automatic pointer incrementation.
*/
uint16_t be16tohp(void** inout_ptr)
{
uint16_t result;
uint16_t *ptr;
if(inout_ptr==NULL)
return 0;
ptr = *inout_ptr;
result = be16toh(*ptr);
ptr++;
*inout_ptr = ptr;
return result;
}
uint32_t be32tohp(void** inout_ptr)
{
uint32_t result;
uint32_t *ptr;
if(inout_ptr==NULL)
return 0;
ptr = *inout_ptr;
result = be32toh(*ptr);
ptr++;
*inout_ptr = ptr;
return result;
}
uint64_t be64tohp(void** inout_ptr)
{
uint64_t result;
uint64_t *ptr;
if(inout_ptr==NULL)
return 0;
ptr = *inout_ptr;
result = be64toh(*ptr);
ptr++;
*inout_ptr = ptr;
return result;
}
enum vtype
hfs_catalog_keyed_record_vtype(const hfs_catalog_keyed_record_t *rec)
{
if (rec->type == HFS_REC_FILE) {
uint32_t mode;
mode = ((const hfs_file_record_t *)rec)->bsd.file_mode;
if (mode != 0)
return IFTOVT(mode);
else
return VREG;
}
else
return VDIR;
}