]> git.cameronkatri.com Git - apple_cmds.git/blob - diskdev_cmds/vsdbutil.tproj/vsdbutil_main.c
Merge branch 'apple'
[apple_cmds.git] / diskdev_cmds / vsdbutil.tproj / vsdbutil_main.c
1 /*
2 * Copyright (c) 2000-2018 Apple Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24 /*
25 About vsdbutil.c:
26 Contains code to manipulate the volume status DB (/var/db/volinfo.database).
27
28 Change History:
29 18-Apr-2000 Pat Dirks New Today.
30
31 */
32
33
34 /* ************************************** I N C L U D E S ***************************************** */
35
36 #include <sys/types.h>
37 #include <sys/param.h>
38 #include <sys/mount.h>
39 #include <sys/stat.h>
40 #include <sys/sysctl.h>
41 #include <sys/time.h>
42 #include <sys/ucred.h>
43 #include <sys/resource.h>
44 // #include <sys/vmmeter.h>
45 #include <sys/wait.h>
46 #include <stdio.h>
47 #include <unistd.h>
48 #include <stdlib.h>
49 #include <fcntl.h>
50 #include <err.h>
51 #include <errno.h>
52 #include <dirent.h>
53 #include <strings.h>
54
55 #include <sys/attr.h>
56 #include <uuid/uuid.h>
57 #include <System/uuid/namespace.h>
58
59 #include <libiosexec.h>
60
61 // This flags array is shared with the mount(8) tool.
62 #include "../mount_flags_dir/mount_flags.h"
63
64 //from mount_flags_dir/mount_flags.c
65 extern mountopt_t optnames[];
66
67
68 /*
69 * CommonCrypto is meant to be a more stable API than OpenSSL.
70 * Defining COMMON_DIGEST_FOR_OPENSSL gives API-compatibility
71 * with OpenSSL, so we don't have to change the code.
72 */
73 #define COMMON_DIGEST_FOR_OPENSSL
74 #include <CommonCrypto/CommonDigest.h>
75 #include <libkern/OSByteOrder.h>
76
77 static char usage[] = "Usage: %s [-a path] | [-c path ] [-d path] [-i]\n";
78
79 static char gHFSTypeName[] = "hfs";
80 static char gAPFSTypeName[] = "apfs";
81
82 /*****************************************************************************
83 *
84 * The following should really be in some system header file:
85 *
86 *****************************************************************************/
87
88 typedef struct VolumeUUID {
89 uuid_t uuid;
90 } VolumeUUID;
91
92 #define VOLUMEUUID64LENGTH 16
93
94 #define VOLUME_USEPERMISSIONS 0x00000001
95 #define VOLUME_VALIDSTATUSBITS ( VOLUME_USEPERMISSIONS )
96
97 typedef void *VolumeStatusDBHandle;
98
99 void ConvertVolumeUUIDString64ToUUID(const char *UUIDString64, VolumeUUID *volumeID);
100
101 int OpenVolumeStatusDB(VolumeStatusDBHandle *DBHandlePtr);
102 int ConvertVolumeStatusDB(VolumeStatusDBHandle DBHandle);
103 int GetVolumeStatusDBEntry(VolumeStatusDBHandle DBHandle, VolumeUUID *volumeID, u_int32_t *VolumeStatus);
104 int SetVolumeStatusDBEntry(VolumeStatusDBHandle DBHandle, VolumeUUID *volumeID, u_int32_t VolumeStatus);
105 int DeleteVolumeStatusDBEntry(VolumeStatusDBHandle DBHandle, VolumeUUID *volumeID);
106 int CloseVolumeStatusDB(VolumeStatusDBHandle DBHandle);
107
108 /*****************************************************************************
109 *
110 * Internal function prototypes:
111 *
112 *****************************************************************************/
113
114 static void check_uid(void);
115 static int GetVolumeUUID(const char *path, VolumeUUID *volumeUUIDPtr);
116 static int AdoptAllLocalVolumes(void);
117 static int AdoptVolume(const char *path);
118 static int DisownVolume(const char *path);
119 static int ClearVolumeUUID(const char *path);
120 static int DisplayVolumeStatus(const char *path);
121 static int UpdateMountStatus(const char *path, u_int32_t volstatus);
122
123
124 static int isVolumeHFS(const char*path);
125
126 int main (int argc, const char *argv[])
127 {
128 int arg;
129 char option;
130 int result = 0;
131
132 if (argc < 2) {
133 fprintf(stderr, usage, argv[0]);
134 exit(1);
135 };
136
137 for (arg = 1; arg < argc; ++arg) {
138 if ((argv[arg][0] == '-') &&
139 ((option = argv[arg][1]) != (char)0) &&
140 (argv[arg][2] == (char)0)) {
141 switch (option) {
142 case 'a':
143 case 'A':
144 /* Pick out the pathname argument: */
145 if (++arg >= argc) {
146 fprintf(stderr, usage, argv[0]);
147 exit(1);
148 }
149
150 check_uid();
151 result = AdoptVolume(argv[arg]);
152 break;
153
154 case 'c':
155 case 'C':
156 /* Pick out the pathname argument: */
157 if (++arg >= argc) {
158 fprintf(stderr, usage, argv[0]);
159 exit(1);
160 };
161
162 result = DisplayVolumeStatus(argv[arg]);
163 break;
164
165 case 'd':
166 case 'D':
167 /* Pick out the pathname argument: */
168 if (++arg >= argc) {
169 fprintf(stderr, usage, argv[0]);
170 exit(1);
171 };
172
173 check_uid();
174 result = DisownVolume(argv[arg]);
175 break;
176
177 case 'h':
178 case 'H':
179 printf(usage, argv[0]);
180 printf("where\n");
181 printf("\t-a adopts (activates) on-disk permissions on the specified path,\n");
182 printf("\t-c checks the status of the permissions usage on the specified path\n");
183 printf("\t-d disowns (deactivates) the on-disk permissions on the specified path\n");
184 printf("\t-i initializes the permissions database to include all mounted HFS/HFS+ volumes\n");
185 break;
186
187 case 'i':
188 case 'I':
189 check_uid();
190 result = AdoptAllLocalVolumes();
191 break;
192
193 case 'x':
194 case 'X':
195 /* Pick out the pathname argument: */
196 if (++arg >= argc) {
197 fprintf(stderr, usage, argv[0]);
198 exit(1);
199 };
200
201 check_uid();
202 result = ClearVolumeUUID(argv[arg]);
203 break;
204
205 default:
206 fprintf(stderr, usage, argv[0]);
207 exit(1);
208 }
209 }
210 }
211
212 if (result < 0) result = 1; // Make sure only positive exit status codes are generated
213
214 exit(result); // ensure the process exit status is returned
215 return result; // ...and make main fit the ANSI spec.
216 }
217
218
219
220 static void check_uid(void) {
221 if (geteuid() != 0) {
222 fprintf(stderr, "###\n");
223 fprintf(stderr, "### You must be root to perform this operation.\n");
224 fprintf(stderr, "###\n");
225 exit(1);
226 };
227 }
228
229
230
231 /*
232 -- UpdateMountStatus
233 --
234 -- Returns: error code (0 if successful).
235 */
236 static int
237 UpdateMountStatus(const char *path, u_int32_t volstatus) {
238 struct statfs mntstat;
239 int result;
240 union wait status;
241 int pid;
242
243 /*
244 * selectors to determine whether or not certain features
245 * should be re-enabled via mount -u
246 */
247 #ifndef MAXMOUNTLEN
248 #define MAXMOUNTLEN 255
249 #endif
250
251 char mountline[MAXMOUNTLEN];
252 char mountstring[MAXMOUNTLEN];
253
254 mountopt_t* opt = NULL;
255 uint64_t flags;
256 uint64_t flags_mask = (MNT_NOSUID | MNT_NODEV |
257 MNT_NOEXEC | MNT_RDONLY |
258 MNT_CPROTECT | MNT_QUARANTINE |
259 MNT_UNION | MNT_DONTBROWSE);
260
261 result = statfs(path, &mntstat);
262 if (result != 0) {
263 warn("couldn't look up mount status for '%s'", path);
264 return errno;
265 }
266
267 bzero (mountline, MAXMOUNTLEN);
268 bzero (mountstring, MAXMOUNTLEN);
269
270 /* first, check for permissions */
271 if (volstatus & VOLUME_USEPERMISSIONS) {
272 strlcpy(mountline, "perm", MAXMOUNTLEN);
273 }
274 else {
275 strlcpy(mountline, "noperm", MAXMOUNTLEN);
276 }
277
278 /* check the flags */
279 flags = (mntstat.f_flags & flags_mask);
280
281 /*
282 * now iterate over all of the strings in the optname array and
283 * add them into the "mount" string if the flag they represent is set.
284 * The optnames array is extern'd (at the top of this file), and is defined
285 * in a .c file within the mount_flags directory
286 */
287 for (opt = optnames; flags && opt->o_opt; opt++) {
288 if ((flags & opt->o_opt) && opt->o_cmd) {
289 snprintf(mountstring, sizeof(mountstring), ",%s", opt->o_cmd);
290 result = strlcat(mountline, mountstring, MAXMOUNTLEN);
291 if (result >= MAXMOUNTLEN) {
292 // bail out, string is too long.
293 return EINVAL;
294 }
295 flags &= ~opt->o_opt;
296 bzero (mountstring, MAXMOUNTLEN);
297 }
298 }
299
300 #ifdef MAXMOUNTLEN
301 #undef MAXMOUNTLEN
302 #endif
303
304 pid = fork();
305 if (pid == 0) {
306 result = execl("/sbin/mount", "mount",
307 "-t", mntstat.f_fstypename,
308 "-u","-o", mountline,
309 mntstat.f_mntfromname, mntstat.f_mntonname, NULL);
310 /* IF WE ARE HERE, WE WERE UNSUCCESFULL */
311 return (1);
312 }
313
314 if (pid == -1) {
315 warn("couldn't fork to execute mount command");
316 return errno;
317 };
318
319 /* Success! */
320 if ((wait4(pid, (int *)&status, 0, NULL) == pid) && (WIFEXITED(status))) {
321 result = status.w_retcode;
322 } else {
323 result = -1;
324 };
325
326 return result;
327 }
328
329
330
331 /*
332 -- AdoptAllLocalVolumes
333 --
334 -- Returns: error code (0 if successful).
335 */
336 static int
337 AdoptAllLocalVolumes(void) {
338 struct statfs *mntstatptr;
339 int fscount;
340
341 fscount = getmntinfo(&mntstatptr, MNT_WAIT);
342 if (fscount == 0) {
343 warn("couldn't get information on mounted volumes");
344 return errno;
345 };
346
347 while (fscount > 0) {
348 if ((strncmp(mntstatptr->f_fstypename, gHFSTypeName, MFSNAMELEN) == 0) ||
349 (strncmp(mntstatptr->f_fstypename, gAPFSTypeName, MFSNAMELEN) == 0)) {
350 (void)AdoptVolume(mntstatptr->f_mntonname);
351 };
352
353 ++mntstatptr;
354 --fscount;
355 };
356
357 return 0;
358 }
359
360
361
362 /*
363 -- AdoptVolume
364 --
365 -- Returns: error code (0 if successful).
366 */
367 static int
368 AdoptVolume(const char *path) {
369 VolumeUUID targetuuid;
370 VolumeStatusDBHandle vsdb;
371 u_int32_t volstatus;
372 int result = 0;
373
374 /* Look up the target volume UUID: */
375 result = GetVolumeUUID(path, &targetuuid);
376 if (result != 0) {
377 warnx("no valid volume UUID found on '%s': %s", path, strerror(result));
378 return result;
379 };
380
381 if (uuid_is_null(targetuuid.uuid)) {
382 warnx("internal error: incomplete UUID generated.");
383 return EINVAL;
384 };
385
386 /* Open the volume status DB to look up the entry for the volume in question: */
387 if ((result = OpenVolumeStatusDB(&vsdb)) != 0) {
388 warnx("couldn't access volume status database: %s", strerror(result));
389 return result;
390 };
391
392 /* Check to see if an entry exists. If not, prepare a default initial status value: */
393 if ((result = GetVolumeStatusDBEntry(vsdb, &targetuuid, &volstatus)) != 0) {
394 volstatus = 0;
395 };
396
397 /* Enable permissions on the specified volume: */
398 volstatus = (volstatus & VOLUME_VALIDSTATUSBITS) | VOLUME_USEPERMISSIONS;
399
400 /* Update the entry in the volume status database: */
401 if ((result = SetVolumeStatusDBEntry(vsdb, &targetuuid, volstatus)) != 0) {
402 warnx("couldn't update volume status database: %s", strerror(result));
403 return result;
404 };
405
406 (void)CloseVolumeStatusDB(vsdb);
407
408 if ((result = UpdateMountStatus(path, volstatus)) != 0) {
409 warnx("couldn't update mount status of '%s': %s", path, strerror(result));
410 return result;
411 };
412
413 return 0;
414 }
415
416
417
418 /*
419 -- DisownVolume
420 --
421 -- Returns: error code (0 if successful).
422 */
423 static int
424 DisownVolume(const char *path) {
425 VolumeUUID targetuuid;
426 VolumeStatusDBHandle vsdb;
427 u_int32_t volstatus;
428 int result = 0;
429
430 /* Look up the target volume UUID: */
431 result = GetVolumeUUID(path, &targetuuid);
432 if (result != 0) {
433 warnx("no valid volume UUID found on '%s': %s", path, strerror(result));
434 return result;
435 };
436
437 volstatus = 0;
438 if (!uuid_is_null(targetuuid.uuid)) {
439 /* Open the volume status DB to look up the entry for the volume in question: */
440 if ((result = OpenVolumeStatusDB(&vsdb)) != 0) {
441 warnx("couldn't access volume status database: %s", strerror(result));
442 return result;
443 };
444
445 /* Check to see if an entry exists. If not, prepare a default initial status value: */
446 if ((result = GetVolumeStatusDBEntry(vsdb, &targetuuid, &volstatus)) != 0) {
447 volstatus = 0;
448 };
449
450 /* Disable permissions on the specified volume: */
451 volstatus = (volstatus & VOLUME_VALIDSTATUSBITS) & ~VOLUME_USEPERMISSIONS;
452
453 /* Update the entry in the volume status database: */
454 if ((result = SetVolumeStatusDBEntry(vsdb, &targetuuid, volstatus)) != 0) {
455 warnx("couldn't update volume status database: %s", strerror(result));
456 return result;
457 };
458
459 (void)CloseVolumeStatusDB(vsdb);
460
461 };
462
463 if ((result = UpdateMountStatus(path, volstatus)) != 0) {
464 warnx("couldn't update mount status of '%s': %s", path, strerror(result));
465 return result;
466 };
467
468 return result;
469 };
470
471
472
473 /*
474 -- ClearVolumeUUID
475 --
476 -- Returns: error code (0 if successful).
477 */
478 static int
479 ClearVolumeUUID(const char *path) {
480 VolumeUUID targetuuid;
481 VolumeStatusDBHandle vsdb;
482 u_int32_t volstatus;
483 int result = 0;
484
485 /* Check to see whether the target volume has an assigned UUID: */
486 result = GetVolumeUUID(path, &targetuuid);
487 if (result != 0) {
488 warnx("couldn't read volume UUID on '%s': %s", path, strerror(result));
489 return result;
490 };
491
492 if (uuid_is_null(targetuuid.uuid) == 0) {
493 /* Open the volume status DB to look up the entry for the volume in question: */
494 if ((result = OpenVolumeStatusDB(&vsdb)) != 0) {
495 warnx("couldn't access volume status database: %s", strerror(result));
496 return result;
497 };
498
499 /* Check to see if an entry exists: */
500 if (GetVolumeStatusDBEntry(vsdb, &targetuuid, &volstatus) == 0) {
501 /* Remove the entry from the volume status database: */
502 if ((result = DeleteVolumeStatusDBEntry(vsdb, &targetuuid)) != 0) {
503 warnx("couldn't update volume status database: %s", strerror(result));
504 return result;
505 };
506 };
507
508 (void)CloseVolumeStatusDB(vsdb);
509
510 if ((result = UpdateMountStatus(path, 0)) != 0) {
511 warnx("couldn't update mount status of '%s': %s", path, strerror(result));
512 return result;
513 };
514
515 };
516
517 return result;
518 };
519
520
521
522 /*
523 -- DisplayVolumeStatus
524 --
525 -- Returns: error code (0 if successful).
526 */
527 static int
528 DisplayVolumeStatus(const char *path) {
529 VolumeUUID targetuuid;
530 VolumeStatusDBHandle vsdb;
531 u_int32_t volstatus;
532 int result = 0;
533
534 /* Look up the target volume UUID, exactly as stored on disk: */
535 result = GetVolumeUUID(path, &targetuuid);
536 if (result != 0) {
537 warnx("couldn't read volume UUID on '%s': %s", path, strerror(result));
538 return result;
539 };
540
541 if (uuid_is_null(targetuuid.uuid)) {
542 warnx("no valid volume UUID found on '%s': permissions are disabled.", path);
543 return 0;
544 };
545
546 /* Open the volume status DB to look up the entry for the volume in question: */
547 if ((result = OpenVolumeStatusDB(&vsdb)) != 0) {
548 warnx("couldn't access volume status database: %s", strerror(result));
549 return result;
550 };
551
552 if ((result = GetVolumeStatusDBEntry(vsdb, &targetuuid, &volstatus)) != 0) {
553 printf("No entry found for '%s'.\n", path);
554 goto Std_Exit;
555 };
556
557 if (volstatus & VOLUME_USEPERMISSIONS) {
558 printf("Permissions on '%s' are enabled.\n", path);
559 } else {
560 printf("Permissions on '%s' are disabled.\n", path);
561 };
562
563 Std_Exit:
564 (void)CloseVolumeStatusDB(vsdb);
565
566 return result;
567 }
568
569 static int isVolumeHFS (const char* path) {
570
571 /* default to no */
572 int result = 0;
573 int isHFS = 0;
574
575 struct statfs statfs_buf;
576
577 result = statfs (path, &statfs_buf);
578 if (result == 0) {
579 if (!strncmp(statfs_buf.f_fstypename, gHFSTypeName, 3)) {
580 isHFS = 1;
581 }
582 }
583
584 return isHFS;
585 }
586
587
588
589 //struct definition for calling getattrlist for finderinfos
590 typedef struct FinderInfoBuf {
591 uint32_t info_length;
592 uint32_t finderinfo[8];
593 } FinderInfoBuf_t;
594
595 typedef struct hfsUUID {
596 uint32_t high;
597 uint32_t low;
598 } hfsUUID_t;
599
600 /*
601 -- GetVolumeUUID
602 --
603 -- Returns: error code (0 if successful).
604 */
605
606 static int
607 GetVolumeUUID(const char *path, VolumeUUID *volumeUUIDPtr) {
608 struct attrlist alist;
609 struct { uint32_t size; uuid_t uuid; } volUUID;
610
611 FinderInfoBuf_t finfo;
612
613 int result;
614
615 /*
616 * For a bit more definition on why we have to do this, check out
617 * hfs.util source. The gist is that IFF the volume is HFS, then
618 * we must check the finderinfo UUID FIRST before querying the
619 * fs for its full UUID via the getattrlist volume call.
620 */
621
622 if (isVolumeHFS(path)) {
623 /* then go get the finderinfo, first... */
624 memset (&alist, 0, sizeof(alist));
625 alist.bitmapcount = ATTR_BIT_MAP_COUNT;
626 alist.reserved = 0;
627 alist.commonattr = ATTR_CMN_FNDRINFO;
628 alist.volattr = ATTR_VOL_INFO;
629 alist.dirattr = 0;
630 alist.fileattr = 0;
631 alist.forkattr = 0;
632
633 result = getattrlist (path, &alist, &finfo, sizeof(finfo), 0);
634 if (result) {
635 warn ("failed to getattrlist finderinfo for %s", path);
636 result = errno;
637 goto Err_Exit;
638 }
639
640 hfsUUID_t* hfs_finfo_uuid = (hfsUUID_t*)(&finfo.finderinfo[6]);
641
642 /*
643 * Note: this is a bit of HFS-specific chicanery. When HFS+ generates
644 * the volume UUID, the formula it uses to generate the 8 bytes of internal
645 * UUID is re-looped/restarted if either high or low is zero. Thus, if we
646 * see either word as '0' then that means we should treat it as an uninitialized
647 * UUID.
648 *
649 * As a result, if we see either word as zero, then clear out the caller's buffer
650 * and return the NULL UUID. Otherwise, we'd get the 8 bytes which potentially include
651 * one or more zeroes run through HFS+'s MD5 algorithm which is not what we want.
652 */
653 //technically should endian-swap this but not necessary here
654 if ((hfs_finfo_uuid->high == 0) || (hfs_finfo_uuid->low == 0)) {
655 uuid_clear (volumeUUIDPtr->uuid);
656 return 0;
657 }
658 }
659
660
661 /* Set up the attrlist structure to get the volume's UUID: */
662 alist.bitmapcount = ATTR_BIT_MAP_COUNT;
663 alist.reserved = 0;
664 alist.commonattr = 0;
665 alist.volattr = (ATTR_VOL_INFO | ATTR_VOL_UUID);
666 alist.dirattr = 0;
667 alist.fileattr = 0;
668 alist.forkattr = 0;
669
670 result = getattrlist(path, &alist, &volUUID, sizeof(volUUID), 0);
671 if (result) {
672 warn("Couldn't get volume information for '%s'", path);
673 result = errno;
674 goto Err_Exit;
675 }
676
677 uuid_copy(volumeUUIDPtr->uuid, volUUID.uuid);
678 result = 0;
679
680 Err_Exit:
681 return result;
682 };
683
684
685
686
687
688 /******************************************************************************
689 *
690 * V O L U M E S T A T U S D A T A B A S E R O U T I N E S
691 *
692 *****************************************************************************/
693
694 #define DBHANDLESIGNATURE 0x75917737
695
696 /* Flag values for operation options: */
697 #define DBMARKPOSITION 1
698
699 static char gVSDBPath[] = "/var/db/volinfo.database";
700
701 #define MAXIOMALLOC 16384
702
703 /* Database layout: */
704
705 struct VSDBKey {
706 char uuidString[36];
707 };
708
709 struct VSDBRecord {
710 char statusFlags[8];
711 };
712
713 struct VSDBEntry {
714 struct VSDBKey key;
715 char keySeparator;
716 char space;
717 struct VSDBRecord record;
718 char terminator;
719 };
720
721 struct VSDBKey64 {
722 char uuid[16];
723 };
724
725 struct VSDBEntry64 {
726 struct VSDBKey64 key;
727 char keySeparator;
728 char space;
729 struct VSDBRecord record;
730 char terminator;
731 };
732
733 #define DBKEYSEPARATOR ':'
734 #define DBBLANKSPACE ' '
735 #define DBRECORDTERMINATOR '\n'
736
737 /* In-memory data structures: */
738
739 struct VSDBState {
740 u_int32_t signature;
741 int dbfile;
742 int dbmode;
743 off_t recordPosition;
744 };
745
746 typedef struct VSDBState *VSDBStatePtr;
747
748
749
750 /* Internal function prototypes: */
751 static int LockDB(VSDBStatePtr dbstateptr, int lockmode);
752 static int UnlockDB(VSDBStatePtr dbstateptr);
753
754 static int FindVolumeRecordByUUID(VSDBStatePtr dbstateptr, VolumeUUID *volumeID, struct VSDBEntry *dbentry, u_int32_t options);
755 static int AddVolumeRecord(VSDBStatePtr dbstateptr, struct VSDBEntry *dbentry);
756 static int UpdateVolumeRecord(VSDBStatePtr dbstateptr, struct VSDBEntry *dbentry);
757 static int GetVSDBEntry(VSDBStatePtr dbstateptr, struct VSDBEntry *dbentry);
758 static int CompareVSDBKeys(struct VSDBKey *key1, struct VSDBKey *key2);
759
760 static void FormatULong(u_int32_t u, char *s);
761 static void FormatDBKey(VolumeUUID *volumeID, struct VSDBKey *dbkey);
762 static void FormatDBRecord(u_int32_t volumeStatusFlags, struct VSDBRecord *dbrecord);
763 static void FormatDBEntry(VolumeUUID *volumeID, u_int32_t volumeStatusFlags, struct VSDBEntry *dbentry);
764 static u_int32_t ConvertHexStringToULong(const char *hs, long maxdigits);
765
766
767
768 /******************************************************************************
769 *
770 * P U B L I S H E D I N T E R F A C E R O U T I N E S
771 *
772 *****************************************************************************/
773
774 void ConvertVolumeUUIDString64ToUUID(const char *UUIDString64, VolumeUUID *volumeID) {
775 int i;
776 char c;
777 u_int32_t nextdigit;
778 u_int32_t high = 0;
779 u_int32_t low = 0;
780 u_int32_t carry;
781 MD5_CTX ctx;
782
783 for (i = 0; (i < VOLUMEUUID64LENGTH) && ((c = UUIDString64[i]) != (char)0) ; ++i) {
784 if ((c >= '0') && (c <= '9')) {
785 nextdigit = c - '0';
786 } else if ((c >= 'A') && (c <= 'F')) {
787 nextdigit = c - 'A' + 10;
788 } else if ((c >= 'a') && (c <= 'f')) {
789 nextdigit = c - 'a' + 10;
790 } else {
791 nextdigit = 0;
792 };
793 carry = ((low & 0xF0000000) >> 28) & 0x0000000F;
794 high = (high << 4) | carry;
795 low = (low << 4) | nextdigit;
796 };
797
798 high = OSSwapHostToBigInt32(high);
799 low = OSSwapHostToBigInt32(low);
800
801 MD5_Init(&ctx);
802 MD5_Update(&ctx, kFSUUIDNamespaceSHA1, sizeof(uuid_t));
803 MD5_Update(&ctx, &high, sizeof(high));
804 MD5_Update(&ctx, &low, sizeof(low));
805 MD5_Final(volumeID->uuid, &ctx);
806
807 volumeID->uuid[6] = (volumeID->uuid[6] & 0x0F) | 0x30;
808 volumeID->uuid[8] = (volumeID->uuid[8] & 0x3F) | 0x80;
809 }
810
811
812
813 int OpenVolumeStatusDB(VolumeStatusDBHandle *DBHandlePtr) {
814 VSDBStatePtr dbstateptr;
815
816 *DBHandlePtr = NULL;
817
818 dbstateptr = (VSDBStatePtr)malloc(sizeof(*dbstateptr));
819 if (dbstateptr == NULL) {
820 return ENOMEM;
821 };
822
823 dbstateptr->dbmode = O_RDWR;
824 dbstateptr->dbfile = open(gVSDBPath, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
825 if (dbstateptr->dbfile == -1) {
826 /*
827 The file couldn't be opened for read/write access:
828 try read-only access before giving up altogether.
829 */
830 dbstateptr->dbmode = O_RDONLY;
831 dbstateptr->dbfile = open(gVSDBPath, O_RDONLY | O_CREAT, S_IRUSR | S_IWUSR);
832 if (dbstateptr->dbfile == -1) {
833 return errno;
834 };
835 };
836
837 dbstateptr->signature = DBHANDLESIGNATURE;
838 *DBHandlePtr = (VolumeStatusDBHandle)dbstateptr;
839 ConvertVolumeStatusDB(*DBHandlePtr);
840 return 0;
841 }
842
843
844
845 int ConvertVolumeStatusDB(VolumeStatusDBHandle DBHandle) {
846 VSDBStatePtr dbstateptr = (VSDBStatePtr)DBHandle;
847 struct VSDBEntry64 entry64;
848 struct stat dbinfo;
849 int result;
850 u_int32_t iobuffersize;
851 void *iobuffer = NULL;
852 int i;
853
854 if (dbstateptr->signature != DBHANDLESIGNATURE) return EINVAL;
855
856 if ((result = LockDB(dbstateptr, LOCK_EX)) != 0) return result;
857
858 lseek(dbstateptr->dbfile, 0, SEEK_SET);
859 result = read(dbstateptr->dbfile, &entry64, sizeof(entry64));
860 if ((result != sizeof(entry64)) ||
861 (entry64.keySeparator != DBKEYSEPARATOR) ||
862 (entry64.space != DBBLANKSPACE) ||
863 (entry64.terminator != DBRECORDTERMINATOR)) {
864 result = 0;
865 goto ErrExit;
866 } else {
867 if ((result = stat(gVSDBPath, &dbinfo)) != 0) goto ErrExit;
868 iobuffersize = dbinfo.st_size;
869 iobuffer = malloc(iobuffersize);
870 if (iobuffer == NULL) {
871 result = ENOMEM;
872 goto ErrExit;
873 };
874
875 lseek(dbstateptr->dbfile, 0, SEEK_SET);
876 result = read(dbstateptr->dbfile, iobuffer, iobuffersize);
877 if (result != iobuffersize) {
878 result = errno;
879 goto ErrExit;
880 };
881 if ((result = ftruncate(dbstateptr->dbfile, 0)) != 0) {
882 goto ErrExit;
883 };
884
885 for (i = 0; i < iobuffersize / sizeof(entry64); i++) {
886 VolumeUUID volumeID;
887 u_int32_t VolumeStatus;
888 struct VSDBEntry dbentry;
889
890 entry64 = *(((struct VSDBEntry64 *)iobuffer) + i);
891 if ((entry64.keySeparator != DBKEYSEPARATOR) ||
892 (entry64.space != DBBLANKSPACE) ||
893 (entry64.terminator != DBRECORDTERMINATOR)) {
894 continue;
895 }
896
897 ConvertVolumeUUIDString64ToUUID(entry64.key.uuid, &volumeID);
898 VolumeStatus = ConvertHexStringToULong(entry64.record.statusFlags, sizeof(entry64.record.statusFlags));
899
900 FormatDBEntry(&volumeID, VolumeStatus, &dbentry);
901 if ((result = AddVolumeRecord(dbstateptr, &dbentry)) != sizeof(dbentry)) {
902 warnx("couldn't convert volume status database: %s", strerror(result));
903 goto ErrExit;
904 };
905 };
906
907 fsync(dbstateptr->dbfile);
908
909 result = 0;
910 };
911
912 ErrExit:
913 if (iobuffer) free(iobuffer);
914 UnlockDB(dbstateptr);
915 return result;
916 }
917
918
919
920 int GetVolumeStatusDBEntry(VolumeStatusDBHandle DBHandle, VolumeUUID *volumeID, u_int32_t *VolumeStatus) {
921 VSDBStatePtr dbstateptr = (VSDBStatePtr)DBHandle;
922 struct VSDBEntry dbentry;
923 int result;
924
925 if (dbstateptr->signature != DBHANDLESIGNATURE) return EINVAL;
926
927 if ((result = LockDB(dbstateptr, LOCK_SH)) != 0) return result;
928
929 if ((result = FindVolumeRecordByUUID(dbstateptr, volumeID, &dbentry, 0)) != 0) {
930 goto ErrExit;
931 };
932 *VolumeStatus = ConvertHexStringToULong(dbentry.record.statusFlags, sizeof(dbentry.record.statusFlags));
933
934 result = 0;
935
936 ErrExit:
937 UnlockDB(dbstateptr);
938 return result;
939 }
940
941
942
943 int SetVolumeStatusDBEntry(VolumeStatusDBHandle DBHandle, VolumeUUID *volumeID, u_int32_t VolumeStatus) {
944 VSDBStatePtr dbstateptr = (VSDBStatePtr)DBHandle;
945 struct VSDBEntry dbentry;
946 int result;
947
948 if (dbstateptr->signature != DBHANDLESIGNATURE) return EINVAL;
949 if (VolumeStatus & ~VOLUME_VALIDSTATUSBITS) return EINVAL;
950
951 if ((result = LockDB(dbstateptr, LOCK_EX)) != 0) return result;
952
953 FormatDBEntry(volumeID, VolumeStatus, &dbentry);
954 if ((result = FindVolumeRecordByUUID(dbstateptr, volumeID, NULL, DBMARKPOSITION)) == 0) {
955 result = UpdateVolumeRecord(dbstateptr, &dbentry);
956 } else if (result == -1) {
957 result = AddVolumeRecord(dbstateptr, &dbentry);
958 } else {
959 goto ErrExit;
960 };
961
962 fsync(dbstateptr->dbfile);
963
964 result = 0;
965
966 ErrExit:
967 UnlockDB(dbstateptr);
968 return result;
969 }
970
971
972
973 int DeleteVolumeStatusDBEntry(VolumeStatusDBHandle DBHandle, VolumeUUID *volumeID) {
974 VSDBStatePtr dbstateptr = (VSDBStatePtr)DBHandle;
975 struct stat dbinfo;
976 int result;
977 u_int32_t iobuffersize;
978 void *iobuffer = NULL;
979 off_t dataoffset;
980 u_int32_t iotransfersize;
981 u_int32_t bytestransferred;
982
983 if (dbstateptr->signature != DBHANDLESIGNATURE) return EINVAL;
984
985 if ((result = LockDB(dbstateptr, LOCK_EX)) != 0) return result;
986
987 if ((result = FindVolumeRecordByUUID(dbstateptr, volumeID, NULL, DBMARKPOSITION)) != 0) {
988 if (result == -1) result = 0; /* Entry wasn't in the database to begin with? */
989 goto StdEdit;
990 } else {
991 if ((result = stat(gVSDBPath, &dbinfo)) != 0) goto ErrExit;
992 if ((dbinfo.st_size - dbstateptr->recordPosition - sizeof(struct VSDBEntry)) <= MAXIOMALLOC) {
993 iobuffersize = dbinfo.st_size - dbstateptr->recordPosition - sizeof(struct VSDBEntry);
994 } else {
995 iobuffersize = MAXIOMALLOC;
996 };
997 if (iobuffersize > 0) {
998 iobuffer = malloc(iobuffersize);
999 if (iobuffer == NULL) {
1000 result = ENOMEM;
1001 goto ErrExit;
1002 };
1003
1004 dataoffset = dbstateptr->recordPosition + sizeof(struct VSDBEntry);
1005 do {
1006 iotransfersize = dbinfo.st_size - dataoffset;
1007 if (iotransfersize > 0) {
1008 if (iotransfersize > iobuffersize) iotransfersize = iobuffersize;
1009
1010 lseek(dbstateptr->dbfile, dataoffset, SEEK_SET);
1011 bytestransferred = read(dbstateptr->dbfile, iobuffer, iotransfersize);
1012 if (bytestransferred != iotransfersize) {
1013 result = errno;
1014 goto ErrExit;
1015 };
1016
1017 lseek(dbstateptr->dbfile, dataoffset - (off_t)sizeof(struct VSDBEntry), SEEK_SET);
1018 bytestransferred = write(dbstateptr->dbfile, iobuffer, iotransfersize);
1019 if (bytestransferred != iotransfersize) {
1020 result = errno;
1021 goto ErrExit;
1022 };
1023
1024 dataoffset += (off_t)iotransfersize;
1025 };
1026 } while (iotransfersize > 0);
1027 };
1028 if ((result = ftruncate(dbstateptr->dbfile, dbinfo.st_size - (off_t)(sizeof(struct VSDBEntry)))) != 0) {
1029 goto ErrExit;
1030 };
1031
1032 fsync(dbstateptr->dbfile);
1033
1034 result = 0;
1035 };
1036
1037 ErrExit:
1038 if (iobuffer) free(iobuffer);
1039 UnlockDB(dbstateptr);
1040
1041 StdEdit:
1042 return result;
1043 }
1044
1045
1046
1047 int CloseVolumeStatusDB(VolumeStatusDBHandle DBHandle) {
1048 VSDBStatePtr dbstateptr = (VSDBStatePtr)DBHandle;
1049
1050 if (dbstateptr->signature != DBHANDLESIGNATURE) return EINVAL;
1051
1052 dbstateptr->signature = 0;
1053
1054 close(dbstateptr->dbfile); /* Nothing we can do about any errors... */
1055 dbstateptr->dbfile = 0;
1056
1057 free(dbstateptr);
1058
1059 return 0;
1060 }
1061
1062
1063
1064 /******************************************************************************
1065 *
1066 * I N T E R N A L O N L Y D A T A B A S E R O U T I N E S
1067 *
1068 *****************************************************************************/
1069
1070 static int LockDB(VSDBStatePtr dbstateptr, int lockmode) {
1071 return flock(dbstateptr->dbfile, lockmode);
1072 }
1073
1074
1075
1076 static int UnlockDB(VSDBStatePtr dbstateptr) {
1077 return flock(dbstateptr->dbfile, LOCK_UN);
1078 }
1079
1080
1081
1082 static int FindVolumeRecordByUUID(VSDBStatePtr dbstateptr, VolumeUUID *volumeID, struct VSDBEntry *targetEntry, u_int32_t options) {
1083 struct VSDBKey searchkey;
1084 struct VSDBEntry dbentry;
1085 int result;
1086
1087 FormatDBKey(volumeID, &searchkey);
1088 lseek(dbstateptr->dbfile, 0, SEEK_SET);
1089
1090 do {
1091 result = GetVSDBEntry(dbstateptr, &dbentry);
1092 if ((result == 0) && (CompareVSDBKeys(&dbentry.key, &searchkey) == 0)) {
1093 if (targetEntry != NULL) {
1094 memcpy(targetEntry, &dbentry, sizeof(*targetEntry));
1095 };
1096 return 0;
1097 };
1098 } while (result == 0);
1099
1100 return -1;
1101 }
1102
1103
1104
1105 static int AddVolumeRecord(VSDBStatePtr dbstateptr , struct VSDBEntry *dbentry) {
1106 lseek(dbstateptr->dbfile, 0, SEEK_END);
1107 return write(dbstateptr->dbfile, dbentry, sizeof(struct VSDBEntry));
1108 }
1109
1110
1111
1112
1113 static int UpdateVolumeRecord(VSDBStatePtr dbstateptr, struct VSDBEntry *dbentry) {
1114 lseek(dbstateptr->dbfile, dbstateptr->recordPosition, SEEK_SET);
1115 return write(dbstateptr->dbfile, dbentry, sizeof(*dbentry));
1116 }
1117
1118
1119
1120 static int GetVSDBEntry(VSDBStatePtr dbstateptr, struct VSDBEntry *dbentry) {
1121 struct VSDBEntry entry;
1122 int result;
1123
1124 dbstateptr->recordPosition = lseek(dbstateptr->dbfile, 0, SEEK_CUR);
1125 result = read(dbstateptr->dbfile, &entry, sizeof(entry));
1126 if ((result != sizeof(entry)) ||
1127 (entry.keySeparator != DBKEYSEPARATOR) ||
1128 (entry.space != DBBLANKSPACE) ||
1129 (entry.terminator != DBRECORDTERMINATOR)) {
1130 return -1;
1131 };
1132
1133 memcpy(dbentry, &entry, sizeof(*dbentry));
1134 return 0;
1135 };
1136
1137
1138
1139 static int CompareVSDBKeys(struct VSDBKey *key1, struct VSDBKey *key2) {
1140 return memcmp(key1->uuidString, key2->uuidString, sizeof(key1->uuidString));
1141 }
1142
1143
1144
1145 /******************************************************************************
1146 *
1147 * F O R M A T T I N G A N D C O N V E R S I O N R O U T I N E S
1148 *
1149 *****************************************************************************/
1150
1151 static void FormatULong(u_int32_t u, char *s) {
1152 u_int32_t d;
1153 int i;
1154 char *digitptr = s;
1155
1156 for (i = 0; i < 8; ++i) {
1157 d = ((u & 0xF0000000) >> 28) & 0x0000000F;
1158 if (d < 10) {
1159 *digitptr++ = (char)(d + '0');
1160 } else {
1161 *digitptr++ = (char)(d - 10 + 'A');
1162 };
1163 u = u << 4;
1164 };
1165 }
1166
1167
1168
1169 static void FormatDBKey(VolumeUUID *volumeID, struct VSDBKey *dbkey) {
1170 uuid_string_t uuid_str;
1171
1172 uuid_unparse(volumeID->uuid, uuid_str);
1173 memcpy(dbkey->uuidString, uuid_str, sizeof(dbkey->uuidString));
1174 }
1175
1176
1177
1178 static void FormatDBRecord(u_int32_t volumeStatusFlags, struct VSDBRecord *dbrecord) {
1179 FormatULong(volumeStatusFlags, dbrecord->statusFlags);
1180 }
1181
1182
1183
1184 static void FormatDBEntry(VolumeUUID *volumeID, u_int32_t volumeStatusFlags, struct VSDBEntry *dbentry) {
1185 FormatDBKey(volumeID, &dbentry->key);
1186 dbentry->keySeparator = DBKEYSEPARATOR;
1187 dbentry->space = DBBLANKSPACE;
1188 FormatDBRecord(volumeStatusFlags, &dbentry->record);
1189 dbentry->terminator = DBRECORDTERMINATOR;
1190 }
1191
1192
1193
1194 static u_int32_t ConvertHexStringToULong(const char *hs, long maxdigits) {
1195 int i;
1196 char c;
1197 u_int32_t nextdigit;
1198 u_int32_t n;
1199
1200 n = 0;
1201 for (i = 0; (i < 8) && ((c = hs[i]) != (char)0) ; ++i) {
1202 if ((c >= '0') && (c <= '9')) {
1203 nextdigit = c - '0';
1204 } else if ((c >= 'A') && (c <= 'F')) {
1205 nextdigit = c - 'A' + 10;
1206 } else if ((c >= 'a') && (c <= 'f')) {
1207 nextdigit = c - 'a' + 10;
1208 } else {
1209 nextdigit = 0;
1210 };
1211 n = (n << 4) + nextdigit;
1212 };
1213
1214 return n;
1215 }