418 lines
10 KiB
C
418 lines
10 KiB
C
/* $NetBSD: hfs_subr.c,v 1.19 2015/06/21 13:43:58 maxv 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.19 2015/06/21 13:43:58 maxv 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/device.h>
|
|
#include <sys/conf.h>
|
|
#include <sys/kauth.h>
|
|
#include <sys/buf.h>
|
|
|
|
#include <fs/hfs/hfs.h>
|
|
|
|
#include <miscfs/specfs/specdev.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;
|
|
|
|
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;
|
|
spec_node_init(vp,
|
|
HFS_CONVERT_RDEV(hp->h_rec.file.bsd.special.raw_device));
|
|
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.u.cnid == HFS_CNID_ROOT_FOLDER)
|
|
vp->v_vflag |= VV_ROOT;
|
|
|
|
*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;
|
|
int result, mode;
|
|
uint64_t psize;
|
|
unsigned secsize;
|
|
|
|
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. */
|
|
mode = vol->readonly ? FREAD : FREAD|FWRITE;
|
|
vn_lock(args->devvp, LK_EXCLUSIVE | LK_RETRY);
|
|
result = VOP_OPEN(args->devvp, mode, FSCRED);
|
|
VOP_UNLOCK(args->devvp);
|
|
if (result != 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);
|
|
if (result != 0) {
|
|
VOP_CLOSE(args->devvp, mode, FSCRED);
|
|
goto error;
|
|
}
|
|
|
|
cbdata->devvp = args->devvp;
|
|
|
|
/* Determine the device's block size. Default to DEV_BSIZE if unavailable.*/
|
|
if (getdisksize(args->devvp, &psize, &secsize) != 0)
|
|
cbdata->devblksz = DEV_BSIZE;
|
|
else
|
|
cbdata->devblksz = 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);
|
|
VOP_UNLOCK(cbdata->devvp);
|
|
}
|
|
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);
|
|
VOP_UNLOCK(devvp);
|
|
}
|
|
|
|
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),
|
|
0, &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, 0);
|
|
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;
|
|
|
|
if (inout_ptr == NULL)
|
|
return 0;
|
|
|
|
memcpy(&result, *inout_ptr, sizeof(result));
|
|
*inout_ptr = (char *)*inout_ptr + sizeof(result);
|
|
return be16toh(result);
|
|
}
|
|
|
|
uint32_t be32tohp(void** inout_ptr)
|
|
{
|
|
uint32_t result;
|
|
|
|
if (inout_ptr == NULL)
|
|
return 0;
|
|
|
|
memcpy(&result, *inout_ptr, sizeof(result));
|
|
*inout_ptr = (char *)*inout_ptr + sizeof(result);
|
|
return be32toh(result);
|
|
}
|
|
|
|
uint64_t be64tohp(void** inout_ptr)
|
|
{
|
|
uint64_t result;
|
|
|
|
if (inout_ptr == NULL)
|
|
return 0;
|
|
|
|
memcpy(&result, *inout_ptr, sizeof(result));
|
|
*inout_ptr = (char *)*inout_ptr + sizeof(result);
|
|
return be64toh(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;
|
|
}
|