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