libhfs/hfs.c

1845 lines
34 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* $Id: hfs.c,v 1.15 1998/11/02 22:09:00 rob Exp $
*/
# include <stdlib.h>
# include <string.h>
# include <time.h>
# include <errno.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: validvname()
* DESCRIPTION: return true if parameter is a valid volume name
*/
static
int validvname(const char *name)
{
int len;
len = strlen(name);
if (len < 1)
ERROR(EINVAL, "volume name cannot be empty");
else if (len > HFS_MAX_VLEN)
ERROR(ENAMETOOLONG,
"volume name can be at most " STR(HFS_MAX_VLEN) " chars");
if (strchr(name, ':'))
ERROR(EINVAL, "volume name may not contain colons");
return 1;
fail:
return 0;
}
/*
* NAME: getvol()
* DESCRIPTION: validate a volume reference
*/
static
int getvol(hfsvol **vol)
{
if (*vol == 0)
{
if (curvol == 0)
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(const char *path, int pnum, int mode)
{
hfsvol *vol, *check;
/* see if the volume is already mounted */
for (check = hfs_mounts; check; check = check->next)
{
if (check->pnum == pnum && v_same(check, path) == 1)
{
/* verify compatible read/write mode */
if (((check->flags & HFS_VOL_READONLY) &&
! (mode & HFS_MODE_RDWR)) ||
(! (check->flags & HFS_VOL_READONLY) &&
(mode & (HFS_MODE_RDWR | HFS_MODE_ANY))))
{
vol = check;
goto done;
}
}
}
vol = ALLOC(hfsvol, 1);
if (vol == 0)
ERROR(ENOMEM, 0);
v_init(vol, mode);
/* open the medium */
switch (mode & HFS_MODE_MASK)
{
case HFS_MODE_RDWR:
case HFS_MODE_ANY:
if (v_open(vol, path, HFS_MODE_RDWR) != -1)
break;
if ((mode & HFS_MODE_MASK) == HFS_MODE_RDWR)
goto fail;
case HFS_MODE_RDONLY:
default:
vol->flags |= HFS_VOL_READONLY;
if (v_open(vol, path, HFS_MODE_RDONLY) == -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 = 0;
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 0;
}
/*
* NAME: hfs->flush()
* DESCRIPTION: flush all pending changes to an HFS volume
*/
int hfs_flush(hfsvol *vol)
{
hfsfile *file;
if (getvol(&vol) == -1)
goto fail;
for (file = vol->files; file; file = file->next)
{
if (f_flush(file) == -1)
goto fail;
}
if (v_flush(vol) == -1)
goto fail;
return 0;
fail:
return -1;
}
/*
* NAME: hfs->flushall()
* DESCRIPTION: flush all pending changes to all mounted HFS volumes
*/
void hfs_flushall(void)
{
hfsvol *vol;
for (vol = hfs_mounts; vol; vol = vol->next)
hfs_flush(vol);
}
/*
* 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)
{
result = v_flush(vol);
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 = 0;
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 == 0)
return curvol;
for (vol = hfs_mounts; vol; vol = vol->next)
{
if (d_relstring(name, vol->mdb.drVN) == 0)
return vol;
}
return 0;
}
/*
* 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;
}
/*
* NAME: hfs->vsetattr()
* DESCRIPTION: change volume attributes
*/
int hfs_vsetattr(hfsvol *vol, hfsvolent *ent)
{
if (getvol(&vol) == -1)
goto fail;
if (ent->clumpsz % vol->mdb.drAlBlkSiz != 0)
ERROR(EINVAL, "illegal clump size");
/* make sure "blessed" folder exists */
if (ent->blessed &&
v_getdthread(vol, ent->blessed, 0, 0) <= 0)
ERROR(EINVAL, "illegal blessed folder");
if (vol->flags & HFS_VOL_READONLY)
ERROR(EROFS, 0);
vol->mdb.drClpSiz = ent->clumpsz;
vol->mdb.drCrDate = d_mtime(ent->crdate);
vol->mdb.drLsMod = d_mtime(ent->mddate);
vol->mdb.drVolBkUp = d_mtime(ent->bkdate);
vol->mdb.drFndrInfo[0] = ent->blessed;
vol->flags |= HFS_VOL_UPDATE_MDB;
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, 0, 0, 0) <= 0)
goto fail;
if (data.cdrType != cdrDirRec)
ERROR(ENOTDIR, 0);
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, 0, 0) <= 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, 0) <= 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 = 0;
CatKeyRec key;
CatDataRec data;
byte pkey[HFS_CATKEYLEN];
if (getvol(&vol) == -1)
goto fail;
dir = ALLOC(hfsdir, 1);
if (dir == 0)
ERROR(ENOMEM, 0);
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, 0, 0, 0) <= 0)
goto fail;
if (data.cdrType != cdrDirRec)
ERROR(ENOTDIR, 0);
dir->dirid = data.u.dir.dirDirID;
dir->vptr = 0;
r_makecatkey(&key, dir->dirid, "");
r_packcatkey(&key, pkey, 0);
if (bt_search(&vol->cat, pkey, &dir->n) <= 0)
goto fail;
}
dir->prev = 0;
dir->next = vol->dirs;
if (vol->dirs)
vol->dirs->prev = dir;
vol->dirs = dir;
return dir;
fail:
FREE(dir);
return 0;
}
/*
* 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 == 0)
ERROR(ENOENT, "no more entries");
if (v_getdthread(vol, HFS_CNID_ROOTDIR, &data, 0) <= 0 ||
v_catsearch(vol, HFS_CNID_ROOTPAR, data.u.dthd.thdCName,
&data, cname, 0) <= 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->create()
* DESCRIPTION: create and open a new file
*/
hfsfile *hfs_create(hfsvol *vol, const char *path,
const char *type, const char *creator)
{
hfsfile *file = 0;
unsigned long parid;
char name[HFS_MAX_FLEN + 1];
CatKeyRec key;
byte record[HFS_MAX_CATRECLEN];
unsigned reclen;
int found;
if (getvol(&vol) == -1)
goto fail;
file = ALLOC(hfsfile, 1);
if (file == 0)
ERROR(ENOMEM, 0);
found = v_resolve(&vol, path, &file->cat, &parid, name, 0);
if (found == -1 || parid == 0)
goto fail;
if (found)
ERROR(EEXIST, 0);
if (parid == HFS_CNID_ROOTPAR)
ERROR(EINVAL, 0);
if (vol->flags & HFS_VOL_READONLY)
ERROR(EROFS, 0);
/* create file `name' in parent `parid' */
if (bt_space(&vol->cat, 1) == -1)
goto fail;
f_init(file, vol, vol->mdb.drNxtCNID++, name);
vol->flags |= HFS_VOL_UPDATE_MDB;
file->parid = parid;
/* create catalog record */
file->cat.u.fil.filUsrWds.fdType =
d_getsl((const unsigned char *) type);
file->cat.u.fil.filUsrWds.fdCreator =
d_getsl((const unsigned char *) creator);
file->cat.u.fil.filCrDat = d_mtime(time(0));
file->cat.u.fil.filMdDat = file->cat.u.fil.filCrDat;
r_makecatkey(&key, file->parid, file->name);
r_packcatrec(&key, &file->cat, record, &reclen);
if (bt_insert(&vol->cat, record, reclen) == -1 ||
v_adjvalence(vol, file->parid, 0, 1) == -1)
goto fail;
/* package file handle for user */
file->next = vol->files;
if (vol->files)
vol->files->prev = file;
vol->files = file;
return file;
fail:
FREE(file);
return 0;
}
/*
* NAME: hfs->open()
* DESCRIPTION: prepare a file for I/O
*/
hfsfile *hfs_open(hfsvol *vol, const char *path)
{
hfsfile *file = 0;
if (getvol(&vol) == -1)
goto fail;
file = ALLOC(hfsfile, 1);
if (file == 0)
ERROR(ENOMEM, 0);
if (v_resolve(&vol, path, &file->cat, &file->parid, file->name, 0) <= 0)
goto fail;
if (file->cat.cdrType != cdrFilRec)
ERROR(EISDIR, 0);
/* package file handle for user */
file->vol = vol;
file->flags = 0;
f_selectfork(file, fkData);
file->prev = 0;
file->next = vol->files;
if (vol->files)
vol->files->prev = file;
vol->files = file;
return file;
fail:
FREE(file);
return 0;
}
/*
* NAME: hfs->setfork()
* DESCRIPTION: select file fork for I/O operations
*/
int hfs_setfork(hfsfile *file, int fork)
{
int result = 0;
if (f_trunc(file) == -1)
result = -1;
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, 0, &lglen, 0);
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->write()
* DESCRIPTION: write to an open file
*/
unsigned long hfs_write(hfsfile *file, const void *buf, unsigned long len)
{
unsigned long *lglen, *pylen, count;
const byte *ptr = buf;
if (file->vol->flags & HFS_VOL_READONLY)
ERROR(EROFS, 0);
f_getptrs(file, 0, &lglen, &pylen);
count = len;
/* set flag to update (at least) the modification time */
if (count)
{
file->cat.u.fil.filMdDat = d_mtime(time(0));
file->flags |= HFS_FILE_UPDATE_CATREC;
}
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 (file->pos + chunk > *pylen)
{
if (bt_space(&file->vol->ext, 1) == -1 ||
f_alloc(file) == -1)
goto fail;
}
if (offs == 0 && chunk == HFS_BLOCKSZ)
{
if (f_putblock(file, bnum, (block *) ptr) == -1)
goto fail;
}
else
{
block b;
if (f_getblock(file, bnum, &b) == -1)
goto fail;
memcpy(b + offs, ptr, chunk);
if (f_putblock(file, bnum, &b) == -1)
goto fail;
}
ptr += chunk;
file->pos += chunk;
count -= chunk;
if (file->pos > *lglen)
*lglen = file->pos;
}
return len;
fail:
return -1;
}
/*
* NAME: hfs->truncate()
* DESCRIPTION: truncate an open file
*/
int hfs_truncate(hfsfile *file, unsigned long len)
{
unsigned long *lglen;
f_getptrs(file, 0, &lglen, 0);
if (*lglen > len)
{
if (file->vol->flags & HFS_VOL_READONLY)
ERROR(EROFS, 0);
*lglen = len;
file->cat.u.fil.filMdDat = d_mtime(time(0));
file->flags |= HFS_FILE_UPDATE_CATREC;
if (file->pos > len)
file->pos = len;
}
return 0;
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, 0, &lglen, 0);
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, 0);
}
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 (f_trunc(file) == -1 ||
f_flush(file) == -1)
result = -1;
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, 0) <= 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->setattr()
* DESCRIPTION: change a file's attributes
*/
int hfs_setattr(hfsvol *vol, const char *path, const hfsdirent *ent)
{
CatDataRec data;
node n;
if (getvol(&vol) == -1 ||
v_resolve(&vol, path, &data, 0, 0, &n) <= 0)
goto fail;
if (vol->flags & HFS_VOL_READONLY)
ERROR(EROFS, 0);
r_packdirent(&data, ent);
return v_putcatrec(&data, &n);
fail:
return -1;
}
/*
* NAME: hfs->fsetattr()
* DESCRIPTION: change an open file's attributes
*/
int hfs_fsetattr(hfsfile *file, const hfsdirent *ent)
{
if (file->vol->flags & HFS_VOL_READONLY)
ERROR(EROFS, 0);
r_packdirent(&file->cat, ent);
file->flags |= HFS_FILE_UPDATE_CATREC;
return 0;
fail:
return -1;
}
/*
* NAME: hfs->mkdir()
* DESCRIPTION: create a new directory
*/
int hfs_mkdir(hfsvol *vol, const char *path)
{
CatDataRec data;
unsigned long parid;
char name[HFS_MAX_FLEN + 1];
int found;
if (getvol(&vol) == -1)
goto fail;
found = v_resolve(&vol, path, &data, &parid, name, 0);
if (found == -1 || parid == 0)
goto fail;
if (found)
ERROR(EEXIST, 0);
if (parid == HFS_CNID_ROOTPAR)
ERROR(EINVAL, 0);
if (vol->flags & HFS_VOL_READONLY)
ERROR(EROFS, 0);
return v_mkdir(vol, parid, name);
fail:
return -1;
}
/*
* NAME: hfs->rmdir()
* DESCRIPTION: delete an empty directory
*/
int hfs_rmdir(hfsvol *vol, const char *path)
{
CatKeyRec key;
CatDataRec data;
unsigned long parid;
char name[HFS_MAX_FLEN + 1];
byte pkey[HFS_CATKEYLEN];
if (getvol(&vol) == -1 ||
v_resolve(&vol, path, &data, &parid, name, 0) <= 0)
goto fail;
if (data.cdrType != cdrDirRec)
ERROR(ENOTDIR, 0);
if (data.u.dir.dirVal != 0)
ERROR(ENOTEMPTY, 0);
if (parid == HFS_CNID_ROOTPAR)
ERROR(EINVAL, 0);
if (vol->flags & HFS_VOL_READONLY)
ERROR(EROFS, 0);
/* delete directory record */
r_makecatkey(&key, parid, name);
r_packcatkey(&key, pkey, 0);
if (bt_delete(&vol->cat, pkey) == -1)
goto fail;
/* delete thread record */
r_makecatkey(&key, data.u.dir.dirDirID, "");
r_packcatkey(&key, pkey, 0);
if (bt_delete(&vol->cat, pkey) == -1 ||
v_adjvalence(vol, parid, 1, -1) == -1)
goto fail;
return 0;
fail:
return -1;
}
/*
* NAME: hfs->delete()
* DESCRIPTION: remove both forks of a file
*/
int hfs_delete(hfsvol *vol, const char *path)
{
hfsfile file;
CatKeyRec key;
byte pkey[HFS_CATKEYLEN];
int found;
if (getvol(&vol) == -1 ||
v_resolve(&vol, path, &file.cat, &file.parid, file.name, 0) <= 0)
goto fail;
if (file.cat.cdrType != cdrFilRec)
ERROR(EISDIR, 0);
if (file.parid == HFS_CNID_ROOTPAR)
ERROR(EINVAL, 0);
if (vol->flags & HFS_VOL_READONLY)
ERROR(EROFS, 0);
/* free allocation blocks */
file.vol = vol;
file.flags = 0;
file.cat.u.fil.filLgLen = 0;
file.cat.u.fil.filRLgLen = 0;
f_selectfork(&file, fkData);
if (f_trunc(&file) == -1)
goto fail;
f_selectfork(&file, fkRsrc);
if (f_trunc(&file) == -1)
goto fail;
/* delete file record */
r_makecatkey(&key, file.parid, file.name);
r_packcatkey(&key, pkey, 0);
if (bt_delete(&vol->cat, pkey) == -1 ||
v_adjvalence(vol, file.parid, 0, -1) == -1)
goto fail;
/* delete file thread, if any */
found = v_getfthread(vol, file.cat.u.fil.filFlNum, 0, 0);
if (found == -1)
goto fail;
if (found)
{
r_makecatkey(&key, file.cat.u.fil.filFlNum, "");
r_packcatkey(&key, pkey, 0);
if (bt_delete(&vol->cat, pkey) == -1)
goto fail;
}
return 0;
fail:
return -1;
}
/*
* NAME: hfs->rename()
* DESCRIPTION: change the name of and/or move a file or directory
*/
int hfs_rename(hfsvol *vol, const char *srcpath, const char *dstpath)
{
hfsvol *srcvol;
CatDataRec src, dst;
unsigned long srcid, dstid;
CatKeyRec key;
char srcname[HFS_MAX_FLEN + 1], dstname[HFS_MAX_FLEN + 1];
byte record[HFS_MAX_CATRECLEN];
unsigned int reclen;
int found, isdir, moving;
node n;
if (getvol(&vol) == -1 ||
v_resolve(&vol, srcpath, &src, &srcid, srcname, 0) <= 0)
goto fail;
isdir = (src.cdrType == cdrDirRec);
srcvol = vol;
found = v_resolve(&vol, dstpath, &dst, &dstid, dstname, 0);
if (found == -1)
goto fail;
if (vol != srcvol)
ERROR(EINVAL, "can't move across volumes");
if (dstid == 0)
ERROR(ENOENT, "bad destination path");
if (found &&
dst.cdrType == cdrDirRec &&
dst.u.dir.dirDirID != src.u.dir.dirDirID)
{
dstid = dst.u.dir.dirDirID;
strcpy(dstname, srcname);
found = v_catsearch(vol, dstid, dstname, 0, 0, 0);
if (found == -1)
goto fail;
}
moving = (srcid != dstid);
if (found)
{
const char *ptr;
ptr = strrchr(dstpath, ':');
if (ptr == 0)
ptr = dstpath;
else
++ptr;
if (*ptr)
strcpy(dstname, ptr);
if (! moving && strcmp(srcname, dstname) == 0)
goto done; /* source and destination are identical */
if (moving || d_relstring(srcname, dstname))
ERROR(EEXIST, "can't use destination name");
}
/* can't move anything into the root directory's parent */
if (moving && dstid == HFS_CNID_ROOTPAR)
ERROR(EINVAL, "can't move above root directory");
if (moving && isdir)
{
unsigned long id;
/* can't move root directory anywhere */
if (src.u.dir.dirDirID == HFS_CNID_ROOTDIR)
ERROR(EINVAL, "can't move root directory");
/* make sure we aren't trying to move a directory inside itself */
for (id = dstid; id != HFS_CNID_ROOTDIR; id = dst.u.dthd.thdParID)
{
if (id == src.u.dir.dirDirID)
ERROR(EINVAL, "can't move directory inside itself");
if (v_getdthread(vol, id, &dst, 0) <= 0)
goto fail;
}
}
if (vol->flags & HFS_VOL_READONLY)
ERROR(EROFS, 0);
/* change volume name */
if (dstid == HFS_CNID_ROOTPAR)
{
if (! validvname(dstname))
goto fail;
strcpy(vol->mdb.drVN, dstname);
vol->flags |= HFS_VOL_UPDATE_MDB;
}
/* remove source record */
r_makecatkey(&key, srcid, srcname);
r_packcatkey(&key, record, 0);
if (bt_delete(&vol->cat, record) == -1)
goto fail;
/* insert destination record */
r_makecatkey(&key, dstid, dstname);
r_packcatrec(&key, &src, record, &reclen);
if (bt_insert(&vol->cat, record, reclen) == -1)
goto fail;
/* update thread record */
if (isdir)
{
if (v_getdthread(vol, src.u.dir.dirDirID, &dst, &n) <= 0)
goto fail;
dst.u.dthd.thdParID = dstid;
strcpy(dst.u.dthd.thdCName, dstname);
if (v_putcatrec(&dst, &n) == -1)
goto fail;
}
else
{
found = v_getfthread(vol, src.u.fil.filFlNum, &dst, &n);
if (found == -1)
goto fail;
if (found)
{
dst.u.fthd.fthdParID = dstid;
strcpy(dst.u.fthd.fthdCName, dstname);
if (v_putcatrec(&dst, &n) == -1)
goto fail;
}
}
/* update directory valences */
if (moving)
{
if (v_adjvalence(vol, srcid, isdir, -1) == -1 ||
v_adjvalence(vol, dstid, isdir, 1) == -1)
goto fail;
}
done:
return 0;
fail:
return -1;
}
/* High-Level Media Routines =============================================== */
/*
* NAME: hfs->zero()
* DESCRIPTION: initialize medium with new/empty DDR and partition map
*/
int hfs_zero(const char *path, unsigned int maxparts, unsigned long *blocks)
{
hfsvol vol;
v_init(&vol, HFS_OPT_NOCACHE);
if (maxparts < 1)
ERROR(EINVAL, "must allow at least 1 partition");
if (v_open(&vol, path, HFS_MODE_RDWR) == -1 ||
v_geometry(&vol, 0) == -1)
goto fail;
if (m_zeroddr(&vol) == -1 ||
m_zeropm(&vol, 1 + maxparts) == -1)
goto fail;
if (blocks)
{
Partition map;
int found;
found = m_findpmentry(&vol, "Apple_Free", &map, 0);
if (found == -1)
goto fail;
if (! found)
ERROR(EIO, "unable to determine free partition space");
*blocks = map.pmPartBlkCnt;
}
if (v_close(&vol) == -1)
goto fail;
return 0;
fail:
v_close(&vol);
return -1;
}
/*
* NAME: hfs->mkpart()
* DESCRIPTION: create a new HFS partition
*/
int hfs_mkpart(const char *path, unsigned long len)
{
hfsvol vol;
v_init(&vol, HFS_OPT_NOCACHE);
if (v_open(&vol, path, HFS_MODE_RDWR) == -1)
goto fail;
if (m_mkpart(&vol, "MacOS", "Apple_HFS", len) == -1)
goto fail;
if (v_close(&vol) == -1)
goto fail;
return 0;
fail:
v_close(&vol);
return -1;
}
/*
* NAME: hfs->nparts()
* DESCRIPTION: return the number of HFS partitions in the medium
*/
int hfs_nparts(const char *path)
{
hfsvol vol;
int nparts, found;
Partition map;
unsigned long bnum = 0;
v_init(&vol, HFS_OPT_NOCACHE);
if (v_open(&vol, path, HFS_MODE_RDONLY) == -1)
goto fail;
nparts = 0;
while (1)
{
found = m_findpmentry(&vol, "Apple_HFS", &map, &bnum);
if (found == -1)
goto fail;
if (! found)
break;
++nparts;
}
if (v_close(&vol) == -1)
goto fail;
return nparts;
fail:
v_close(&vol);
return -1;
}
/*
* NAME: hcompare()
* DESCRIPTION: comparison function for qsort of blocks to be spared
*/
static
int hcompare(const unsigned int *n1, const unsigned int *n2)
{
return *n1 - *n2;
}
/*
* NAME: hfs->format()
* DESCRIPTION: write a new filesystem
*/
int hfs_format(const char *path, int pnum, int mode, const char *vname,
unsigned int nbadblocks, const unsigned long badblocks[])
{
hfsvol vol;
btree *ext = &vol.ext;
btree *cat = &vol.cat;
unsigned int i, *badalloc = 0;
v_init(&vol, mode);
if (! validvname(vname))
goto fail;
if (v_open(&vol, path, HFS_MODE_RDWR) == -1 ||
v_geometry(&vol, pnum) == -1)
goto fail;
/* initialize volume geometry */
vol.lpa = 1 + ((vol.vlen - 6) >> 16);
if (vol.flags & HFS_OPT_2048)
vol.lpa = (vol.lpa + 3) & ~3;
vol.vbmsz = (vol.vlen / vol.lpa + 0x0fff) >> 12;
vol.mdb.drSigWord = HFS_SIGWORD;
vol.mdb.drCrDate = d_mtime(time(0));
vol.mdb.drLsMod = vol.mdb.drCrDate;
vol.mdb.drAtrb = 0;
vol.mdb.drNmFls = 0;
vol.mdb.drVBMSt = 3;
vol.mdb.drAllocPtr = 0;
vol.mdb.drAlBlkSiz = vol.lpa << HFS_BLOCKSZ_BITS;
vol.mdb.drClpSiz = vol.mdb.drAlBlkSiz << 2;
vol.mdb.drAlBlSt = vol.mdb.drVBMSt + vol.vbmsz;
if (vol.flags & HFS_OPT_2048)
vol.mdb.drAlBlSt = ((vol.vstart & 3) + vol.mdb.drAlBlSt + 3) & ~3;
vol.mdb.drNmAlBlks = (vol.vlen - 2 - vol.mdb.drAlBlSt) / vol.lpa;
vol.mdb.drNxtCNID = HFS_CNID_ROOTDIR; /* modified later */
vol.mdb.drFreeBks = vol.mdb.drNmAlBlks;
strcpy(vol.mdb.drVN, vname);
vol.mdb.drVolBkUp = 0;
vol.mdb.drVSeqNum = 0;
vol.mdb.drWrCnt = 0;
vol.mdb.drXTClpSiz = vol.mdb.drNmAlBlks / 128 * vol.mdb.drAlBlkSiz;
vol.mdb.drCTClpSiz = vol.mdb.drXTClpSiz;
vol.mdb.drNmRtDirs = 0;
vol.mdb.drFilCnt = 0;
vol.mdb.drDirCnt = -1; /* incremented when root directory is created */
for (i = 0; i < 8; ++i)
vol.mdb.drFndrInfo[i] = 0;
vol.mdb.drEmbedSigWord = 0x0000;
vol.mdb.drEmbedExtent.xdrStABN = 0;
vol.mdb.drEmbedExtent.xdrNumABlks = 0;
/* vol.mdb.drXTFlSize */
/* vol.mdb.drCTFlSize */
/* vol.mdb.drXTExtRec[0..2] */
/* vol.mdb.drCTExtRec[0..2] */
vol.flags |= HFS_VOL_UPDATE_MDB | HFS_VOL_UPDATE_ALTMDB;
/* initialize volume bitmap */
vol.vbm = ALLOC(block, vol.vbmsz);
if (vol.vbm == 0)
ERROR(ENOMEM, 0);
memset(vol.vbm, 0, vol.vbmsz << HFS_BLOCKSZ_BITS);
vol.flags |= HFS_VOL_UPDATE_VBM;
/* perform initial bad block sparing */
if (nbadblocks > 0)
{
if (nbadblocks * 4 > vol.vlen)
ERROR(EINVAL, "volume contains too many bad blocks");
badalloc = ALLOC(unsigned int, nbadblocks);
if (badalloc == 0)
ERROR(ENOMEM, 0);
if (vol.mdb.drNmAlBlks == 1594)
vol.mdb.drFreeBks = --vol.mdb.drNmAlBlks;
for (i = 0; i < nbadblocks; ++i)
{
unsigned long bnum;
unsigned int anum;
bnum = badblocks[i];
if (bnum < vol.mdb.drAlBlSt || bnum == vol.vlen - 2)
ERROR(EINVAL, "can't spare critical bad block");
else if (bnum >= vol.vlen)
ERROR(EINVAL, "bad block not in volume");
anum = (bnum - vol.mdb.drAlBlSt) / vol.lpa;
if (anum < vol.mdb.drNmAlBlks)
BMSET(vol.vbm, anum);
badalloc[i] = anum;
}
vol.mdb.drAtrb |= HFS_ATRB_BBSPARED;
}
/* create extents overflow file */
n_init(&ext->hdrnd, ext, ndHdrNode, 0);
ext->hdrnd.nnum = 0;
ext->hdrnd.nd.ndNRecs = 3;
ext->hdrnd.roff[1] = 0x078;
ext->hdrnd.roff[2] = 0x0f8;
ext->hdrnd.roff[3] = 0x1f8;
memset(HFS_NODEREC(ext->hdrnd, 1), 0, 128);
ext->hdr.bthDepth = 0;
ext->hdr.bthRoot = 0;
ext->hdr.bthNRecs = 0;
ext->hdr.bthFNode = 0;
ext->hdr.bthLNode = 0;
ext->hdr.bthNodeSize = HFS_BLOCKSZ;
ext->hdr.bthKeyLen = 0x07;
ext->hdr.bthNNodes = 0;
ext->hdr.bthFree = 0;
for (i = 0; i < 76; ++i)
ext->hdr.bthResv[i] = 0;
ext->map = ALLOC(byte, HFS_MAP1SZ);
if (ext->map == 0)
ERROR(ENOMEM, 0);
memset(ext->map, 0, HFS_MAP1SZ);
BMSET(ext->map, 0);
ext->mapsz = HFS_MAP1SZ;
ext->flags = HFS_BT_UPDATE_HDR;
/* create catalog file */
n_init(&cat->hdrnd, cat, ndHdrNode, 0);
cat->hdrnd.nnum = 0;
cat->hdrnd.nd.ndNRecs = 3;
cat->hdrnd.roff[1] = 0x078;
cat->hdrnd.roff[2] = 0x0f8;
cat->hdrnd.roff[3] = 0x1f8;
memset(HFS_NODEREC(cat->hdrnd, 1), 0, 128);
cat->hdr.bthDepth = 0;
cat->hdr.bthRoot = 0;
cat->hdr.bthNRecs = 0;
cat->hdr.bthFNode = 0;
cat->hdr.bthLNode = 0;
cat->hdr.bthNodeSize = HFS_BLOCKSZ;
cat->hdr.bthKeyLen = 0x25;
cat->hdr.bthNNodes = 0;
cat->hdr.bthFree = 0;
for (i = 0; i < 76; ++i)
cat->hdr.bthResv[i] = 0;
cat->map = ALLOC(byte, HFS_MAP1SZ);
if (cat->map == 0)
ERROR(ENOMEM, 0);
memset(cat->map, 0, HFS_MAP1SZ);
BMSET(cat->map, 0);
cat->mapsz = HFS_MAP1SZ;
cat->flags = HFS_BT_UPDATE_HDR;
/* allocate space for header nodes (and initial extents) */
if (bt_space(ext, 1) == -1 ||
bt_space(cat, 1) == -1)
goto fail;
--ext->hdr.bthFree;
--cat->hdr.bthFree;
/* create extent records for bad blocks */
if (nbadblocks > 0)
{
hfsfile bbfile;
ExtDescriptor extent;
ExtDataRec *extrec;
ExtKeyRec key;
byte record[HFS_MAX_EXTRECLEN];
unsigned int reclen;
f_init(&bbfile, &vol, HFS_CNID_BADALLOC, "bad blocks");
qsort(badalloc, nbadblocks, sizeof(*badalloc),
(int (*)(const void *, const void *)) hcompare);
for (i = 0; i < nbadblocks; ++i)
{
if (i == 0 || badalloc[i] != extent.xdrStABN)
{
extent.xdrStABN = badalloc[i];
extent.xdrNumABlks = 1;
if (extent.xdrStABN < vol.mdb.drNmAlBlks &&
f_addextent(&bbfile, &extent) == -1)
goto fail;
}
}
/* flush local extents into extents overflow file */
f_getptrs(&bbfile, &extrec, 0, 0);
r_makeextkey(&key, bbfile.fork, bbfile.cat.u.fil.filFlNum, 0);
r_packextrec(&key, extrec, record, &reclen);
if (bt_insert(&vol.ext, record, reclen) == -1)
goto fail;
}
vol.flags |= HFS_VOL_MOUNTED;
/* create root directory */
if (v_mkdir(&vol, HFS_CNID_ROOTPAR, vname) == -1)
goto fail;
vol.mdb.drNxtCNID = 16; /* first CNID not reserved by Apple */
/* write boot blocks */
if (m_zerobb(&vol) == -1)
goto fail;
/* zero other unused space, if requested */
if (vol.flags & HFS_OPT_ZERO)
{
block b;
unsigned long bnum;
memset(&b, 0, sizeof(b));
/* between MDB and VBM (never) */
for (bnum = 3; bnum < vol.mdb.drVBMSt; ++bnum)
b_writelb(&vol, bnum, &b);
/* between VBM and first allocation block (sometimes if HFS_OPT_2048) */
for (bnum = vol.mdb.drVBMSt + vol.vbmsz; bnum < vol.mdb.drAlBlSt; ++bnum)
b_writelb(&vol, bnum, &b);
/* between last allocation block and alternate MDB (sometimes) */
for (bnum = vol.mdb.drAlBlSt + vol.mdb.drNmAlBlks * vol.lpa;
bnum < vol.vlen - 2; ++bnum)
b_writelb(&vol, bnum, &b);
/* final block (always) */
b_writelb(&vol, vol.vlen - 1, &b);
}
/* flush remaining state and close volume */
if (v_close(&vol) == -1)
goto fail;
FREE(badalloc);
return 0;
fail:
v_close(&vol);
FREE(badalloc);
return -1;
}