747 lines
13 KiB
C
747 lines
13 KiB
C
/*
|
|
* libhfs - library for reading and writing Macintosh HFS volumes
|
|
* Copyright (C) 1996-1998 Robert Leslie
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston,
|
|
* MA 02110-1301, USA.
|
|
*
|
|
* $Id: hfs.c,v 1.15 1998/11/02 22:09:00 rob Exp $
|
|
*/
|
|
|
|
#include "config.h"
|
|
#include "libhfs.h"
|
|
#include "data.h"
|
|
#include "block.h"
|
|
#include "medium.h"
|
|
#include "file.h"
|
|
#include "btree.h"
|
|
#include "node.h"
|
|
#include "record.h"
|
|
#include "volume.h"
|
|
|
|
const char *hfs_error = "no error"; /* static error string */
|
|
|
|
hfsvol *hfs_mounts; /* linked list of mounted volumes */
|
|
|
|
static
|
|
hfsvol *curvol; /* current volume */
|
|
|
|
|
|
/*
|
|
* NAME: getvol()
|
|
* DESCRIPTION: validate a volume reference
|
|
*/
|
|
static
|
|
int getvol(hfsvol **vol)
|
|
{
|
|
if (*vol == NULL)
|
|
{
|
|
if (curvol == NULL)
|
|
ERROR(EINVAL, "no volume is current");
|
|
|
|
*vol = curvol;
|
|
}
|
|
|
|
return 0;
|
|
|
|
fail:
|
|
return -1;
|
|
}
|
|
|
|
/* High-Level Volume Routines ============================================== */
|
|
|
|
/*
|
|
* NAME: hfs->mount()
|
|
* DESCRIPTION: open an HFS volume; return volume descriptor or 0 (error)
|
|
*/
|
|
hfsvol *hfs_mount( int os_fd, int pnum)
|
|
{
|
|
hfsvol *vol, *check;
|
|
int mode = HFS_MODE_RDONLY;
|
|
|
|
/* see if the volume is already mounted */
|
|
for (check = hfs_mounts; check; check = check->next)
|
|
{
|
|
if (check->pnum == pnum && v_same(check, os_fd) == 1)
|
|
{
|
|
vol = check;
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
vol = ALLOC(hfsvol, 1);
|
|
if (vol == NULL)
|
|
ERROR(ENOMEM, NULL);
|
|
|
|
v_init(vol, mode);
|
|
|
|
vol->flags |= HFS_VOL_READONLY;
|
|
if( v_open(vol, os_fd) == -1 )
|
|
goto fail;
|
|
|
|
/* mount the volume */
|
|
|
|
if (v_geometry(vol, pnum) == -1 ||
|
|
v_mount(vol) == -1)
|
|
goto fail;
|
|
|
|
/* add to linked list of volumes */
|
|
|
|
vol->prev = NULL;
|
|
vol->next = hfs_mounts;
|
|
|
|
if (hfs_mounts)
|
|
hfs_mounts->prev = vol;
|
|
|
|
hfs_mounts = vol;
|
|
|
|
done:
|
|
++vol->refs;
|
|
curvol = vol;
|
|
|
|
return vol;
|
|
|
|
fail:
|
|
if (vol)
|
|
{
|
|
v_close(vol);
|
|
FREE(vol);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/*
|
|
* NAME: hfs->umount()
|
|
* DESCRIPTION: close an HFS volume
|
|
*/
|
|
int hfs_umount(hfsvol *vol)
|
|
{
|
|
int result = 0;
|
|
|
|
if (getvol(&vol) == -1)
|
|
goto fail;
|
|
|
|
if (--vol->refs)
|
|
{
|
|
goto done;
|
|
}
|
|
|
|
/* close all open files and directories */
|
|
|
|
while (vol->files)
|
|
{
|
|
if (hfs_close(vol->files) == -1)
|
|
result = -1;
|
|
}
|
|
|
|
while (vol->dirs)
|
|
{
|
|
if (hfs_closedir(vol->dirs) == -1)
|
|
result = -1;
|
|
}
|
|
|
|
/* close medium */
|
|
|
|
if (v_close(vol) == -1)
|
|
result = -1;
|
|
|
|
/* remove from linked list of volumes */
|
|
|
|
if (vol->prev)
|
|
vol->prev->next = vol->next;
|
|
if (vol->next)
|
|
vol->next->prev = vol->prev;
|
|
|
|
if (vol == hfs_mounts)
|
|
hfs_mounts = vol->next;
|
|
if (vol == curvol)
|
|
curvol = NULL;
|
|
|
|
FREE(vol);
|
|
|
|
done:
|
|
return result;
|
|
|
|
fail:
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* NAME: hfs->umountall()
|
|
* DESCRIPTION: unmount all mounted volumes
|
|
*/
|
|
void hfs_umountall(void)
|
|
{
|
|
while (hfs_mounts)
|
|
hfs_umount(hfs_mounts);
|
|
}
|
|
|
|
/*
|
|
* NAME: hfs->getvol()
|
|
* DESCRIPTION: return a pointer to a mounted volume
|
|
*/
|
|
hfsvol *hfs_getvol(const char *name)
|
|
{
|
|
hfsvol *vol;
|
|
|
|
if (name == NULL)
|
|
return curvol;
|
|
|
|
for (vol = hfs_mounts; vol; vol = vol->next)
|
|
{
|
|
if (d_relstring(name, vol->mdb.drVN) == 0)
|
|
return vol;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* NAME: hfs->setvol()
|
|
* DESCRIPTION: change the current volume
|
|
*/
|
|
void hfs_setvol(hfsvol *vol)
|
|
{
|
|
curvol = vol;
|
|
}
|
|
|
|
/*
|
|
* NAME: hfs->vstat()
|
|
* DESCRIPTION: return volume statistics
|
|
*/
|
|
int hfs_vstat(hfsvol *vol, hfsvolent *ent)
|
|
{
|
|
if (getvol(&vol) == -1)
|
|
goto fail;
|
|
|
|
strcpy(ent->name, vol->mdb.drVN);
|
|
|
|
ent->flags = (vol->flags & HFS_VOL_READONLY) ? HFS_ISLOCKED : 0;
|
|
|
|
ent->totbytes = vol->mdb.drNmAlBlks * vol->mdb.drAlBlkSiz;
|
|
ent->freebytes = vol->mdb.drFreeBks * vol->mdb.drAlBlkSiz;
|
|
|
|
ent->alblocksz = vol->mdb.drAlBlkSiz;
|
|
ent->clumpsz = vol->mdb.drClpSiz;
|
|
|
|
ent->numfiles = vol->mdb.drFilCnt;
|
|
ent->numdirs = vol->mdb.drDirCnt;
|
|
|
|
ent->crdate = d_ltime(vol->mdb.drCrDate);
|
|
ent->mddate = d_ltime(vol->mdb.drLsMod);
|
|
ent->bkdate = d_ltime(vol->mdb.drVolBkUp);
|
|
|
|
ent->blessed = vol->mdb.drFndrInfo[0];
|
|
|
|
return 0;
|
|
|
|
fail:
|
|
return -1;
|
|
}
|
|
|
|
|
|
/* High-Level Directory Routines =========================================== */
|
|
|
|
/*
|
|
* NAME: hfs->chdir()
|
|
* DESCRIPTION: change current HFS directory
|
|
*/
|
|
int hfs_chdir(hfsvol *vol, const char *path)
|
|
{
|
|
CatDataRec data;
|
|
|
|
if (getvol(&vol) == -1 ||
|
|
v_resolve(&vol, path, &data, NULL, NULL, NULL) <= 0)
|
|
goto fail;
|
|
|
|
if (data.cdrType != cdrDirRec)
|
|
ERROR(ENOTDIR, NULL);
|
|
|
|
vol->cwd = data.u.dir.dirDirID;
|
|
|
|
return 0;
|
|
|
|
fail:
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* NAME: hfs->getcwd()
|
|
* DESCRIPTION: return the current working directory ID
|
|
*/
|
|
unsigned long hfs_getcwd(hfsvol *vol)
|
|
{
|
|
if (getvol(&vol) == -1)
|
|
return 0;
|
|
|
|
return vol->cwd;
|
|
}
|
|
|
|
/*
|
|
* NAME: hfs->setcwd()
|
|
* DESCRIPTION: set the current working directory ID
|
|
*/
|
|
int hfs_setcwd(hfsvol *vol, unsigned long id)
|
|
{
|
|
if (getvol(&vol) == -1)
|
|
goto fail;
|
|
|
|
if (id == vol->cwd)
|
|
goto done;
|
|
|
|
/* make sure the directory exists */
|
|
|
|
if (v_getdthread(vol, id, NULL, NULL) <= 0)
|
|
goto fail;
|
|
|
|
vol->cwd = id;
|
|
|
|
done:
|
|
return 0;
|
|
|
|
fail:
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* NAME: hfs->dirinfo()
|
|
* DESCRIPTION: given a directory ID, return its (name and) parent ID
|
|
*/
|
|
int hfs_dirinfo(hfsvol *vol, unsigned long *id, char *name)
|
|
{
|
|
CatDataRec thread;
|
|
|
|
if (getvol(&vol) == -1 ||
|
|
v_getdthread(vol, *id, &thread, NULL) <= 0)
|
|
goto fail;
|
|
|
|
*id = thread.u.dthd.thdParID;
|
|
|
|
if (name)
|
|
strcpy(name, thread.u.dthd.thdCName);
|
|
|
|
return 0;
|
|
|
|
fail:
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* NAME: hfs->opendir()
|
|
* DESCRIPTION: prepare to read the contents of a directory
|
|
*/
|
|
hfsdir *hfs_opendir(hfsvol *vol, const char *path)
|
|
{
|
|
hfsdir *dir = NULL;
|
|
CatKeyRec key;
|
|
CatDataRec data;
|
|
byte pkey[HFS_CATKEYLEN];
|
|
|
|
if (getvol(&vol) == -1)
|
|
goto fail;
|
|
|
|
dir = ALLOC(hfsdir, 1);
|
|
if (dir == NULL)
|
|
ERROR(ENOMEM, NULL);
|
|
|
|
dir->vol = vol;
|
|
|
|
if (*path == 0)
|
|
{
|
|
/* meta-directory containing root dirs from all mounted volumes */
|
|
|
|
dir->dirid = 0;
|
|
dir->vptr = hfs_mounts;
|
|
}
|
|
else
|
|
{
|
|
if (v_resolve(&vol, path, &data, NULL, NULL, NULL) <= 0)
|
|
goto fail;
|
|
|
|
if (data.cdrType != cdrDirRec)
|
|
ERROR(ENOTDIR, NULL);
|
|
|
|
dir->dirid = data.u.dir.dirDirID;
|
|
dir->vptr = NULL;
|
|
|
|
r_makecatkey(&key, dir->dirid, "");
|
|
r_packcatkey(&key, pkey, NULL);
|
|
|
|
if (bt_search(&vol->cat, pkey, &dir->n) <= 0)
|
|
goto fail;
|
|
}
|
|
|
|
dir->prev = NULL;
|
|
dir->next = vol->dirs;
|
|
|
|
if (vol->dirs)
|
|
vol->dirs->prev = dir;
|
|
|
|
vol->dirs = dir;
|
|
|
|
return dir;
|
|
|
|
fail:
|
|
FREE(dir);
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* NAME: hfs->readdir()
|
|
* DESCRIPTION: return the next entry in the directory
|
|
*/
|
|
int hfs_readdir(hfsdir *dir, hfsdirent *ent)
|
|
{
|
|
CatKeyRec key;
|
|
CatDataRec data;
|
|
const byte *ptr;
|
|
|
|
if (dir->dirid == 0)
|
|
{
|
|
hfsvol *vol;
|
|
char cname[HFS_MAX_FLEN + 1];
|
|
|
|
for (vol = hfs_mounts; vol; vol = vol->next)
|
|
{
|
|
if (vol == dir->vptr)
|
|
break;
|
|
}
|
|
|
|
if (vol == NULL)
|
|
ERROR(ENOENT, "no more entries");
|
|
|
|
if (v_getdthread(vol, HFS_CNID_ROOTDIR, &data, NULL) <= 0 ||
|
|
v_catsearch(vol, HFS_CNID_ROOTPAR, data.u.dthd.thdCName,
|
|
&data, cname, NULL) <= 0)
|
|
goto fail;
|
|
|
|
r_unpackdirent(HFS_CNID_ROOTPAR, cname, &data, ent);
|
|
|
|
dir->vptr = vol->next;
|
|
|
|
goto done;
|
|
}
|
|
|
|
if (dir->n.rnum == -1)
|
|
ERROR(ENOENT, "no more entries");
|
|
|
|
while (1)
|
|
{
|
|
++dir->n.rnum;
|
|
|
|
while (dir->n.rnum >= dir->n.nd.ndNRecs)
|
|
{
|
|
if (dir->n.nd.ndFLink == 0)
|
|
{
|
|
dir->n.rnum = -1;
|
|
ERROR(ENOENT, "no more entries");
|
|
}
|
|
|
|
if (bt_getnode(&dir->n, dir->n.bt, dir->n.nd.ndFLink) == -1)
|
|
{
|
|
dir->n.rnum = -1;
|
|
goto fail;
|
|
}
|
|
|
|
dir->n.rnum = 0;
|
|
}
|
|
|
|
ptr = HFS_NODEREC(dir->n, dir->n.rnum);
|
|
|
|
r_unpackcatkey(ptr, &key);
|
|
|
|
if (key.ckrParID != dir->dirid)
|
|
{
|
|
dir->n.rnum = -1;
|
|
ERROR(ENOENT, "no more entries");
|
|
}
|
|
|
|
r_unpackcatdata(HFS_RECDATA(ptr), &data);
|
|
|
|
switch (data.cdrType)
|
|
{
|
|
case cdrDirRec:
|
|
case cdrFilRec:
|
|
r_unpackdirent(key.ckrParID, key.ckrCName, &data, ent);
|
|
goto done;
|
|
|
|
case cdrThdRec:
|
|
case cdrFThdRec:
|
|
break;
|
|
|
|
default:
|
|
dir->n.rnum = -1;
|
|
ERROR(EIO, "unexpected directory entry found");
|
|
}
|
|
}
|
|
|
|
done:
|
|
return 0;
|
|
|
|
fail:
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* NAME: hfs->closedir()
|
|
* DESCRIPTION: stop reading a directory
|
|
*/
|
|
int hfs_closedir(hfsdir *dir)
|
|
{
|
|
hfsvol *vol = dir->vol;
|
|
|
|
if (dir->prev)
|
|
dir->prev->next = dir->next;
|
|
if (dir->next)
|
|
dir->next->prev = dir->prev;
|
|
if (dir == vol->dirs)
|
|
vol->dirs = dir->next;
|
|
|
|
FREE(dir);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* High-Level File Routines ================================================ */
|
|
|
|
/*
|
|
* NAME: hfs->open()
|
|
* DESCRIPTION: prepare a file for I/O
|
|
*/
|
|
hfsfile *hfs_open(hfsvol *vol, const char *path)
|
|
{
|
|
hfsfile *file = NULL;
|
|
|
|
if (getvol(&vol) == -1)
|
|
goto fail;
|
|
|
|
file = ALLOC(hfsfile, 1);
|
|
if (file == NULL)
|
|
ERROR(ENOMEM, NULL);
|
|
|
|
if (v_resolve(&vol, path, &file->cat, &file->parid, file->name, NULL) <= 0)
|
|
goto fail;
|
|
|
|
if (file->cat.cdrType != cdrFilRec)
|
|
ERROR(EISDIR, NULL);
|
|
|
|
/* package file handle for user */
|
|
|
|
file->vol = vol;
|
|
file->flags = 0;
|
|
|
|
f_selectfork(file, fkData);
|
|
|
|
file->prev = NULL;
|
|
file->next = vol->files;
|
|
|
|
if (vol->files)
|
|
vol->files->prev = file;
|
|
|
|
vol->files = file;
|
|
|
|
return file;
|
|
|
|
fail:
|
|
FREE(file);
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* NAME: hfs->setfork()
|
|
* DESCRIPTION: select file fork for I/O operations
|
|
*/
|
|
int hfs_setfork(hfsfile *file, int fork)
|
|
{
|
|
int result = 0;
|
|
|
|
f_selectfork(file, fork ? fkRsrc : fkData);
|
|
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* NAME: hfs->getfork()
|
|
* DESCRIPTION: return the current fork for I/O operations
|
|
*/
|
|
int hfs_getfork(hfsfile *file)
|
|
{
|
|
return file->fork != fkData;
|
|
}
|
|
|
|
/*
|
|
* NAME: hfs->read()
|
|
* DESCRIPTION: read from an open file
|
|
*/
|
|
unsigned long hfs_read(hfsfile *file, void *buf, unsigned long len)
|
|
{
|
|
unsigned long *lglen, count;
|
|
byte *ptr = buf;
|
|
|
|
f_getptrs(file, NULL, &lglen, NULL);
|
|
|
|
if (file->pos + len > *lglen)
|
|
len = *lglen - file->pos;
|
|
|
|
count = len;
|
|
while (count)
|
|
{
|
|
unsigned long bnum, offs, chunk;
|
|
|
|
bnum = file->pos >> HFS_BLOCKSZ_BITS;
|
|
offs = file->pos & (HFS_BLOCKSZ - 1);
|
|
|
|
chunk = HFS_BLOCKSZ - offs;
|
|
if (chunk > count)
|
|
chunk = count;
|
|
|
|
if (offs == 0 && chunk == HFS_BLOCKSZ)
|
|
{
|
|
if (f_getblock(file, bnum, (block *) ptr) == -1)
|
|
goto fail;
|
|
}
|
|
else
|
|
{
|
|
block b;
|
|
|
|
if (f_getblock(file, bnum, &b) == -1)
|
|
goto fail;
|
|
|
|
memcpy(ptr, b + offs, chunk);
|
|
}
|
|
|
|
ptr += chunk;
|
|
|
|
file->pos += chunk;
|
|
count -= chunk;
|
|
}
|
|
|
|
return len;
|
|
|
|
fail:
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* NAME: hfs->seek()
|
|
* DESCRIPTION: change file seek pointer
|
|
*/
|
|
unsigned long hfs_seek(hfsfile *file, long offset, int from)
|
|
{
|
|
unsigned long *lglen, newpos;
|
|
|
|
f_getptrs(file, NULL, &lglen, NULL);
|
|
|
|
switch (from)
|
|
{
|
|
case HFS_SEEK_SET:
|
|
newpos = (offset < 0) ? 0 : offset;
|
|
break;
|
|
|
|
case HFS_SEEK_CUR:
|
|
if (offset < 0 && (unsigned long) -offset > file->pos)
|
|
newpos = 0;
|
|
else
|
|
newpos = file->pos + offset;
|
|
break;
|
|
|
|
case HFS_SEEK_END:
|
|
if (offset < 0 && (unsigned long) -offset > *lglen)
|
|
newpos = 0;
|
|
else
|
|
newpos = *lglen + offset;
|
|
break;
|
|
|
|
default:
|
|
ERROR(EINVAL, NULL);
|
|
}
|
|
|
|
if (newpos > *lglen)
|
|
newpos = *lglen;
|
|
|
|
file->pos = newpos;
|
|
|
|
return newpos;
|
|
|
|
fail:
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* NAME: hfs->close()
|
|
* DESCRIPTION: close a file
|
|
*/
|
|
int hfs_close(hfsfile *file)
|
|
{
|
|
hfsvol *vol = file->vol;
|
|
int result = 0;
|
|
|
|
if (file->prev)
|
|
file->prev->next = file->next;
|
|
if (file->next)
|
|
file->next->prev = file->prev;
|
|
if (file == vol->files)
|
|
vol->files = file->next;
|
|
|
|
FREE(file);
|
|
|
|
return result;
|
|
}
|
|
|
|
/* High-Level Catalog Routines ============================================= */
|
|
|
|
/*
|
|
* NAME: hfs->stat()
|
|
* DESCRIPTION: return catalog information for an arbitrary path
|
|
*/
|
|
int hfs_stat(hfsvol *vol, const char *path, hfsdirent *ent)
|
|
{
|
|
CatDataRec data;
|
|
unsigned long parid;
|
|
char name[HFS_MAX_FLEN + 1];
|
|
|
|
if (getvol(&vol) == -1 ||
|
|
v_resolve(&vol, path, &data, &parid, name, NULL) <= 0)
|
|
goto fail;
|
|
|
|
r_unpackdirent(parid, name, &data, ent);
|
|
|
|
return 0;
|
|
|
|
fail:
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* NAME: hfs->fstat()
|
|
* DESCRIPTION: return catalog information for an open file
|
|
*/
|
|
int hfs_fstat(hfsfile *file, hfsdirent *ent)
|
|
{
|
|
r_unpackdirent(file->parid, file->name, &file->cat, ent);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* NAME: hfs->probe()
|
|
* DESCRIPTION: return whether a HFS filesystem is present at the given offset
|
|
*/
|
|
int hfs_probe(int fd, long long offset)
|
|
{
|
|
return v_probe(fd, offset);
|
|
}
|