| /* |
| * 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: volume.c,v 1.12 1998/11/02 22:09:10 rob Exp $ |
| */ |
| |
| #include "config.h" |
| #include "libhfs.h" |
| #include "volume.h" |
| #include "data.h" |
| #include "block.h" |
| #include "low.h" |
| #include "medium.h" |
| #include "file.h" |
| #include "btree.h" |
| #include "record.h" |
| #include "os.h" |
| |
| #include "libc/byteorder.h" |
| |
| /* |
| * NAME: vol->init() |
| * DESCRIPTION: initialize volume structure |
| */ |
| void v_init(hfsvol *vol, int flags) |
| { |
| btree *ext = &vol->ext; |
| btree *cat = &vol->cat; |
| |
| vol->os_fd = 0; |
| vol->flags = flags & HFS_VOL_OPT_MASK; |
| |
| vol->pnum = -1; |
| vol->vstart = 0; |
| vol->vlen = 0; |
| vol->lpa = 0; |
| |
| vol->cache = NULL; |
| |
| vol->vbm = NULL; |
| vol->vbmsz = 0; |
| |
| f_init(&ext->f, vol, HFS_CNID_EXT, "extents overflow"); |
| |
| ext->map = NULL; |
| ext->mapsz = 0; |
| ext->flags = 0; |
| |
| ext->keyunpack = (keyunpackfunc) r_unpackextkey; |
| ext->keycompare = (keycomparefunc) r_compareextkeys; |
| |
| f_init(&cat->f, vol, HFS_CNID_CAT, "catalog"); |
| |
| cat->map = NULL; |
| cat->mapsz = 0; |
| cat->flags = 0; |
| |
| cat->keyunpack = (keyunpackfunc) r_unpackcatkey; |
| cat->keycompare = (keycomparefunc) r_comparecatkeys; |
| |
| vol->cwd = HFS_CNID_ROOTDIR; |
| |
| vol->refs = 0; |
| vol->files = NULL; |
| vol->dirs = NULL; |
| |
| vol->prev = NULL; |
| vol->next = NULL; |
| } |
| |
| /* |
| * NAME: vol->open() |
| * DESCRIPTION: open volume source and lock against concurrent updates |
| */ |
| int v_open(hfsvol *vol, int os_fd ) |
| { |
| if (vol->flags & HFS_VOL_OPEN) |
| ERROR(EINVAL, "volume already open"); |
| |
| vol->flags |= HFS_VOL_OPEN; |
| vol->os_fd = os_fd; |
| |
| /* initialize volume block cache (OK to fail) */ |
| |
| if (! (vol->flags & HFS_OPT_NOCACHE) && |
| b_init(vol) != -1) |
| vol->flags |= HFS_VOL_USINGCACHE; |
| |
| return 0; |
| |
| fail: |
| return -1; |
| } |
| |
| /* |
| * NAME: vol->close() |
| * DESCRIPTION: close access path to volume source |
| */ |
| int v_close(hfsvol *vol) |
| { |
| int result = 0; |
| |
| if (! (vol->flags & HFS_VOL_OPEN)) |
| goto done; |
| |
| if ((vol->flags & HFS_VOL_USINGCACHE) && |
| b_finish(vol) == -1) |
| result = -1; |
| |
| vol->flags &= ~(HFS_VOL_OPEN | HFS_VOL_MOUNTED | HFS_VOL_USINGCACHE); |
| |
| /* free dynamically allocated structures */ |
| |
| FREE(vol->vbm); |
| |
| vol->vbm = NULL; |
| vol->vbmsz = 0; |
| |
| FREE(vol->ext.map); |
| FREE(vol->cat.map); |
| |
| vol->ext.map = NULL; |
| vol->cat.map = NULL; |
| |
| done: |
| return result; |
| } |
| |
| /* |
| * NAME: vol->same() |
| * DESCRIPTION: return 1 iff path is same as open volume |
| */ |
| int v_same(hfsvol *vol, int os_fd ) |
| { |
| return vol->os_fd == os_fd; |
| } |
| |
| /* |
| * NAME: vol->geometry() |
| * DESCRIPTION: determine volume location and size (possibly in a partition) |
| */ |
| int v_geometry(hfsvol *vol, int pnum) |
| { |
| Partition map; |
| unsigned long bnum = 0; |
| int found; |
| |
| vol->pnum = pnum; |
| |
| if (pnum == 0) |
| { |
| vol->vstart = 0; |
| vol->vlen = b_size(vol); |
| |
| if (vol->vlen == 0) |
| goto fail; |
| } |
| else |
| { |
| while (pnum--) |
| { |
| found = m_findpmentry(vol, "Apple_HFS", &map, &bnum); |
| if (found == -1 || ! found) |
| goto fail; |
| } |
| |
| vol->vstart = map.pmPyPartStart; |
| vol->vlen = map.pmPartBlkCnt; |
| |
| if (map.pmDataCnt) |
| { |
| if ((unsigned long) map.pmLgDataStart + |
| (unsigned long) map.pmDataCnt > vol->vlen) |
| ERROR(EINVAL, "partition data overflows partition"); |
| |
| vol->vstart += (unsigned long) map.pmLgDataStart; |
| vol->vlen = map.pmDataCnt; |
| } |
| |
| if (vol->vlen == 0) |
| ERROR(EINVAL, "volume partition is empty"); |
| } |
| |
| if (vol->vlen < 800 * (1024 >> HFS_BLOCKSZ_BITS)) |
| ERROR(EINVAL, "volume is smaller than 800K"); |
| |
| return 0; |
| |
| fail: |
| return -1; |
| } |
| |
| /* |
| * NAME: vol->readmdb() |
| * DESCRIPTION: load Master Directory Block into memory |
| */ |
| int v_readmdb(hfsvol *vol) |
| { |
| if (l_getmdb(vol, &vol->mdb, 0) == -1) |
| goto fail; |
| |
| if (vol->mdb.drSigWord != HFS_SIGWORD) |
| { |
| if (vol->mdb.drSigWord == HFS_SIGWORD_MFS) |
| ERROR(EINVAL, "MFS volume format not supported"); |
| else |
| ERROR(EINVAL, "not a Macintosh HFS volume"); |
| } |
| |
| if (vol->mdb.drAlBlkSiz % HFS_BLOCKSZ != 0) |
| ERROR(EINVAL, "bad volume allocation block size"); |
| |
| vol->lpa = vol->mdb.drAlBlkSiz >> HFS_BLOCKSZ_BITS; |
| |
| /* extents pseudo-file structs */ |
| |
| vol->ext.f.cat.u.fil.filStBlk = vol->mdb.drXTExtRec[0].xdrStABN; |
| vol->ext.f.cat.u.fil.filLgLen = vol->mdb.drXTFlSize; |
| vol->ext.f.cat.u.fil.filPyLen = vol->mdb.drXTFlSize; |
| |
| vol->ext.f.cat.u.fil.filCrDat = vol->mdb.drCrDate; |
| vol->ext.f.cat.u.fil.filMdDat = vol->mdb.drLsMod; |
| |
| memcpy(&vol->ext.f.cat.u.fil.filExtRec, |
| &vol->mdb.drXTExtRec, sizeof(ExtDataRec)); |
| |
| f_selectfork(&vol->ext.f, fkData); |
| |
| /* catalog pseudo-file structs */ |
| |
| vol->cat.f.cat.u.fil.filStBlk = vol->mdb.drCTExtRec[0].xdrStABN; |
| vol->cat.f.cat.u.fil.filLgLen = vol->mdb.drCTFlSize; |
| vol->cat.f.cat.u.fil.filPyLen = vol->mdb.drCTFlSize; |
| |
| vol->cat.f.cat.u.fil.filCrDat = vol->mdb.drCrDate; |
| vol->cat.f.cat.u.fil.filMdDat = vol->mdb.drLsMod; |
| |
| memcpy(&vol->cat.f.cat.u.fil.filExtRec, |
| &vol->mdb.drCTExtRec, sizeof(ExtDataRec)); |
| |
| f_selectfork(&vol->cat.f, fkData); |
| |
| return 0; |
| |
| fail: |
| return -1; |
| } |
| |
| /* |
| * NAME: vol->readvbm() |
| * DESCRIPTION: read volume bitmap into memory |
| */ |
| int v_readvbm(hfsvol *vol) |
| { |
| unsigned int vbmst = vol->mdb.drVBMSt; |
| unsigned int vbmsz = (vol->mdb.drNmAlBlks + 0x0fff) >> 12; |
| block *bp; |
| |
| ASSERT(vol->vbm == 0); |
| |
| if (vol->mdb.drAlBlSt - vbmst < vbmsz) |
| ERROR(EIO, "volume bitmap collides with volume data"); |
| |
| vol->vbm = ALLOC(block, vbmsz); |
| if (vol->vbm == NULL) |
| ERROR(ENOMEM, NULL); |
| |
| vol->vbmsz = vbmsz; |
| |
| for (bp = vol->vbm; vbmsz--; ++bp) |
| { |
| if (b_readlb(vol, vbmst++, bp) == -1) |
| goto fail; |
| } |
| |
| return 0; |
| |
| fail: |
| FREE(vol->vbm); |
| |
| vol->vbm = NULL; |
| vol->vbmsz = 0; |
| |
| return -1; |
| } |
| |
| /* |
| * NAME: vol->mount() |
| * DESCRIPTION: load volume information into memory |
| */ |
| int v_mount(hfsvol *vol) |
| { |
| /* read the MDB, volume bitmap, and extents/catalog B*-tree headers */ |
| |
| if (v_readmdb(vol) == -1 || |
| v_readvbm(vol) == -1 || |
| bt_readhdr(&vol->ext) == -1 || |
| bt_readhdr(&vol->cat) == -1) |
| goto fail; |
| |
| if (vol->mdb.drAtrb & HFS_ATRB_SLOCKED) |
| vol->flags |= HFS_VOL_READONLY; |
| else if (vol->flags & HFS_VOL_READONLY) |
| vol->mdb.drAtrb |= HFS_ATRB_HLOCKED; |
| else |
| vol->mdb.drAtrb &= ~HFS_ATRB_HLOCKED; |
| |
| vol->flags |= HFS_VOL_MOUNTED; |
| |
| return 0; |
| |
| fail: |
| return -1; |
| } |
| |
| /* |
| * NAME: vol->catsearch() |
| * DESCRIPTION: search catalog tree |
| */ |
| int v_catsearch(hfsvol *vol, unsigned long parid, const char *name, |
| CatDataRec *data, char *cname, node *np) |
| { |
| CatKeyRec key; |
| byte pkey[HFS_CATKEYLEN]; |
| const byte *ptr; |
| node n; |
| int found; |
| |
| if (np == NULL) |
| np = &n; |
| |
| r_makecatkey(&key, parid, name); |
| r_packcatkey(&key, pkey, NULL); |
| |
| 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(hfsfile *file, unsigned int fabn, |
| ExtDataRec *data, node *np) |
| { |
| ExtKeyRec key; |
| ExtDataRec extsave; |
| unsigned int fabnsave; |
| byte pkey[HFS_EXTKEYLEN]; |
| const byte *ptr; |
| node n; |
| int found; |
| |
| if (np == NULL) |
| np = &n; |
| |
| r_makeextkey(&key, file->fork, file->cat.u.fil.filFlNum, fabn); |
| r_packextkey(&key, pkey, NULL); |
| |
| /* 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(hfsvol *vol, unsigned long id, |
| CatDataRec *thread, node *np, int type) |
| { |
| CatDataRec rec; |
| int found; |
| |
| if (thread == NULL) |
| thread = &rec; |
| |
| found = v_catsearch(vol, id, "", thread, NULL, np); |
| if (found == 1 && thread->cdrType != type) |
| ERROR(EIO, "bad thread record"); |
| |
| return found; |
| |
| fail: |
| return -1; |
| } |
| |
| |
| /* |
| * NAME: vol->resolve() |
| * DESCRIPTION: translate a pathname; return catalog information |
| */ |
| int v_resolve(hfsvol **vol, const char *path, |
| CatDataRec *data, unsigned long *parid, char *fname, node *np) |
| { |
| unsigned long dirid; |
| char name[HFS_MAX_FLEN + 1], *nptr; |
| int found = 0; |
| |
| if (*path == 0) |
| ERROR(ENOENT, "empty path"); |
| |
| if (parid) |
| *parid = 0; |
| |
| nptr = strchr(path, ':'); |
| |
| if (*path == ':' || nptr == NULL) |
| { |
| dirid = (*vol)->cwd; /* relative path */ |
| |
| if (*path == ':') |
| ++path; |
| |
| if (*path == 0) |
| { |
| found = v_getdthread(*vol, dirid, data, NULL); |
| if (found == -1) |
| goto fail; |
| |
| if (found) |
| { |
| if (parid) |
| *parid = data->u.dthd.thdParID; |
| |
| found = v_catsearch(*vol, data->u.dthd.thdParID, |
| data->u.dthd.thdCName, data, fname, np); |
| if (found == -1) |
| goto fail; |
| } |
| |
| goto done; |
| } |
| } |
| else |
| { |
| hfsvol *check; |
| |
| dirid = HFS_CNID_ROOTPAR; /* absolute path */ |
| |
| if (nptr - path > HFS_MAX_VLEN) |
| ERROR(ENAMETOOLONG, NULL); |
| |
| 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; |
| } |
| } |
| } |
| |
| while (1) |
| { |
| while (*path == ':') |
| { |
| ++path; |
| |
| found = v_getdthread(*vol, dirid, data, NULL); |
| if (found == -1) |
| goto fail; |
| else if (! found) |
| goto done; |
| |
| dirid = data->u.dthd.thdParID; |
| } |
| |
| if (*path == 0) |
| { |
| found = v_getdthread(*vol, dirid, data, NULL); |
| if (found == -1) |
| goto fail; |
| |
| if (found) |
| { |
| if (parid) |
| *parid = data->u.dthd.thdParID; |
| |
| found = v_catsearch(*vol, data->u.dthd.thdParID, |
| data->u.dthd.thdCName, data, fname, np); |
| if (found == -1) |
| goto fail; |
| } |
| |
| goto done; |
| } |
| |
| nptr = name; |
| while (nptr < name + sizeof(name) - 1 && *path && *path != ':') |
| *nptr++ = *path++; |
| |
| if (*path && *path != ':') |
| ERROR(ENAMETOOLONG, NULL); |
| |
| *nptr = 0; |
| if (*path == ':') |
| ++path; |
| |
| if (parid) |
| *parid = dirid; |
| |
| found = v_catsearch(*vol, dirid, name, data, fname, np); |
| if (found == -1) |
| goto fail; |
| |
| if (! found) |
| { |
| if (*path && parid) |
| *parid = 0; |
| |
| if (*path == 0 && fname) |
| strcpy(fname, name); |
| |
| goto done; |
| } |
| |
| switch (data->cdrType) |
| { |
| case cdrDirRec: |
| if (*path == 0) |
| goto done; |
| |
| dirid = data->u.dir.dirDirID; |
| break; |
| |
| case cdrFilRec: |
| if (*path == 0) |
| goto done; |
| |
| ERROR(ENOTDIR, "invalid pathname"); |
| |
| default: |
| ERROR(EIO, "unexpected catalog record"); |
| } |
| } |
| |
| done: |
| return found; |
| |
| fail: |
| return -1; |
| } |
| |
| /* Determine whether the volume is a HFS volume */ |
| int |
| v_probe(int fd, long long offset) |
| { |
| MDB *mdb; |
| |
| mdb = (MDB*)malloc(2 * 512); |
| os_seek_offset( fd, 2 * 512 + offset ); |
| os_read(fd, mdb, 2, 9); |
| |
| if (__be16_to_cpu(mdb->drSigWord) != HFS_SIGWORD) { |
| free(mdb); |
| return 0; |
| } |
| |
| free(mdb); |
| return -1; |
| } |