| /* |
| * 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); |
| } |