cdrtools/libhfs_iso/volume.c

775 lines
14 KiB
C
Raw Permalink Normal View History

2025-06-15 04:19:58 +08:00
/* @(#)volume.c 1.9 09/07/11 joerg */
#include <schily/mconfig.h>
#ifndef lint
static UConst char sccsid[] =
"@(#)volume.c 1.9 09/07/11 joerg";
#endif
/*
* hfsutils - tools for reading and writing Macintosh HFS volumes
* Copyright (C) 1996, 1997 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.
*/
#include <schily/stdlib.h>
#include <schily/string.h>
#include <schily/errno.h>
#include "internal.h"
#include "data.h"
#include "low.h"
#include "btree.h"
#include "record.h"
#include "volume.h"
static void markexts __PR((block *vbm, ExtDataRec *exts));
/*
* NAME: vol->catsearch()
* DESCRIPTION: search catalog tree
*/
int v_catsearch(vol, parid, name, data, cname, np)
hfsvol *vol;
long parid;
char *name;
CatDataRec *data;
char *cname;
node *np;
{
CatKeyRec key;
unsigned char pkey[HFS_CATKEYLEN];
node n;
unsigned char *ptr;
int found;
if (np == 0)
np = &n;
r_makecatkey(&key, parid, name);
r_packcatkey(&key, pkey, 0);
found = bt_search(&vol->cat, pkey, np);
if (found <= 0)
return found;
ptr = HFS_NODEREC(*np, np->rnum);
if (cname)
{
r_unpackcatkey(ptr, &key);
strcpy(cname, key.ckrCName);
}
if (data)
r_unpackcatdata(HFS_RECDATA(ptr), data);
return 1;
}
/*
* NAME: vol->extsearch()
* DESCRIPTION: search extents tree
*/
int v_extsearch(file, fabn, data, np)
hfsfile *file;
unsigned int fabn;
ExtDataRec *data;
node *np;
{
ExtKeyRec key;
ExtDataRec extsave;
unsigned int fabnsave;
unsigned char pkey[HFS_EXTKEYLEN];
node n;
unsigned char *ptr;
int found;
if (np == 0)
np = &n;
r_makeextkey(&key, file->fork, file->cat.u.fil.filFlNum, fabn);
r_packextkey(&key, pkey, 0);
/* in case bt_search() clobbers these */
memcpy(extsave, &file->ext, sizeof(ExtDataRec));
fabnsave = file->fabn;
found = bt_search(&file->vol->ext, pkey, np);
memcpy(file->ext, extsave, sizeof(ExtDataRec));
file->fabn = fabnsave;
if (found <= 0)
return found;
if (data)
{
ptr = HFS_NODEREC(*np, np->rnum);
r_unpackextdata(HFS_RECDATA(ptr), data);
}
return 1;
}
/*
* NAME: vol->getthread()
* DESCRIPTION: retrieve catalog thread information for a file or directory
*/
int v_getthread(vol, id, thread, np, type)
hfsvol *vol;
long id;
CatDataRec *thread;
node *np;
int type;
{
CatDataRec rec;
int found;
if (thread == 0)
thread = &rec;
found = v_catsearch(vol, id, "", thread, 0, np);
if (found <= 0)
return found;
if (thread->cdrType != type)
{
ERROR(EIO, "bad thread record");
return -1;
}
return 1;
}
/*
* NAME: vol->putcatrec()
* DESCRIPTION: store catalog information
*/
int v_putcatrec(data, np)
CatDataRec *data;
node *np;
{
unsigned char pdata[HFS_CATDATALEN], *ptr;
int len = 0;
r_packcatdata(data, pdata, &len);
ptr = HFS_NODEREC(*np, np->rnum);
memcpy(HFS_RECDATA(ptr), pdata, len);
return bt_putnode(np);
}
/*
* NAME: vol->putextrec()
* DESCRIPTION: store extent information
*/
int v_putextrec(data, np)
ExtDataRec *data;
node *np;
{
unsigned char pdata[HFS_EXTDATALEN], *ptr;
int len = 0;
r_packextdata(data, pdata, &len);
ptr = HFS_NODEREC(*np, np->rnum);
memcpy(HFS_RECDATA(ptr), pdata, len);
return bt_putnode(np);
}
/*
* NAME: vol->allocblocks()
* DESCRIPTION: allocate a contiguous range of blocks
*/
int v_allocblocks(vol, blocks)
hfsvol *vol;
ExtDescriptor *blocks;
{
unsigned int request, found, foundat, start, end, pt;
block *vbm;
int wrap = 0;
if (vol->mdb.drFreeBks == 0)
{
ERROR(ENOSPC, "volume full");
return -1;
}
request = blocks->xdrNumABlks;
found = 0;
foundat = 0;
start = vol->mdb.drAllocPtr;
end = vol->mdb.drNmAlBlks;
pt = start;
vbm = vol->vbm;
if (request == 0)
abort();
for (;;)
{
unsigned int mark;
/* skip blocks in use */
while (pt < end && BMTST(vbm, pt))
++pt;
if (wrap && pt >= start)
break;
/* count blocks not in use */
mark = pt;
while (pt < end && pt - mark < request && ! BMTST(vbm, pt))
++pt;
if (pt - mark > found)
{
found = pt - mark;
foundat = mark;
}
if (pt == end)
pt = 0, wrap = 1;
if (found == request)
break;
}
if (found == 0 || found > vol->mdb.drFreeBks)
{
ERROR(EIO, "bad volume bitmap or free block count");
return -1;
}
blocks->xdrStABN = foundat;
blocks->xdrNumABlks = found;
vol->mdb.drAllocPtr = pt;
vol->mdb.drFreeBks -= found;
for (pt = foundat; pt < foundat + found; ++pt)
BMSET(vbm, pt);
vol->flags |= HFS_UPDATE_MDB | HFS_UPDATE_VBM;
return 0;
}
/*
* NAME: vol->freeblocks()
* DESCRIPTION: deallocate a contiguous range of blocks
*/
void v_freeblocks(vol, blocks)
hfsvol *vol;
ExtDescriptor *blocks;
{
unsigned int start, len, pt;
block *vbm;
start = blocks->xdrStABN;
len = blocks->xdrNumABlks;
vbm = vol->vbm;
vol->mdb.drFreeBks += len;
for (pt = start; pt < start + len; ++pt)
BMCLR(vbm, pt);
vol->flags |= HFS_UPDATE_MDB | HFS_UPDATE_VBM;
}
/*
* NAME: vol->resolve()
* DESCRIPTION: translate a pathname; return catalog information
*/
int v_resolve(vol, path, data, parid, fname, np)
hfsvol **vol;
char *path;
CatDataRec *data;
long *parid;
char *fname;
node *np;
{
long dirid;
char name[HFS_MAX_FLEN + 1], *nptr;
int found;
if (*path == 0)
{
ERROR(ENOENT, "empty path");
return -1;
}
if (parid)
*parid = 0;
nptr = strchr(path, ':');
if (*path == ':' || nptr == 0)
{
dirid = (*vol)->cwd; /* relative path */
if (*path == ':')
++path;
if (*path == 0)
{
found = v_getdthread(*vol, dirid, data, 0);
if (found <= 0)
return found;
if (parid)
*parid = data->u.dthd.thdParID;
return v_catsearch(*vol, data->u.dthd.thdParID,
data->u.dthd.thdCName, data, fname, np);
}
}
else
{
hfsvol *check;
dirid = HFS_CNID_ROOTPAR; /* absolute path */
if (nptr - path > HFS_MAX_VLEN)
{
ERROR(ENAMETOOLONG, 0);
return -1;
}
strncpy(name, path, nptr - path);
name[nptr - path] = 0;
for (check = hfs_mounts; check; check = check->next)
{
if (d_relstring(check->mdb.drVN, name) == 0)
{
*vol = check;
break;
}
}
}
for (;;)
{
while (*path == ':')
{
++path;
found = v_getdthread(*vol, dirid, data, 0);
if (found <= 0)
return found;
dirid = data->u.dthd.thdParID;
}
if (*path == 0)
{
found = v_getdthread(*vol, dirid, data, 0);
if (found <= 0)
return found;
if (parid)
*parid = data->u.dthd.thdParID;
return v_catsearch(*vol, data->u.dthd.thdParID,
data->u.dthd.thdCName, data, fname, np);
}
nptr = name;
while (nptr < name + sizeof(name) - 1 && *path && *path != ':')
*nptr++ = *path++;
if (*path && *path != ':')
{
ERROR(ENAMETOOLONG, 0);
return -1;
}
*nptr = 0;
if (*path == ':')
++path;
if (parid)
*parid = dirid;
found = v_catsearch(*vol, dirid, name, data, fname, np);
if (found < 0)
return -1;
if (found == 0)
{
if (*path && parid)
*parid = 0;
if (*path == 0 && fname)
strcpy(fname, name);
return 0;
}
switch (data->cdrType)
{
case cdrDirRec:
if (*path == 0)
return 1;
dirid = data->u.dir.dirDirID;
break;
case cdrFilRec:
if (*path == 0)
return 1;
ERROR(ENOTDIR, "invalid pathname");
return -1;
default:
ERROR(EIO, "unexpected catalog record");
return -1;
}
}
}
/*
* NAME: vol->destruct()
* DESCRIPTION: free memory consumed by a volume descriptor
*/
void v_destruct(vol)
hfsvol *vol;
{
FREE(vol->vbm);
FREE(vol->ext.map);
FREE(vol->cat.map);
FREE(vol);
}
/*
* NAME: vol->getvol()
* DESCRIPTION: validate a volume reference
*/
int v_getvol(vol)
hfsvol **vol;
{
if (*vol == 0)
{
if (hfs_curvol == 0)
{
ERROR(EINVAL, "no volume is current");
return -1;
}
*vol = hfs_curvol;
}
return 0;
}
/*
* NAME: vol->flush()
* DESCRIPTION: flush all pending changes (B*-tree, MDB, VBM) to disk
*/
int v_flush(vol, umounting)
hfsvol *vol;
int umounting;
{
if (! (vol->flags & HFS_READONLY))
{
if ((vol->ext.flags & HFS_UPDATE_BTHDR) &&
bt_writehdr(&vol->ext) < 0)
return -1;
if ((vol->cat.flags & HFS_UPDATE_BTHDR) &&
bt_writehdr(&vol->cat) < 0)
return -1;
if ((vol->flags & HFS_UPDATE_VBM) &&
l_writevbm(vol) < 0)
return -1;
if (umounting &&
! (vol->mdb.drAtrb & HFS_ATRB_UMOUNTED))
{
vol->mdb.drAtrb |= HFS_ATRB_UMOUNTED;
vol->flags |= HFS_UPDATE_MDB;
}
if ((vol->flags & (HFS_UPDATE_MDB | HFS_UPDATE_ALTMDB)) &&
l_writemdb(vol) < 0)
return -1;
}
return 0;
}
/*
* NAME: vol->adjvalence()
* DESCRIPTION: update a volume's valence counts
*/
int v_adjvalence(vol, parid, isdir, adj)
hfsvol *vol;
long parid;
int isdir;
int adj;
{
node n;
CatDataRec data;
if (isdir)
vol->mdb.drDirCnt += adj;
else
vol->mdb.drFilCnt += adj;
vol->flags |= HFS_UPDATE_MDB;
if (parid == HFS_CNID_ROOTDIR)
{
if (isdir)
vol->mdb.drNmRtDirs += adj;
else
vol->mdb.drNmFls += adj;
}
else if (parid == HFS_CNID_ROOTPAR)
return 0;
if (v_getdthread(vol, parid, &data, 0) <= 0 ||
v_catsearch(vol, data.u.dthd.thdParID, data.u.dthd.thdCName,
&data, 0, &n) <= 0 ||
data.cdrType != cdrDirRec)
{
ERROR(EIO, "can't find parent directory");
return -1;
}
data.u.dir.dirVal += adj;
data.u.dir.dirMdDat = d_tomtime(time(0));
return v_putcatrec(&data, &n);
}
/*
* NAME: vol->newfolder()
* DESCRIPTION: create a new HFS folder
*/
int v_newfolder(vol, parid, name)
hfsvol *vol;
long parid;
char *name;
{
CatKeyRec key;
CatDataRec data;
long id;
unsigned char record[HFS_CATRECMAXLEN];
int i, reclen;
if (bt_space(&vol->cat, 2) < 0)
return -1;
id = vol->mdb.drNxtCNID++;
vol->flags |= HFS_UPDATE_MDB;
/* create directory record */
data.cdrType = cdrDirRec;
data.cdrResrv2 = 0;
data.u.dir.dirFlags = 0;
data.u.dir.dirVal = 0;
data.u.dir.dirDirID = id;
data.u.dir.dirCrDat = d_tomtime(time(0));
data.u.dir.dirMdDat = data.u.dir.dirCrDat;
data.u.dir.dirBkDat = 0;
memset(&data.u.dir.dirUsrInfo, 0, sizeof(data.u.dir.dirUsrInfo));
memset(&data.u.dir.dirFndrInfo, 0, sizeof(data.u.dir.dirFndrInfo));
for (i = 0; i < 4; ++i)
data.u.dir.dirResrv[i] = 0;
r_makecatkey(&key, parid, name);
r_packcatkey(&key, record, &reclen);
r_packcatdata(&data, HFS_RECDATA(record), &reclen);
if (bt_insert(&vol->cat, record, reclen) < 0)
return -1;
/* create thread record */
data.cdrType = cdrThdRec;
data.cdrResrv2 = 0;
data.u.dthd.thdResrv[0] = 0;
data.u.dthd.thdResrv[1] = 0;
data.u.dthd.thdParID = parid;
strcpy(data.u.dthd.thdCName, name);
r_makecatkey(&key, id, "");
r_packcatkey(&key, record, &reclen);
r_packcatdata(&data, HFS_RECDATA(record), &reclen);
if (bt_insert(&vol->cat, record, reclen) < 0 ||
v_adjvalence(vol, parid, 1, 1) < 0)
return -1;
return 0;
}
/*
* NAME: markexts()
* DESCRIPTION: set bits from an extent record in the volume bitmap
*/
static
void markexts(vbm, exts)
block *vbm;
ExtDataRec *exts;
{
int i;
unsigned int start, len;
for (i = 0; i < 3; ++i)
{
for (start = (*exts)[i].xdrStABN,
len = (*exts)[i].xdrNumABlks; len--; ++start)
BMSET(vbm, start);
}
}
/*
* NAME: vol->scavenge()
* DESCRIPTION: safeguard blocks in the volume bitmap
*/
int v_scavenge(vol)
hfsvol *vol;
{
block *vbm = vol->vbm;
node n;
unsigned int pt, blks;
if (vbm == 0)
return 0;
markexts(vbm, &vol->mdb.drXTExtRec);
markexts(vbm, &vol->mdb.drCTExtRec);
vol->flags |= HFS_UPDATE_VBM;
/* scavenge the extents overflow file */
n.bt = &vol->ext;
n.nnum = vol->ext.hdr.bthFNode;
if (n.nnum > 0)
{
if (bt_getnode(&n) < 0)
return -1;
n.rnum = 0;
for (;;)
{
ExtDataRec data;
unsigned char *ptr;
while (n.rnum >= (int)n.nd.ndNRecs)
{
n.nnum = n.nd.ndFLink;
if (n.nnum == 0)
break;
if (bt_getnode(&n) < 0)
return -1;
n.rnum = 0;
}
if (n.nnum == 0)
break;
ptr = HFS_NODEREC(n, n.rnum);
r_unpackextdata(HFS_RECDATA(ptr), &data);
markexts(vbm, &data);
++n.rnum;
}
}
/* scavenge the catalog file */
n.bt = &vol->cat;
n.nnum = vol->cat.hdr.bthFNode;
if (n.nnum > 0)
{
if (bt_getnode(&n) < 0)
return -1;
n.rnum = 0;
for (;;)
{
CatDataRec data;
unsigned char *ptr;
while (n.rnum >= (int)n.nd.ndNRecs)
{
n.nnum = n.nd.ndFLink;
if (n.nnum == 0)
break;
if (bt_getnode(&n) < 0)
return -1;
n.rnum = 0;
}
if (n.nnum == 0)
break;
ptr = HFS_NODEREC(n, n.rnum);
r_unpackcatdata(HFS_RECDATA(ptr), &data);
if (data.cdrType == cdrFilRec)
{
markexts(vbm, &data.u.fil.filExtRec);
markexts(vbm, &data.u.fil.filRExtRec);
}
++n.rnum;
}
}
for (blks = 0, pt = vol->mdb.drNmAlBlks; pt--; )
{
if (! BMTST(vbm, pt))
++blks;
}
if (vol->mdb.drFreeBks != blks)
{
vol->mdb.drFreeBks = blks;
vol->flags |= HFS_UPDATE_MDB;
}
return 0;
}