1 /*
2 * libhfs - library for reading and writing Macintosh HFS volumes
3 * Copyright (C) 1996-1998 Robert Leslie
4 *
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.
9 *
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.
14 *
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,
18 * MA 02110-1301, USA.
19 *
20 * $Id: hfs.c,v 1.15 1998/11/02 22:09:00 rob Exp $
21 */
22
23 #include "config.h"
24 #include "libhfs.h"
25 #include "data.h"
26 #include "block.h"
27 #include "medium.h"
28 #include "file.h"
29 #include "btree.h"
30 #include "node.h"
31 #include "record.h"
32 #include "volume.h"
33
34 const char *hfs_error = "no error"; /* static error string */
35
36 hfsvol *hfs_mounts; /* linked list of mounted volumes */
37
38 static
39 hfsvol *curvol; /* current volume */
40
41
42 /*
43 * NAME: getvol()
44 * DESCRIPTION: validate a volume reference
45 */
46 static
getvol(hfsvol ** vol)47 int getvol(hfsvol **vol)
48 {
49 if (*vol == NULL)
50 {
51 if (curvol == NULL)
52 ERROR(EINVAL, "no volume is current");
53
54 *vol = curvol;
55 }
56
57 return 0;
58
59 fail:
60 return -1;
61 }
62
63 /* High-Level Volume Routines ============================================== */
64
65 /*
66 * NAME: hfs->mount()
67 * DESCRIPTION: open an HFS volume; return volume descriptor or 0 (error)
68 */
hfs_mount(int os_fd,int pnum)69 hfsvol *hfs_mount( int os_fd, int pnum)
70 {
71 hfsvol *vol, *check;
72 int mode = HFS_MODE_RDONLY;
73
74 /* see if the volume is already mounted */
75 for (check = hfs_mounts; check; check = check->next)
76 {
77 if (check->pnum == pnum && v_same(check, os_fd) == 1)
78 {
79 vol = check;
80 goto done;
81 }
82 }
83
84 vol = ALLOC(hfsvol, 1);
85 if (vol == NULL)
86 ERROR(ENOMEM, NULL);
87
88 v_init(vol, mode);
89
90 vol->flags |= HFS_VOL_READONLY;
91 if( v_open(vol, os_fd) == -1 )
92 goto fail;
93
94 /* mount the volume */
95
96 if (v_geometry(vol, pnum) == -1 ||
97 v_mount(vol) == -1)
98 goto fail;
99
100 /* add to linked list of volumes */
101
102 vol->prev = NULL;
103 vol->next = hfs_mounts;
104
105 if (hfs_mounts)
106 hfs_mounts->prev = vol;
107
108 hfs_mounts = vol;
109
110 done:
111 ++vol->refs;
112 curvol = vol;
113
114 return vol;
115
116 fail:
117 if (vol)
118 {
119 v_close(vol);
120 FREE(vol);
121 }
122
123 return NULL;
124 }
125
126
127 /*
128 * NAME: hfs->umount()
129 * DESCRIPTION: close an HFS volume
130 */
hfs_umount(hfsvol * vol)131 int hfs_umount(hfsvol *vol)
132 {
133 int result = 0;
134
135 if (getvol(&vol) == -1)
136 goto fail;
137
138 if (--vol->refs)
139 {
140 goto done;
141 }
142
143 /* close all open files and directories */
144
145 while (vol->files)
146 {
147 if (hfs_close(vol->files) == -1)
148 result = -1;
149 }
150
151 while (vol->dirs)
152 {
153 if (hfs_closedir(vol->dirs) == -1)
154 result = -1;
155 }
156
157 /* close medium */
158
159 if (v_close(vol) == -1)
160 result = -1;
161
162 /* remove from linked list of volumes */
163
164 if (vol->prev)
165 vol->prev->next = vol->next;
166 if (vol->next)
167 vol->next->prev = vol->prev;
168
169 if (vol == hfs_mounts)
170 hfs_mounts = vol->next;
171 if (vol == curvol)
172 curvol = NULL;
173
174 FREE(vol);
175
176 done:
177 return result;
178
179 fail:
180 return -1;
181 }
182
183 /*
184 * NAME: hfs->umountall()
185 * DESCRIPTION: unmount all mounted volumes
186 */
hfs_umountall(void)187 void hfs_umountall(void)
188 {
189 while (hfs_mounts)
190 hfs_umount(hfs_mounts);
191 }
192
193 /*
194 * NAME: hfs->getvol()
195 * DESCRIPTION: return a pointer to a mounted volume
196 */
hfs_getvol(const char * name)197 hfsvol *hfs_getvol(const char *name)
198 {
199 hfsvol *vol;
200
201 if (name == NULL)
202 return curvol;
203
204 for (vol = hfs_mounts; vol; vol = vol->next)
205 {
206 if (d_relstring(name, vol->mdb.drVN) == 0)
207 return vol;
208 }
209
210 return NULL;
211 }
212
213 /*
214 * NAME: hfs->setvol()
215 * DESCRIPTION: change the current volume
216 */
hfs_setvol(hfsvol * vol)217 void hfs_setvol(hfsvol *vol)
218 {
219 curvol = vol;
220 }
221
222 /*
223 * NAME: hfs->vstat()
224 * DESCRIPTION: return volume statistics
225 */
hfs_vstat(hfsvol * vol,hfsvolent * ent)226 int hfs_vstat(hfsvol *vol, hfsvolent *ent)
227 {
228 if (getvol(&vol) == -1)
229 goto fail;
230
231 strcpy(ent->name, vol->mdb.drVN);
232
233 ent->flags = (vol->flags & HFS_VOL_READONLY) ? HFS_ISLOCKED : 0;
234
235 ent->totbytes = vol->mdb.drNmAlBlks * vol->mdb.drAlBlkSiz;
236 ent->freebytes = vol->mdb.drFreeBks * vol->mdb.drAlBlkSiz;
237
238 ent->alblocksz = vol->mdb.drAlBlkSiz;
239 ent->clumpsz = vol->mdb.drClpSiz;
240
241 ent->numfiles = vol->mdb.drFilCnt;
242 ent->numdirs = vol->mdb.drDirCnt;
243
244 ent->crdate = d_ltime(vol->mdb.drCrDate);
245 ent->mddate = d_ltime(vol->mdb.drLsMod);
246 ent->bkdate = d_ltime(vol->mdb.drVolBkUp);
247
248 ent->blessed = vol->mdb.drFndrInfo[0];
249
250 return 0;
251
252 fail:
253 return -1;
254 }
255
256
257 /* High-Level Directory Routines =========================================== */
258
259 /*
260 * NAME: hfs->chdir()
261 * DESCRIPTION: change current HFS directory
262 */
hfs_chdir(hfsvol * vol,const char * path)263 int hfs_chdir(hfsvol *vol, const char *path)
264 {
265 CatDataRec data;
266
267 if (getvol(&vol) == -1 ||
268 v_resolve(&vol, path, &data, NULL, NULL, NULL) <= 0)
269 goto fail;
270
271 if (data.cdrType != cdrDirRec)
272 ERROR(ENOTDIR, NULL);
273
274 vol->cwd = data.u.dir.dirDirID;
275
276 return 0;
277
278 fail:
279 return -1;
280 }
281
282 /*
283 * NAME: hfs->getcwd()
284 * DESCRIPTION: return the current working directory ID
285 */
hfs_getcwd(hfsvol * vol)286 unsigned long hfs_getcwd(hfsvol *vol)
287 {
288 if (getvol(&vol) == -1)
289 return 0;
290
291 return vol->cwd;
292 }
293
294 /*
295 * NAME: hfs->setcwd()
296 * DESCRIPTION: set the current working directory ID
297 */
hfs_setcwd(hfsvol * vol,unsigned long id)298 int hfs_setcwd(hfsvol *vol, unsigned long id)
299 {
300 if (getvol(&vol) == -1)
301 goto fail;
302
303 if (id == vol->cwd)
304 goto done;
305
306 /* make sure the directory exists */
307
308 if (v_getdthread(vol, id, NULL, NULL) <= 0)
309 goto fail;
310
311 vol->cwd = id;
312
313 done:
314 return 0;
315
316 fail:
317 return -1;
318 }
319
320 /*
321 * NAME: hfs->dirinfo()
322 * DESCRIPTION: given a directory ID, return its (name and) parent ID
323 */
hfs_dirinfo(hfsvol * vol,unsigned long * id,char * name)324 int hfs_dirinfo(hfsvol *vol, unsigned long *id, char *name)
325 {
326 CatDataRec thread;
327
328 if (getvol(&vol) == -1 ||
329 v_getdthread(vol, *id, &thread, NULL) <= 0)
330 goto fail;
331
332 *id = thread.u.dthd.thdParID;
333
334 if (name)
335 strcpy(name, thread.u.dthd.thdCName);
336
337 return 0;
338
339 fail:
340 return -1;
341 }
342
343 /*
344 * NAME: hfs->opendir()
345 * DESCRIPTION: prepare to read the contents of a directory
346 */
hfs_opendir(hfsvol * vol,const char * path)347 hfsdir *hfs_opendir(hfsvol *vol, const char *path)
348 {
349 hfsdir *dir = NULL;
350 CatKeyRec key;
351 CatDataRec data;
352 byte pkey[HFS_CATKEYLEN];
353
354 if (getvol(&vol) == -1)
355 goto fail;
356
357 dir = ALLOC(hfsdir, 1);
358 if (dir == NULL)
359 ERROR(ENOMEM, NULL);
360
361 dir->vol = vol;
362
363 if (*path == 0)
364 {
365 /* meta-directory containing root dirs from all mounted volumes */
366
367 dir->dirid = 0;
368 dir->vptr = hfs_mounts;
369 }
370 else
371 {
372 if (v_resolve(&vol, path, &data, NULL, NULL, NULL) <= 0)
373 goto fail;
374
375 if (data.cdrType != cdrDirRec)
376 ERROR(ENOTDIR, NULL);
377
378 dir->dirid = data.u.dir.dirDirID;
379 dir->vptr = NULL;
380
381 r_makecatkey(&key, dir->dirid, "");
382 r_packcatkey(&key, pkey, NULL);
383
384 if (bt_search(&vol->cat, pkey, &dir->n) <= 0)
385 goto fail;
386 }
387
388 dir->prev = NULL;
389 dir->next = vol->dirs;
390
391 if (vol->dirs)
392 vol->dirs->prev = dir;
393
394 vol->dirs = dir;
395
396 return dir;
397
398 fail:
399 FREE(dir);
400 return NULL;
401 }
402
403 /*
404 * NAME: hfs->readdir()
405 * DESCRIPTION: return the next entry in the directory
406 */
hfs_readdir(hfsdir * dir,hfsdirent * ent)407 int hfs_readdir(hfsdir *dir, hfsdirent *ent)
408 {
409 CatKeyRec key;
410 CatDataRec data;
411 const byte *ptr;
412
413 if (dir->dirid == 0)
414 {
415 hfsvol *vol;
416 char cname[HFS_MAX_FLEN + 1];
417
418 for (vol = hfs_mounts; vol; vol = vol->next)
419 {
420 if (vol == dir->vptr)
421 break;
422 }
423
424 if (vol == NULL)
425 ERROR(ENOENT, "no more entries");
426
427 if (v_getdthread(vol, HFS_CNID_ROOTDIR, &data, NULL) <= 0 ||
428 v_catsearch(vol, HFS_CNID_ROOTPAR, data.u.dthd.thdCName,
429 &data, cname, NULL) <= 0)
430 goto fail;
431
432 r_unpackdirent(HFS_CNID_ROOTPAR, cname, &data, ent);
433
434 dir->vptr = vol->next;
435
436 goto done;
437 }
438
439 if (dir->n.rnum == -1)
440 ERROR(ENOENT, "no more entries");
441
442 while (1)
443 {
444 ++dir->n.rnum;
445
446 while (dir->n.rnum >= dir->n.nd.ndNRecs)
447 {
448 if (dir->n.nd.ndFLink == 0)
449 {
450 dir->n.rnum = -1;
451 ERROR(ENOENT, "no more entries");
452 }
453
454 if (bt_getnode(&dir->n, dir->n.bt, dir->n.nd.ndFLink) == -1)
455 {
456 dir->n.rnum = -1;
457 goto fail;
458 }
459
460 dir->n.rnum = 0;
461 }
462
463 ptr = HFS_NODEREC(dir->n, dir->n.rnum);
464
465 r_unpackcatkey(ptr, &key);
466
467 if (key.ckrParID != dir->dirid)
468 {
469 dir->n.rnum = -1;
470 ERROR(ENOENT, "no more entries");
471 }
472
473 r_unpackcatdata(HFS_RECDATA(ptr), &data);
474
475 switch (data.cdrType)
476 {
477 case cdrDirRec:
478 case cdrFilRec:
479 r_unpackdirent(key.ckrParID, key.ckrCName, &data, ent);
480 goto done;
481
482 case cdrThdRec:
483 case cdrFThdRec:
484 break;
485
486 default:
487 dir->n.rnum = -1;
488 ERROR(EIO, "unexpected directory entry found");
489 }
490 }
491
492 done:
493 return 0;
494
495 fail:
496 return -1;
497 }
498
499 /*
500 * NAME: hfs->closedir()
501 * DESCRIPTION: stop reading a directory
502 */
hfs_closedir(hfsdir * dir)503 int hfs_closedir(hfsdir *dir)
504 {
505 hfsvol *vol = dir->vol;
506
507 if (dir->prev)
508 dir->prev->next = dir->next;
509 if (dir->next)
510 dir->next->prev = dir->prev;
511 if (dir == vol->dirs)
512 vol->dirs = dir->next;
513
514 FREE(dir);
515
516 return 0;
517 }
518
519 /* High-Level File Routines ================================================ */
520
521 /*
522 * NAME: hfs->open()
523 * DESCRIPTION: prepare a file for I/O
524 */
hfs_open(hfsvol * vol,const char * path)525 hfsfile *hfs_open(hfsvol *vol, const char *path)
526 {
527 hfsfile *file = NULL;
528
529 if (getvol(&vol) == -1)
530 goto fail;
531
532 file = ALLOC(hfsfile, 1);
533 if (file == NULL)
534 ERROR(ENOMEM, NULL);
535
536 if (v_resolve(&vol, path, &file->cat, &file->parid, file->name, NULL) <= 0)
537 goto fail;
538
539 if (file->cat.cdrType != cdrFilRec)
540 ERROR(EISDIR, NULL);
541
542 /* package file handle for user */
543
544 file->vol = vol;
545 file->flags = 0;
546
547 f_selectfork(file, fkData);
548
549 file->prev = NULL;
550 file->next = vol->files;
551
552 if (vol->files)
553 vol->files->prev = file;
554
555 vol->files = file;
556
557 return file;
558
559 fail:
560 FREE(file);
561 return NULL;
562 }
563
564 /*
565 * NAME: hfs->setfork()
566 * DESCRIPTION: select file fork for I/O operations
567 */
hfs_setfork(hfsfile * file,int fork)568 int hfs_setfork(hfsfile *file, int fork)
569 {
570 int result = 0;
571
572 f_selectfork(file, fork ? fkRsrc : fkData);
573
574 return result;
575 }
576
577 /*
578 * NAME: hfs->getfork()
579 * DESCRIPTION: return the current fork for I/O operations
580 */
hfs_getfork(hfsfile * file)581 int hfs_getfork(hfsfile *file)
582 {
583 return file->fork != fkData;
584 }
585
586 /*
587 * NAME: hfs->read()
588 * DESCRIPTION: read from an open file
589 */
hfs_read(hfsfile * file,void * buf,unsigned long len)590 unsigned long hfs_read(hfsfile *file, void *buf, unsigned long len)
591 {
592 unsigned long *lglen, count;
593 byte *ptr = buf;
594
595 f_getptrs(file, NULL, &lglen, NULL);
596
597 if (file->pos + len > *lglen)
598 len = *lglen - file->pos;
599
600 count = len;
601 while (count)
602 {
603 unsigned long bnum, offs, chunk;
604
605 bnum = file->pos >> HFS_BLOCKSZ_BITS;
606 offs = file->pos & (HFS_BLOCKSZ - 1);
607
608 chunk = HFS_BLOCKSZ - offs;
609 if (chunk > count)
610 chunk = count;
611
612 if (offs == 0 && chunk == HFS_BLOCKSZ)
613 {
614 if (f_getblock(file, bnum, (block *) ptr) == -1)
615 goto fail;
616 }
617 else
618 {
619 block b;
620
621 if (f_getblock(file, bnum, &b) == -1)
622 goto fail;
623
624 memcpy(ptr, b + offs, chunk);
625 }
626
627 ptr += chunk;
628
629 file->pos += chunk;
630 count -= chunk;
631 }
632
633 return len;
634
635 fail:
636 return -1;
637 }
638
639 /*
640 * NAME: hfs->seek()
641 * DESCRIPTION: change file seek pointer
642 */
hfs_seek(hfsfile * file,long offset,int from)643 unsigned long hfs_seek(hfsfile *file, long offset, int from)
644 {
645 unsigned long *lglen, newpos;
646
647 f_getptrs(file, NULL, &lglen, NULL);
648
649 switch (from)
650 {
651 case HFS_SEEK_SET:
652 newpos = (offset < 0) ? 0 : offset;
653 break;
654
655 case HFS_SEEK_CUR:
656 if (offset < 0 && (unsigned long) -offset > file->pos)
657 newpos = 0;
658 else
659 newpos = file->pos + offset;
660 break;
661
662 case HFS_SEEK_END:
663 if (offset < 0 && (unsigned long) -offset > *lglen)
664 newpos = 0;
665 else
666 newpos = *lglen + offset;
667 break;
668
669 default:
670 ERROR(EINVAL, NULL);
671 }
672
673 if (newpos > *lglen)
674 newpos = *lglen;
675
676 file->pos = newpos;
677
678 return newpos;
679
680 fail:
681 return -1;
682 }
683
684 /*
685 * NAME: hfs->close()
686 * DESCRIPTION: close a file
687 */
hfs_close(hfsfile * file)688 int hfs_close(hfsfile *file)
689 {
690 hfsvol *vol = file->vol;
691 int result = 0;
692
693 if (file->prev)
694 file->prev->next = file->next;
695 if (file->next)
696 file->next->prev = file->prev;
697 if (file == vol->files)
698 vol->files = file->next;
699
700 FREE(file);
701
702 return result;
703 }
704
705 /* High-Level Catalog Routines ============================================= */
706
707 /*
708 * NAME: hfs->stat()
709 * DESCRIPTION: return catalog information for an arbitrary path
710 */
hfs_stat(hfsvol * vol,const char * path,hfsdirent * ent)711 int hfs_stat(hfsvol *vol, const char *path, hfsdirent *ent)
712 {
713 CatDataRec data;
714 unsigned long parid;
715 char name[HFS_MAX_FLEN + 1];
716
717 if (getvol(&vol) == -1 ||
718 v_resolve(&vol, path, &data, &parid, name, NULL) <= 0)
719 goto fail;
720
721 r_unpackdirent(parid, name, &data, ent);
722
723 return 0;
724
725 fail:
726 return -1;
727 }
728
729 /*
730 * NAME: hfs->fstat()
731 * DESCRIPTION: return catalog information for an open file
732 */
hfs_fstat(hfsfile * file,hfsdirent * ent)733 int hfs_fstat(hfsfile *file, hfsdirent *ent)
734 {
735 r_unpackdirent(file->parid, file->name, &file->cat, ent);
736
737 return 0;
738 }
739
740 /*
741 * NAME: hfs->probe()
742 * DESCRIPTION: return whether a HFS filesystem is present at the given offset
743 */
hfs_probe(int fd,long long offset)744 int hfs_probe(int fd, long long offset)
745 {
746 return v_probe(fd, offset);
747 }
748