1 /*
2  * This file has been modified for the cdrkit suite.
3  *
4  * The behaviour and appearence of the program code below can differ to a major
5  * extent from the version distributed by the original author(s).
6  *
7  * For details, see Changelog file distributed with the cdrkit package. If you
8  * received this file from another source then ask the distributing person for
9  * a log of modifications.
10  *
11  */
12 
13 /* @(#)volume.c	1.4 04/06/17 joerg */
14 /*
15  * hfsutils - tools for reading and writing Macintosh HFS volumes
16  * Copyright (C) 1996, 1997 Robert Leslie
17  *
18  * This program is free software; you can redistribute it and/or modify
19  * it under the terms of the GNU General Public License as published by
20  * the Free Software Foundation; either version 2 of the License, or
21  * (at your option) any later version.
22  *
23  * This program is distributed in the hope that it will be useful,
24  * but WITHOUT ANY WARRANTY; without even the implied warranty of
25  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
26  * GNU General Public License for more details.
27  *
28  * You should have received a copy of the GNU General Public License
29  * along with this program; if not, write to the Free Software
30  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
31  */
32 
33 #include <mconfig.h>
34 #include <stdxlib.h>
35 #include <strdefs.h>
36 #include <errno.h>
37 
38 #include "internal.h"
39 #include "data.h"
40 #include "low.h"
41 #include "btree.h"
42 #include "record.h"
43 #include "volume.h"
44 
45 static	void	markexts(block *vbm, ExtDataRec *exts);
46 
47 
48 /*
49  * NAME:	vol->catsearch()
50  * DESCRIPTION:	search catalog tree
51  */
v_catsearch(hfsvol * vol,long parid,char * name,CatDataRec * data,char * cname,node * np)52 int v_catsearch(hfsvol *vol, long parid, char *name, CatDataRec *data,
53                 char *cname, node *np)
54 {
55   CatKeyRec key;
56   unsigned char pkey[HFS_CATKEYLEN];
57   node n;
58   unsigned char *ptr;
59   int found;
60 
61   if (np == 0)
62     np = &n;
63 
64   r_makecatkey(&key, parid, name);
65   r_packcatkey(&key, pkey, 0);
66 
67   found = bt_search(&vol->cat, pkey, np);
68   if (found <= 0)
69     return found;
70 
71   ptr = HFS_NODEREC(*np, np->rnum);
72 
73   if (cname)
74     {
75       r_unpackcatkey(ptr, &key);
76       strcpy(cname, key.ckrCName);
77     }
78 
79   if (data)
80     r_unpackcatdata(HFS_RECDATA(ptr), data);
81 
82   return 1;
83 }
84 
85 /*
86  * NAME:	vol->extsearch()
87  * DESCRIPTION:	search extents tree
88  */
v_extsearch(hfsfile * file,unsigned int fabn,ExtDataRec * data,node * np)89 int v_extsearch(hfsfile *file, unsigned int fabn, ExtDataRec *data, node *np)
90 {
91   ExtKeyRec key;
92   ExtDataRec extsave;
93   unsigned int fabnsave;
94   unsigned char pkey[HFS_EXTKEYLEN];
95   node n;
96   unsigned char *ptr;
97   int found;
98 
99   if (np == 0)
100     np = &n;
101 
102   r_makeextkey(&key, file->fork, file->cat.u.fil.filFlNum, fabn);
103   r_packextkey(&key, pkey, 0);
104 
105   /* in case bt_search() clobbers these */
106 
107   memcpy(&extsave, &file->ext, sizeof(ExtDataRec));
108   fabnsave = file->fabn;
109 
110   found = bt_search(&file->vol->ext, pkey, np);
111 
112   memcpy(&file->ext, &extsave, sizeof(ExtDataRec));
113   file->fabn = fabnsave;
114 
115   if (found <= 0)
116     return found;
117 
118   if (data)
119     {
120       ptr = HFS_NODEREC(*np, np->rnum);
121       r_unpackextdata(HFS_RECDATA(ptr), data);
122     }
123 
124   return 1;
125 }
126 
127 /*
128  * NAME:	vol->getthread()
129  * DESCRIPTION:	retrieve catalog thread information for a file or directory
130  */
v_getthread(hfsvol * vol,long id,CatDataRec * thread,node * np,int type)131 int v_getthread(hfsvol *vol, long id, CatDataRec *thread, node *np, int type)
132 {
133   CatDataRec rec;
134   int found;
135 
136   if (thread == 0)
137     thread = &rec;
138 
139   found = v_catsearch(vol, id, "", thread, 0, np);
140   if (found <= 0)
141     return found;
142 
143   if (thread->cdrType != type)
144     {
145       ERROR(EIO, "bad thread record");
146       return -1;
147     }
148 
149   return 1;
150 }
151 
152 /*
153  * NAME:	vol->putcatrec()
154  * DESCRIPTION:	store catalog information
155  */
v_putcatrec(CatDataRec * data,node * np)156 int v_putcatrec(CatDataRec *data, node *np)
157 {
158   unsigned char pdata[HFS_CATDATALEN], *ptr;
159   int len = 0;
160 
161   r_packcatdata(data, pdata, &len);
162 
163   ptr = HFS_NODEREC(*np, np->rnum);
164   memcpy(HFS_RECDATA(ptr), pdata, len);
165 
166   return bt_putnode(np);
167 }
168 
169 /*
170  * NAME:	vol->putextrec()
171  * DESCRIPTION:	store extent information
172  */
v_putextrec(ExtDataRec * data,node * np)173 int v_putextrec(ExtDataRec *data, node *np)
174 {
175   unsigned char pdata[HFS_EXTDATALEN], *ptr;
176   int len = 0;
177 
178   r_packextdata(data, pdata, &len);
179 
180   ptr = HFS_NODEREC(*np, np->rnum);
181   memcpy(HFS_RECDATA(ptr), pdata, len);
182 
183   return bt_putnode(np);
184 }
185 
186 /*
187  * NAME:	vol->allocblocks()
188  * DESCRIPTION:	allocate a contiguous range of blocks
189  */
v_allocblocks(hfsvol * vol,ExtDescriptor * blocks)190 int v_allocblocks(hfsvol *vol, ExtDescriptor *blocks)
191 {
192   unsigned int request, found, foundat, start, end, pt;
193   block *vbm;
194   int wrap = 0;
195 
196   if (vol->mdb.drFreeBks == 0)
197     {
198       ERROR(ENOSPC, "volume full");
199       return -1;
200     }
201 
202   request = blocks->xdrNumABlks;
203   found   = 0;
204   foundat = 0;
205   start   = vol->mdb.drAllocPtr;
206   end     = vol->mdb.drNmAlBlks;
207   pt      = start;
208   vbm     = vol->vbm;
209 
210   if (request == 0)
211     abort();
212 
213   for (;;)
214     {
215       unsigned int mark;
216 
217       /* skip blocks in use */
218 
219       while (pt < end && BMTST(vbm, pt))
220 	++pt;
221 
222       if (wrap && pt >= start)
223 	break;
224 
225       /* count blocks not in use */
226 
227       mark = pt;
228       while (pt < end && pt - mark < request && ! BMTST(vbm, pt))
229 	++pt;
230 
231       if (pt - mark > found)
232 	{
233 	  found   = pt - mark;
234 	  foundat = mark;
235 	}
236 
237       if (pt == end)
238 	pt = 0, wrap = 1;
239 
240       if (found == request)
241 	break;
242     }
243 
244   if (found == 0 || found > vol->mdb.drFreeBks)
245     {
246       ERROR(EIO, "bad volume bitmap or free block count");
247       return -1;
248     }
249 
250   blocks->xdrStABN    = foundat;
251   blocks->xdrNumABlks = found;
252 
253   vol->mdb.drAllocPtr = pt;
254   vol->mdb.drFreeBks -= found;
255 
256   for (pt = foundat; pt < foundat + found; ++pt)
257     BMSET(vbm, pt);
258 
259   vol->flags |= HFS_UPDATE_MDB | HFS_UPDATE_VBM;
260 
261   return 0;
262 }
263 
264 /*
265  * NAME:	vol->freeblocks()
266  * DESCRIPTION:	deallocate a contiguous range of blocks
267  */
v_freeblocks(hfsvol * vol,ExtDescriptor * blocks)268 void v_freeblocks(hfsvol *vol, ExtDescriptor *blocks)
269 {
270   unsigned int start, len, pt;
271   block *vbm;
272 
273   start = blocks->xdrStABN;
274   len   = blocks->xdrNumABlks;
275   vbm   = vol->vbm;
276 
277   vol->mdb.drFreeBks += len;
278 
279   for (pt = start; pt < start + len; ++pt)
280     BMCLR(vbm, pt);
281 
282   vol->flags |= HFS_UPDATE_MDB | HFS_UPDATE_VBM;
283 }
284 
285 /*
286  * NAME:	vol->resolve()
287  * DESCRIPTION:	translate a pathname; return catalog information
288  */
v_resolve(hfsvol ** vol,char * path,CatDataRec * data,long * parid,char * fname,node * np)289 int v_resolve(hfsvol **vol, char *path, CatDataRec *data, long *parid,
290               char *fname, node *np)
291 {
292   long dirid;
293   char name[HFS_MAX_FLEN + 1], *nptr;
294   int found;
295 
296   if (*path == 0)
297     {
298       ERROR(ENOENT, "empty path");
299       return -1;
300     }
301 
302   if (parid)
303     *parid = 0;
304 
305   nptr = strchr(path, ':');
306 
307   if (*path == ':' || nptr == 0)
308     {
309       dirid = (*vol)->cwd;  /* relative path */
310 
311       if (*path == ':')
312 	++path;
313 
314       if (*path == 0)
315 	{
316 	  found = v_getdthread(*vol, dirid, data, 0);
317 	  if (found <= 0)
318 	    return found;
319 
320 	  if (parid)
321 	    *parid = data->u.dthd.thdParID;
322 
323 	  return v_catsearch(*vol, data->u.dthd.thdParID,
324 			     data->u.dthd.thdCName, data, fname, np);
325 	}
326     }
327   else
328     {
329       hfsvol *check;
330 
331       dirid = HFS_CNID_ROOTPAR;  /* absolute path */
332 
333       if (nptr - path > HFS_MAX_VLEN)
334 	{
335 	  ERROR(ENAMETOOLONG, 0);
336 	  return -1;
337 	}
338 
339       strncpy(name, path, nptr - path);
340       name[nptr - path] = 0;
341 
342       for (check = hfs_mounts; check; check = check->next)
343 	{
344 	  if (d_relstring(check->mdb.drVN, name) == 0)
345 	    {
346 	      *vol = check;
347 	      break;
348 	    }
349 	}
350     }
351 
352   for (;;)
353     {
354       while (*path == ':')
355 	{
356 	  ++path;
357 
358 	  found = v_getdthread(*vol, dirid, data, 0);
359 	  if (found <= 0)
360 	    return found;
361 
362 	  dirid = data->u.dthd.thdParID;
363 	}
364 
365       if (*path == 0)
366 	{
367 	  found = v_getdthread(*vol, dirid, data, 0);
368 	  if (found <= 0)
369 	    return found;
370 
371 	  if (parid)
372 	    *parid = data->u.dthd.thdParID;
373 
374 	  return v_catsearch(*vol, data->u.dthd.thdParID,
375 			     data->u.dthd.thdCName, data, fname, np);
376 	}
377 
378       nptr = name;
379       while (nptr < name + sizeof(name) - 1 && *path && *path != ':')
380 	*nptr++ = *path++;
381 
382       if (*path && *path != ':')
383 	{
384 	  ERROR(ENAMETOOLONG, 0);
385 	  return -1;
386 	}
387 
388       *nptr = 0;
389       if (*path == ':')
390 	++path;
391 
392       if (parid)
393 	*parid = dirid;
394 
395       found = v_catsearch(*vol, dirid, name, data, fname, np);
396       if (found < 0)
397 	return -1;
398 
399       if (found == 0)
400 	{
401 	  if (*path && parid)
402 	    *parid = 0;
403 
404 	  if (*path == 0 && fname)
405 	    strcpy(fname, name);
406 
407 	  return 0;
408 	}
409 
410       switch (data->cdrType)
411 	{
412 	case cdrDirRec:
413 	  if (*path == 0)
414 	    return 1;
415 
416 	  dirid = data->u.dir.dirDirID;
417 	  break;
418 
419 	case cdrFilRec:
420 	  if (*path == 0)
421 	    return 1;
422 
423 	  ERROR(ENOTDIR, "invalid pathname");
424 	  return -1;
425 
426 	default:
427 	  ERROR(EIO, "unexpected catalog record");
428 	  return -1;
429 	}
430     }
431 }
432 
433 /*
434  * NAME:	vol->destruct()
435  * DESCRIPTION:	free memory consumed by a volume descriptor
436  */
v_destruct(hfsvol * vol)437 void v_destruct(hfsvol *vol)
438 {
439   FREE(vol->vbm);
440 
441   FREE(vol->ext.map);
442   FREE(vol->cat.map);
443 
444   FREE(vol);
445 }
446 
447 /*
448  * NAME:	vol->getvol()
449  * DESCRIPTION:	validate a volume reference
450  */
v_getvol(hfsvol ** vol)451 int v_getvol(hfsvol **vol)
452 {
453   if (*vol == 0)
454     {
455       if (hfs_curvol == 0)
456 	{
457 	  ERROR(EINVAL, "no volume is current");
458 	  return -1;
459 	}
460 
461       *vol = hfs_curvol;
462     }
463 
464   return 0;
465 }
466 
467 /*
468  * NAME:	vol->flush()
469  * DESCRIPTION:	flush all pending changes (B*-tree, MDB, VBM) to disk
470  */
v_flush(hfsvol * vol,int umounting)471 int v_flush(hfsvol *vol, int umounting)
472 {
473   if (! (vol->flags & HFS_READONLY))
474     {
475       if ((vol->ext.flags & HFS_UPDATE_BTHDR) &&
476 	  bt_writehdr(&vol->ext) < 0)
477 	return -1;
478 
479       if ((vol->cat.flags & HFS_UPDATE_BTHDR) &&
480 	  bt_writehdr(&vol->cat) < 0)
481 	return -1;
482 
483       if ((vol->flags & HFS_UPDATE_VBM) &&
484 	  l_writevbm(vol) < 0)
485 	return -1;
486 
487       if (umounting &&
488 	  ! (vol->mdb.drAtrb & HFS_ATRB_UMOUNTED))
489 	{
490 	  vol->mdb.drAtrb |= HFS_ATRB_UMOUNTED;
491 	  vol->flags |= HFS_UPDATE_MDB;
492 	}
493 
494       if ((vol->flags & (HFS_UPDATE_MDB | HFS_UPDATE_ALTMDB)) &&
495 	  l_writemdb(vol) < 0)
496 	return -1;
497     }
498 
499   return 0;
500 }
501 
502 /*
503  * NAME:	vol->adjvalence()
504  * DESCRIPTION:	update a volume's valence counts
505  */
v_adjvalence(hfsvol * vol,long parid,int isdir,int adj)506 int v_adjvalence(hfsvol *vol, long parid, int isdir, int adj)
507 {
508   node n;
509   CatDataRec data;
510 
511   if (isdir)
512     vol->mdb.drDirCnt += adj;
513   else
514     vol->mdb.drFilCnt += adj;
515 
516   vol->flags |= HFS_UPDATE_MDB;
517 
518   if (parid == HFS_CNID_ROOTDIR)
519     {
520       if (isdir)
521 	vol->mdb.drNmRtDirs += adj;
522       else
523 	vol->mdb.drNmFls    += adj;
524     }
525   else if (parid == HFS_CNID_ROOTPAR)
526     return 0;
527 
528   if (v_getdthread(vol, parid, &data, 0) <= 0 ||
529       v_catsearch(vol, data.u.dthd.thdParID, data.u.dthd.thdCName,
530 		  &data, 0, &n) <= 0 ||
531       data.cdrType != cdrDirRec)
532     {
533       ERROR(EIO, "can't find parent directory");
534       return -1;
535     }
536 
537   data.u.dir.dirVal  += adj;
538   data.u.dir.dirMdDat = d_tomtime(time(0));
539 
540   return v_putcatrec(&data, &n);
541 }
542 
543 /*
544  * NAME:	vol->newfolder()
545  * DESCRIPTION:	create a new HFS folder
546  */
v_newfolder(hfsvol * vol,long parid,char * name)547 int v_newfolder(hfsvol *vol, long parid, char *name)
548 {
549   CatKeyRec key;
550   CatDataRec data;
551   long id;
552   unsigned char record[HFS_CATRECMAXLEN];
553   int i, reclen;
554 
555   if (bt_space(&vol->cat, 2) < 0)
556     return -1;
557 
558   id = vol->mdb.drNxtCNID++;
559   vol->flags |= HFS_UPDATE_MDB;
560 
561   /* create directory record */
562 
563   data.cdrType   = cdrDirRec;
564   data.cdrResrv2 = 0;
565 
566   data.u.dir.dirFlags = 0;
567   data.u.dir.dirVal   = 0;
568   data.u.dir.dirDirID = id;
569   data.u.dir.dirCrDat = d_tomtime(time(0));
570   data.u.dir.dirMdDat = data.u.dir.dirCrDat;
571   data.u.dir.dirBkDat = 0;
572 
573   memset(&data.u.dir.dirUsrInfo,  0, sizeof(data.u.dir.dirUsrInfo));
574   memset(&data.u.dir.dirFndrInfo, 0, sizeof(data.u.dir.dirFndrInfo));
575   for (i = 0; i < 4; ++i)
576     data.u.dir.dirResrv[i] = 0;
577 
578   r_makecatkey(&key, parid, name);
579   r_packcatkey(&key, record, &reclen);
580   r_packcatdata(&data, HFS_RECDATA(record), &reclen);
581 
582   if (bt_insert(&vol->cat, record, reclen) < 0)
583     return -1;
584 
585   /* create thread record */
586 
587   data.cdrType   = cdrThdRec;
588   data.cdrResrv2 = 0;
589 
590   data.u.dthd.thdResrv[0] = 0;
591   data.u.dthd.thdResrv[1] = 0;
592   data.u.dthd.thdParID    = parid;
593   strcpy(data.u.dthd.thdCName, name);
594 
595   r_makecatkey(&key, id, "");
596   r_packcatkey(&key, record, &reclen);
597   r_packcatdata(&data, HFS_RECDATA(record), &reclen);
598 
599   if (bt_insert(&vol->cat, record, reclen) < 0 ||
600       v_adjvalence(vol, parid, 1, 1) < 0)
601     return -1;
602 
603   return 0;
604 }
605 
606 /*
607  * NAME:	markexts()
608  * DESCRIPTION:	set bits from an extent record in the volume bitmap
609  */
610 static
markexts(block * vbm,ExtDataRec * exts)611 void markexts(block *vbm, ExtDataRec *exts)
612 {
613   int i;
614   unsigned int start, len;
615 
616   for (i = 0; i < 3; ++i)
617     {
618       for (start = (*exts)[i].xdrStABN,
619 	     len = (*exts)[i].xdrNumABlks; len--; ++start)
620 	BMSET(vbm, start);
621     }
622 }
623 
624 /*
625  * NAME:	vol->scavenge()
626  * DESCRIPTION:	safeguard blocks in the volume bitmap
627  */
v_scavenge(hfsvol * vol)628 int v_scavenge(hfsvol *vol)
629 {
630   block *vbm = vol->vbm;
631   node n;
632   unsigned int pt, blks;
633 
634   if (vbm == 0)
635     return 0;
636 
637   markexts(vbm, &vol->mdb.drXTExtRec);
638   markexts(vbm, &vol->mdb.drCTExtRec);
639 
640   vol->flags |= HFS_UPDATE_VBM;
641 
642   /* scavenge the extents overflow file */
643 
644   n.bt   = &vol->ext;
645   n.nnum = vol->ext.hdr.bthFNode;
646 
647   if (n.nnum > 0)
648     {
649       if (bt_getnode(&n) < 0)
650 	return -1;
651 
652       n.rnum = 0;
653 
654       for (;;)
655 	{
656 	  ExtDataRec data;
657 	  unsigned char *ptr;
658 
659 	  while (n.rnum >= (int)n.nd.ndNRecs)
660 	    {
661 	      n.nnum = n.nd.ndFLink;
662 	      if (n.nnum == 0)
663 		break;
664 
665 	      if (bt_getnode(&n) < 0)
666 		return -1;
667 
668 	      n.rnum = 0;
669 	    }
670 
671 	  if (n.nnum == 0)
672 	    break;
673 
674 	  ptr = HFS_NODEREC(n, n.rnum);
675 	  r_unpackextdata(HFS_RECDATA(ptr), &data);
676 
677 	  markexts(vbm, &data);
678 
679 	  ++n.rnum;
680 	}
681     }
682 
683   /* scavenge the catalog file */
684 
685   n.bt   = &vol->cat;
686   n.nnum = vol->cat.hdr.bthFNode;
687 
688   if (n.nnum > 0)
689     {
690       if (bt_getnode(&n) < 0)
691 	return -1;
692 
693       n.rnum = 0;
694 
695       for (;;)
696 	{
697 	  CatDataRec data;
698 	  unsigned char *ptr;
699 
700 	  while (n.rnum >= (int)n.nd.ndNRecs)
701 	    {
702 	      n.nnum = n.nd.ndFLink;
703 	      if (n.nnum == 0)
704 		break;
705 
706 	      if (bt_getnode(&n) < 0)
707 		return -1;
708 
709 	      n.rnum = 0;
710 	    }
711 
712 	  if (n.nnum == 0)
713 	    break;
714 
715 	  ptr = HFS_NODEREC(n, n.rnum);
716 	  r_unpackcatdata(HFS_RECDATA(ptr), &data);
717 
718 	  if (data.cdrType == cdrFilRec)
719 	    {
720 	      markexts(vbm, &data.u.fil.filExtRec);
721 	      markexts(vbm, &data.u.fil.filRExtRec);
722 	    }
723 
724 	  ++n.rnum;
725 	}
726     }
727 
728   for (blks = 0, pt = vol->mdb.drNmAlBlks; pt--; )
729     {
730       if (! BMTST(vbm, pt))
731 	++blks;
732     }
733 
734   if (vol->mdb.drFreeBks != blks)
735     {
736       vol->mdb.drFreeBks = blks;
737       vol->flags |= HFS_UPDATE_MDB;
738     }
739 
740   return 0;
741 }
742