2 * libhfs - library for reading and writing Macintosh HFS volumes
3 * Copyright (C) 1996-1998 Robert Leslie
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston,
20 * $Id: block.c,v 1.11 1998/11/02 22:08:52 rob Exp $
30 #define INUSE(b) ((b)->flags & HFS_BUCKET_INUSE)
31 #define DIRTY(b) ((b)->flags & HFS_BUCKET_DIRTY)
35 * DESCRIPTION: initialize a volume's block cache
37 int b_init(hfsvol *vol)
42 ASSERT(vol->cache == 0);
44 cache = ALLOC(bcache, 1);
51 cache->tail = &cache->chain[HFS_CACHESZ - 1];
56 for (i = 0; i < HFS_CACHESZ; ++i)
58 bucket *b = &cache->chain[i];
64 b->data = &cache->pool[i];
73 cache->chain[0].cprev = cache->tail;
74 cache->tail->cnext = &cache->chain[0];
76 for (i = 0; i < HFS_HASHSZ; ++i)
77 cache->hash[i] = NULL;
87 * NAME: block->showstats()
88 * DESCRIPTION: output cache hit/miss ratio
90 void b_showstats(const bcache *cache)
92 fprintf(stderr, "BLOCK: CACHE vol 0x%lx \"%s\" hit/miss ratio = %.3f\n",
93 (unsigned long) cache->vol, cache->vol->mdb.drVN,
94 (float) cache->hits / (float) cache->misses);
98 * NAME: block->dumpcache()
99 * DESCRIPTION: dump the cache tables for a volume
101 void b_dumpcache(const bcache *cache)
106 fprintf(stderr, "BLOCK CACHE DUMP:\n");
108 for (i = 0, b = cache->tail->cnext; i < HFS_CACHESZ; ++i, b = b->cnext)
112 fprintf(stderr, "\t %lu", b->bnum);
114 fprintf(stderr, "*");
116 fprintf(stderr, ":%u", b->count);
120 fprintf(stderr, "\n");
122 fprintf(stderr, "BLOCK HASH DUMP:\n");
124 for (i = 0; i < HFS_HASHSZ; ++i)
128 for (b = cache->hash[i]; b; b = b->hnext)
131 fprintf(stderr, " %d:", i);
135 fprintf(stderr, " %lu", b->bnum);
137 fprintf(stderr, "*");
139 fprintf(stderr, ":%u", b->count);
146 fprintf(stderr, "\n");
153 * DESCRIPTION: fill a chain of bucket buffers with a single read
156 int fillchain(hfsvol *vol, bucket **bptr, unsigned int *count)
158 bucket *blist[HFS_BLOCKBUFSZ], **start = bptr;
159 unsigned long bnum=-2; // XXX
162 for (len = 0; len < HFS_BLOCKBUFSZ &&
163 (unsigned int) (bptr - start) < *count; ++bptr)
168 if (len > 0 && (*bptr)->bnum != bnum)
171 blist[len++] = *bptr;
172 bnum = (*bptr)->bnum + 1;
175 *count = bptr - start;
181 if (b_readpb(vol, vol->vstart + blist[0]->bnum,
182 blist[0]->data, 1) == -1)
187 block buffer[HFS_BLOCKBUFSZ];
189 if (b_readpb(vol, vol->vstart + blist[0]->bnum, buffer, len) == -1)
192 for (i = 0; i < len; ++i)
193 memcpy(blist[i]->data, buffer[i], HFS_BLOCKSZ);
196 for (i = 0; i < len; ++i)
198 blist[i]->flags |= HFS_BUCKET_INUSE;
199 blist[i]->flags &= ~HFS_BUCKET_DIRTY;
212 * DESCRIPTION: comparison function for qsort of cache bucket pointers
215 int compare(const bucket **b1, const bucket **b2)
219 diff = (*b1)->bnum - (*b2)->bnum;
231 * DESCRIPTION: fill or flush an array of cache buckets to a volume
234 int dobuckets(hfsvol *vol, bucket **chain, unsigned int len,
235 int (*func)(hfsvol *, bucket **, unsigned int *))
237 unsigned int count, i;
240 qsort(chain, len, sizeof(*chain),
241 (int (*)(const void *, const void *)) compare);
243 for (i = 0; i < len; i += count)
246 if (func(vol, chain + i, &count) == -1)
253 # define fillbuckets(vol, chain, len) dobuckets(vol, chain, len, fillchain)
256 * NAME: block->finish()
257 * DESCRIPTION: commit and free a volume's block cache
259 int b_finish(hfsvol *vol)
263 if (vol->cache == NULL)
267 b_dumpcache(vol->cache);
279 * DESCRIPTION: locate a bucket in the cache, and/or its hash slot
282 bucket *findbucket(bcache *cache, unsigned long bnum, bucket ***hslot)
286 *hslot = &cache->hash[bnum & (HFS_HASHSZ - 1)];
288 for (b = **hslot; b; b = b->hnext)
290 if (INUSE(b) && b->bnum == bnum)
299 * DESCRIPTION: free a bucket for reuse, flushing if necessary
302 int reuse(bcache *cache, bucket *b, unsigned long bnum)
309 fprintf(stderr, "BLOCK: CACHE reusing bucket containing "
310 "vol 0x%lx block %lu:%u\n",
311 (unsigned long) cache->vol, b->bnum, b->count);
314 if (INUSE(b) && DIRTY(b))
316 /* flush most recently unused buckets */
318 for (bptr = b, i = 0; i < HFS_BLOCKBUFSZ; ++i)
324 b->flags &= ~HFS_BUCKET_INUSE;
333 * DESCRIPTION: move a bucket to an appropriate place near head of the chain
336 void cplace(bcache *cache, bucket *b)
340 for (p = cache->tail->cnext; p->count > 1; p = p->cnext)
343 b->cnext->cprev = b->cprev;
344 b->cprev->cnext = b->cnext;
346 if (cache->tail == b)
347 cache->tail = b->cprev;
358 * DESCRIPTION: move a bucket to the head of its hash slot
361 void hplace(bucket **hslot, bucket *b)
366 *b->hprev = b->hnext;
368 b->hnext->hprev = b->hprev;
374 (*hslot)->hprev = &b->hnext;
382 * DESCRIPTION: fetch a bucket from the cache, or an empty one to be filled
385 bucket *getbucket(bcache *cache, unsigned long bnum, int fill)
387 bucket **hslot, *b, *p, *bptr,
388 *chain[HFS_BLOCKBUFSZ], **slots[HFS_BLOCKBUFSZ];
390 b = findbucket(cache, bnum, &hslot);
394 /* cache hit; move towards head of cache chain */
398 if (++b->count > b->cprev->count &&
399 b != cache->tail->cnext)
412 if (cache->tail == b)
418 /* cache miss; reuse least-used cache bucket */
424 if (reuse(cache, b, bnum) == -1)
429 unsigned int len = 0;
432 slots[len++] = hslot;
434 for (bptr = b->cprev;
435 len < (HFS_BLOCKBUFSZ >> 1) && ++bnum < cache->vol->vlen;
438 if (findbucket(cache, bnum, &hslot))
441 if (reuse(cache, bptr, bnum) == -1)
445 slots[len++] = hslot;
448 if (fillbuckets(cache->vol, chain, len) == -1)
453 cplace(cache, chain[len]);
454 hplace(slots[len], chain[len]);
460 /* move bucket to appropriate place in chain */
465 /* insert at front of hash chain */
476 * NAME: block->readpb()
477 * DESCRIPTION: read blocks from the physical medium (bypassing cache)
479 int b_readpb(hfsvol *vol, unsigned long bnum, block *bp, unsigned int blen)
481 unsigned long nblocks;
484 fprintf(stderr, "BLOCK: READ vol 0x%lx block %lu",
485 (unsigned long) vol, bnum);
487 fprintf(stderr, "+%u[..%lu]\n", blen - 1, bnum + blen - 1);
489 fprintf(stderr, "\n");
492 nblocks = os_seek(vol->os_fd, bnum, HFS_BLOCKSZ_BITS );
493 if (nblocks == (unsigned long) -1)
497 ERROR(EIO, "block seek failed for read");
499 nblocks = os_read(vol->os_fd, bp, blen, HFS_BLOCKSZ_BITS);
500 if (nblocks == (unsigned long) -1)
504 ERROR(EIO, "incomplete block read");
514 * NAME: block->readlb()
515 * DESCRIPTION: read a logical block from a volume (or from the cache)
517 int b_readlb(hfsvol *vol, unsigned long bnum, block *bp)
519 if (vol->vlen > 0 && bnum >= vol->vlen)
520 ERROR(EIO, "read nonexistent logical block");
526 b = getbucket(vol->cache, bnum, 1);
530 memcpy(bp, b->data, HFS_BLOCKSZ);
534 if (b_readpb(vol, vol->vstart + bnum, bp, 1) == -1)
545 * NAME: block->readab()
546 * DESCRIPTION: read a block from an allocation block from a volume
548 int b_readab(hfsvol *vol, unsigned int anum, unsigned int index, block *bp)
550 /* verify the allocation block exists and is marked as in-use */
552 if (anum >= vol->mdb.drNmAlBlks)
553 ERROR(EIO, "read nonexistent allocation block");
554 else if (vol->vbm && ! BMTST(vol->vbm, anum))
555 ERROR(EIO, "read unallocated block");
557 return b_readlb(vol, vol->mdb.drAlBlSt + anum * vol->lpa + index, bp);
565 * NAME: block->size()
566 * DESCRIPTION: return the number of physical blocks on a volume's medium
568 unsigned long b_size(hfsvol *vol)
570 unsigned long low, high, mid;
573 high = os_seek(vol->os_fd, -1, HFS_BLOCKSZ_BITS);
575 if (high != (unsigned long) -1 && high > 0)
578 /* manual size detection: first check there is at least 1 block in medium */
580 if (b_readpb(vol, 0, &b, 1) == -1)
581 ERROR(EIO, "size of medium indeterminable or empty");
583 for (low = 0, high = 2880;
584 high > 0 && b_readpb(vol, high - 1, &b, 1) != -1;
589 ERROR(EIO, "size of medium indeterminable or too large");
591 /* common case: 1440K floppy */
593 if (low == 2879 && b_readpb(vol, 2880, &b, 1) == -1)
596 /* binary search for other sizes */
598 while (low < high - 1)
600 mid = (low + high) >> 1;
602 if (b_readpb(vol, mid, &b, 1) == -1)