/* * Copyright (c) 2000-2018 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /* About vsdbutil.c: Contains code to manipulate the volume status DB (/var/db/volinfo.database). Change History: 18-Apr-2000 Pat Dirks New Today. */ /* ************************************** I N C L U D E S ***************************************** */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // This flags array is shared with the mount(8) tool. #include "../mount_flags_dir/mount_flags.h" //from mount_flags_dir/mount_flags.c extern mountopt_t optnames[]; /* * CommonCrypto is meant to be a more stable API than OpenSSL. * Defining COMMON_DIGEST_FOR_OPENSSL gives API-compatibility * with OpenSSL, so we don't have to change the code. */ #define COMMON_DIGEST_FOR_OPENSSL #include #include static char usage[] = "Usage: %s [-a path] | [-c path ] [-d path] [-i]\n"; static char gHFSTypeName[] = "hfs"; static char gAPFSTypeName[] = "apfs"; /***************************************************************************** * * The following should really be in some system header file: * *****************************************************************************/ typedef struct VolumeUUID { uuid_t uuid; } VolumeUUID; #define VOLUMEUUID64LENGTH 16 #define VOLUME_USEPERMISSIONS 0x00000001 #define VOLUME_VALIDSTATUSBITS ( VOLUME_USEPERMISSIONS ) typedef void *VolumeStatusDBHandle; void ConvertVolumeUUIDString64ToUUID(const char *UUIDString64, VolumeUUID *volumeID); int OpenVolumeStatusDB(VolumeStatusDBHandle *DBHandlePtr); int ConvertVolumeStatusDB(VolumeStatusDBHandle DBHandle); int GetVolumeStatusDBEntry(VolumeStatusDBHandle DBHandle, VolumeUUID *volumeID, u_int32_t *VolumeStatus); int SetVolumeStatusDBEntry(VolumeStatusDBHandle DBHandle, VolumeUUID *volumeID, u_int32_t VolumeStatus); int DeleteVolumeStatusDBEntry(VolumeStatusDBHandle DBHandle, VolumeUUID *volumeID); int CloseVolumeStatusDB(VolumeStatusDBHandle DBHandle); /***************************************************************************** * * Internal function prototypes: * *****************************************************************************/ static void check_uid(void); static int GetVolumeUUID(const char *path, VolumeUUID *volumeUUIDPtr); static int AdoptAllLocalVolumes(void); static int AdoptVolume(const char *path); static int DisownVolume(const char *path); static int ClearVolumeUUID(const char *path); static int DisplayVolumeStatus(const char *path); static int UpdateMountStatus(const char *path, u_int32_t volstatus); static int isVolumeHFS(const char*path); int main (int argc, const char *argv[]) { int arg; char option; int result = 0; if (argc < 2) { fprintf(stderr, usage, argv[0]); exit(1); }; for (arg = 1; arg < argc; ++arg) { if ((argv[arg][0] == '-') && ((option = argv[arg][1]) != (char)0) && (argv[arg][2] == (char)0)) { switch (option) { case 'a': case 'A': /* Pick out the pathname argument: */ if (++arg >= argc) { fprintf(stderr, usage, argv[0]); exit(1); } check_uid(); result = AdoptVolume(argv[arg]); break; case 'c': case 'C': /* Pick out the pathname argument: */ if (++arg >= argc) { fprintf(stderr, usage, argv[0]); exit(1); }; result = DisplayVolumeStatus(argv[arg]); break; case 'd': case 'D': /* Pick out the pathname argument: */ if (++arg >= argc) { fprintf(stderr, usage, argv[0]); exit(1); }; check_uid(); result = DisownVolume(argv[arg]); break; case 'h': case 'H': printf(usage, argv[0]); printf("where\n"); printf("\t-a adopts (activates) on-disk permissions on the specified path,\n"); printf("\t-c checks the status of the permissions usage on the specified path\n"); printf("\t-d disowns (deactivates) the on-disk permissions on the specified path\n"); printf("\t-i initializes the permissions database to include all mounted HFS/HFS+ volumes\n"); break; case 'i': case 'I': check_uid(); result = AdoptAllLocalVolumes(); break; case 'x': case 'X': /* Pick out the pathname argument: */ if (++arg >= argc) { fprintf(stderr, usage, argv[0]); exit(1); }; check_uid(); result = ClearVolumeUUID(argv[arg]); break; default: fprintf(stderr, usage, argv[0]); exit(1); } } } if (result < 0) result = 1; // Make sure only positive exit status codes are generated exit(result); // ensure the process exit status is returned return result; // ...and make main fit the ANSI spec. } static void check_uid(void) { if (geteuid() != 0) { fprintf(stderr, "###\n"); fprintf(stderr, "### You must be root to perform this operation.\n"); fprintf(stderr, "###\n"); exit(1); }; } /* -- UpdateMountStatus -- -- Returns: error code (0 if successful). */ static int UpdateMountStatus(const char *path, u_int32_t volstatus) { struct statfs mntstat; int result; union wait status; int pid; /* * selectors to determine whether or not certain features * should be re-enabled via mount -u */ #ifndef MAXMOUNTLEN #define MAXMOUNTLEN 255 #endif char mountline[MAXMOUNTLEN]; char mountstring[MAXMOUNTLEN]; mountopt_t* opt = NULL; uint64_t flags; uint64_t flags_mask = (MNT_NOSUID | MNT_NODEV | MNT_NOEXEC | MNT_RDONLY | MNT_CPROTECT | MNT_QUARANTINE | MNT_UNION | MNT_DONTBROWSE); result = statfs(path, &mntstat); if (result != 0) { warn("couldn't look up mount status for '%s'", path); return errno; } bzero (mountline, MAXMOUNTLEN); bzero (mountstring, MAXMOUNTLEN); /* first, check for permissions */ if (volstatus & VOLUME_USEPERMISSIONS) { strlcpy(mountline, "perm", MAXMOUNTLEN); } else { strlcpy(mountline, "noperm", MAXMOUNTLEN); } /* check the flags */ flags = (mntstat.f_flags & flags_mask); /* * now iterate over all of the strings in the optname array and * add them into the "mount" string if the flag they represent is set. * The optnames array is extern'd (at the top of this file), and is defined * in a .c file within the mount_flags directory */ for (opt = optnames; flags && opt->o_opt; opt++) { if ((flags & opt->o_opt) && opt->o_cmd) { snprintf(mountstring, sizeof(mountstring), ",%s", opt->o_cmd); result = strlcat(mountline, mountstring, MAXMOUNTLEN); if (result >= MAXMOUNTLEN) { // bail out, string is too long. return EINVAL; } flags &= ~opt->o_opt; bzero (mountstring, MAXMOUNTLEN); } } #ifdef MAXMOUNTLEN #undef MAXMOUNTLEN #endif pid = fork(); if (pid == 0) { result = execl("/sbin/mount", "mount", "-t", mntstat.f_fstypename, "-u","-o", mountline, mntstat.f_mntfromname, mntstat.f_mntonname, NULL); /* IF WE ARE HERE, WE WERE UNSUCCESFULL */ return (1); } if (pid == -1) { warn("couldn't fork to execute mount command"); return errno; }; /* Success! */ if ((wait4(pid, (int *)&status, 0, NULL) == pid) && (WIFEXITED(status))) { result = status.w_retcode; } else { result = -1; }; return result; } /* -- AdoptAllLocalVolumes -- -- Returns: error code (0 if successful). */ static int AdoptAllLocalVolumes(void) { struct statfs *mntstatptr; int fscount; fscount = getmntinfo(&mntstatptr, MNT_WAIT); if (fscount == 0) { warn("couldn't get information on mounted volumes"); return errno; }; while (fscount > 0) { if ((strncmp(mntstatptr->f_fstypename, gHFSTypeName, MFSNAMELEN) == 0) || (strncmp(mntstatptr->f_fstypename, gAPFSTypeName, MFSNAMELEN) == 0)) { (void)AdoptVolume(mntstatptr->f_mntonname); }; ++mntstatptr; --fscount; }; return 0; } /* -- AdoptVolume -- -- Returns: error code (0 if successful). */ static int AdoptVolume(const char *path) { VolumeUUID targetuuid; VolumeStatusDBHandle vsdb; u_int32_t volstatus; int result = 0; /* Look up the target volume UUID: */ result = GetVolumeUUID(path, &targetuuid); if (result != 0) { warnx("no valid volume UUID found on '%s': %s", path, strerror(result)); return result; }; if (uuid_is_null(targetuuid.uuid)) { warnx("internal error: incomplete UUID generated."); return EINVAL; }; /* Open the volume status DB to look up the entry for the volume in question: */ if ((result = OpenVolumeStatusDB(&vsdb)) != 0) { warnx("couldn't access volume status database: %s", strerror(result)); return result; }; /* Check to see if an entry exists. If not, prepare a default initial status value: */ if ((result = GetVolumeStatusDBEntry(vsdb, &targetuuid, &volstatus)) != 0) { volstatus = 0; }; /* Enable permissions on the specified volume: */ volstatus = (volstatus & VOLUME_VALIDSTATUSBITS) | VOLUME_USEPERMISSIONS; /* Update the entry in the volume status database: */ if ((result = SetVolumeStatusDBEntry(vsdb, &targetuuid, volstatus)) != 0) { warnx("couldn't update volume status database: %s", strerror(result)); return result; }; (void)CloseVolumeStatusDB(vsdb); if ((result = UpdateMountStatus(path, volstatus)) != 0) { warnx("couldn't update mount status of '%s': %s", path, strerror(result)); return result; }; return 0; } /* -- DisownVolume -- -- Returns: error code (0 if successful). */ static int DisownVolume(const char *path) { VolumeUUID targetuuid; VolumeStatusDBHandle vsdb; u_int32_t volstatus; int result = 0; /* Look up the target volume UUID: */ result = GetVolumeUUID(path, &targetuuid); if (result != 0) { warnx("no valid volume UUID found on '%s': %s", path, strerror(result)); return result; }; volstatus = 0; if (!uuid_is_null(targetuuid.uuid)) { /* Open the volume status DB to look up the entry for the volume in question: */ if ((result = OpenVolumeStatusDB(&vsdb)) != 0) { warnx("couldn't access volume status database: %s", strerror(result)); return result; }; /* Check to see if an entry exists. If not, prepare a default initial status value: */ if ((result = GetVolumeStatusDBEntry(vsdb, &targetuuid, &volstatus)) != 0) { volstatus = 0; }; /* Disable permissions on the specified volume: */ volstatus = (volstatus & VOLUME_VALIDSTATUSBITS) & ~VOLUME_USEPERMISSIONS; /* Update the entry in the volume status database: */ if ((result = SetVolumeStatusDBEntry(vsdb, &targetuuid, volstatus)) != 0) { warnx("couldn't update volume status database: %s", strerror(result)); return result; }; (void)CloseVolumeStatusDB(vsdb); }; if ((result = UpdateMountStatus(path, volstatus)) != 0) { warnx("couldn't update mount status of '%s': %s", path, strerror(result)); return result; }; return result; }; /* -- ClearVolumeUUID -- -- Returns: error code (0 if successful). */ static int ClearVolumeUUID(const char *path) { VolumeUUID targetuuid; VolumeStatusDBHandle vsdb; u_int32_t volstatus; int result = 0; /* Check to see whether the target volume has an assigned UUID: */ result = GetVolumeUUID(path, &targetuuid); if (result != 0) { warnx("couldn't read volume UUID on '%s': %s", path, strerror(result)); return result; }; if (uuid_is_null(targetuuid.uuid) == 0) { /* Open the volume status DB to look up the entry for the volume in question: */ if ((result = OpenVolumeStatusDB(&vsdb)) != 0) { warnx("couldn't access volume status database: %s", strerror(result)); return result; }; /* Check to see if an entry exists: */ if (GetVolumeStatusDBEntry(vsdb, &targetuuid, &volstatus) == 0) { /* Remove the entry from the volume status database: */ if ((result = DeleteVolumeStatusDBEntry(vsdb, &targetuuid)) != 0) { warnx("couldn't update volume status database: %s", strerror(result)); return result; }; }; (void)CloseVolumeStatusDB(vsdb); if ((result = UpdateMountStatus(path, 0)) != 0) { warnx("couldn't update mount status of '%s': %s", path, strerror(result)); return result; }; }; return result; }; /* -- DisplayVolumeStatus -- -- Returns: error code (0 if successful). */ static int DisplayVolumeStatus(const char *path) { VolumeUUID targetuuid; VolumeStatusDBHandle vsdb; u_int32_t volstatus; int result = 0; /* Look up the target volume UUID, exactly as stored on disk: */ result = GetVolumeUUID(path, &targetuuid); if (result != 0) { warnx("couldn't read volume UUID on '%s': %s", path, strerror(result)); return result; }; if (uuid_is_null(targetuuid.uuid)) { warnx("no valid volume UUID found on '%s': permissions are disabled.", path); return 0; }; /* Open the volume status DB to look up the entry for the volume in question: */ if ((result = OpenVolumeStatusDB(&vsdb)) != 0) { warnx("couldn't access volume status database: %s", strerror(result)); return result; }; if ((result = GetVolumeStatusDBEntry(vsdb, &targetuuid, &volstatus)) != 0) { printf("No entry found for '%s'.\n", path); goto Std_Exit; }; if (volstatus & VOLUME_USEPERMISSIONS) { printf("Permissions on '%s' are enabled.\n", path); } else { printf("Permissions on '%s' are disabled.\n", path); }; Std_Exit: (void)CloseVolumeStatusDB(vsdb); return result; } static int isVolumeHFS (const char* path) { /* default to no */ int result = 0; int isHFS = 0; struct statfs statfs_buf; result = statfs (path, &statfs_buf); if (result == 0) { if (!strncmp(statfs_buf.f_fstypename, gHFSTypeName, 3)) { isHFS = 1; } } return isHFS; } //struct definition for calling getattrlist for finderinfos typedef struct FinderInfoBuf { uint32_t info_length; uint32_t finderinfo[8]; } FinderInfoBuf_t; typedef struct hfsUUID { uint32_t high; uint32_t low; } hfsUUID_t; /* -- GetVolumeUUID -- -- Returns: error code (0 if successful). */ static int GetVolumeUUID(const char *path, VolumeUUID *volumeUUIDPtr) { struct attrlist alist; struct { uint32_t size; uuid_t uuid; } volUUID; FinderInfoBuf_t finfo; int result; /* * For a bit more definition on why we have to do this, check out * hfs.util source. The gist is that IFF the volume is HFS, then * we must check the finderinfo UUID FIRST before querying the * fs for its full UUID via the getattrlist volume call. */ if (isVolumeHFS(path)) { /* then go get the finderinfo, first... */ memset (&alist, 0, sizeof(alist)); alist.bitmapcount = ATTR_BIT_MAP_COUNT; alist.reserved = 0; alist.commonattr = ATTR_CMN_FNDRINFO; alist.volattr = ATTR_VOL_INFO; alist.dirattr = 0; alist.fileattr = 0; alist.forkattr = 0; result = getattrlist (path, &alist, &finfo, sizeof(finfo), 0); if (result) { warn ("failed to getattrlist finderinfo for %s", path); result = errno; goto Err_Exit; } hfsUUID_t* hfs_finfo_uuid = (hfsUUID_t*)(&finfo.finderinfo[6]); /* * Note: this is a bit of HFS-specific chicanery. When HFS+ generates * the volume UUID, the formula it uses to generate the 8 bytes of internal * UUID is re-looped/restarted if either high or low is zero. Thus, if we * see either word as '0' then that means we should treat it as an uninitialized * UUID. * * As a result, if we see either word as zero, then clear out the caller's buffer * and return the NULL UUID. Otherwise, we'd get the 8 bytes which potentially include * one or more zeroes run through HFS+'s MD5 algorithm which is not what we want. */ //technically should endian-swap this but not necessary here if ((hfs_finfo_uuid->high == 0) || (hfs_finfo_uuid->low == 0)) { uuid_clear (volumeUUIDPtr->uuid); return 0; } } /* Set up the attrlist structure to get the volume's UUID: */ alist.bitmapcount = ATTR_BIT_MAP_COUNT; alist.reserved = 0; alist.commonattr = 0; alist.volattr = (ATTR_VOL_INFO | ATTR_VOL_UUID); alist.dirattr = 0; alist.fileattr = 0; alist.forkattr = 0; result = getattrlist(path, &alist, &volUUID, sizeof(volUUID), 0); if (result) { warn("Couldn't get volume information for '%s'", path); result = errno; goto Err_Exit; } uuid_copy(volumeUUIDPtr->uuid, volUUID.uuid); result = 0; Err_Exit: return result; }; /****************************************************************************** * * 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 * *****************************************************************************/ #define DBHANDLESIGNATURE 0x75917737 /* Flag values for operation options: */ #define DBMARKPOSITION 1 static char gVSDBPath[] = "/var/db/volinfo.database"; #define MAXIOMALLOC 16384 /* Database layout: */ struct VSDBKey { char uuidString[36]; }; struct VSDBRecord { char statusFlags[8]; }; struct VSDBEntry { struct VSDBKey key; char keySeparator; char space; struct VSDBRecord record; char terminator; }; struct VSDBKey64 { char uuid[16]; }; struct VSDBEntry64 { struct VSDBKey64 key; char keySeparator; char space; struct VSDBRecord record; char terminator; }; #define DBKEYSEPARATOR ':' #define DBBLANKSPACE ' ' #define DBRECORDTERMINATOR '\n' /* In-memory data structures: */ struct VSDBState { u_int32_t signature; int dbfile; int dbmode; off_t recordPosition; }; typedef struct VSDBState *VSDBStatePtr; /* Internal function prototypes: */ static int LockDB(VSDBStatePtr dbstateptr, int lockmode); static int UnlockDB(VSDBStatePtr dbstateptr); static int FindVolumeRecordByUUID(VSDBStatePtr dbstateptr, VolumeUUID *volumeID, struct VSDBEntry *dbentry, u_int32_t options); static int AddVolumeRecord(VSDBStatePtr dbstateptr, struct VSDBEntry *dbentry); static int UpdateVolumeRecord(VSDBStatePtr dbstateptr, struct VSDBEntry *dbentry); static int GetVSDBEntry(VSDBStatePtr dbstateptr, struct VSDBEntry *dbentry); static int CompareVSDBKeys(struct VSDBKey *key1, struct VSDBKey *key2); static void FormatULong(u_int32_t u, char *s); static void FormatDBKey(VolumeUUID *volumeID, struct VSDBKey *dbkey); static void FormatDBRecord(u_int32_t volumeStatusFlags, struct VSDBRecord *dbrecord); static void FormatDBEntry(VolumeUUID *volumeID, u_int32_t volumeStatusFlags, struct VSDBEntry *dbentry); static u_int32_t ConvertHexStringToULong(const char *hs, long maxdigits); /****************************************************************************** * * 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 * *****************************************************************************/ void ConvertVolumeUUIDString64ToUUID(const char *UUIDString64, VolumeUUID *volumeID) { int i; char c; u_int32_t nextdigit; u_int32_t high = 0; u_int32_t low = 0; u_int32_t carry; MD5_CTX ctx; for (i = 0; (i < VOLUMEUUID64LENGTH) && ((c = UUIDString64[i]) != (char)0) ; ++i) { if ((c >= '0') && (c <= '9')) { nextdigit = c - '0'; } else if ((c >= 'A') && (c <= 'F')) { nextdigit = c - 'A' + 10; } else if ((c >= 'a') && (c <= 'f')) { nextdigit = c - 'a' + 10; } else { nextdigit = 0; }; carry = ((low & 0xF0000000) >> 28) & 0x0000000F; high = (high << 4) | carry; low = (low << 4) | nextdigit; }; high = OSSwapHostToBigInt32(high); low = OSSwapHostToBigInt32(low); MD5_Init(&ctx); MD5_Update(&ctx, kFSUUIDNamespaceSHA1, sizeof(uuid_t)); MD5_Update(&ctx, &high, sizeof(high)); MD5_Update(&ctx, &low, sizeof(low)); MD5_Final(volumeID->uuid, &ctx); volumeID->uuid[6] = (volumeID->uuid[6] & 0x0F) | 0x30; volumeID->uuid[8] = (volumeID->uuid[8] & 0x3F) | 0x80; } int OpenVolumeStatusDB(VolumeStatusDBHandle *DBHandlePtr) { VSDBStatePtr dbstateptr; *DBHandlePtr = NULL; dbstateptr = (VSDBStatePtr)malloc(sizeof(*dbstateptr)); if (dbstateptr == NULL) { return ENOMEM; }; dbstateptr->dbmode = O_RDWR; dbstateptr->dbfile = open(gVSDBPath, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); if (dbstateptr->dbfile == -1) { /* The file couldn't be opened for read/write access: try read-only access before giving up altogether. */ dbstateptr->dbmode = O_RDONLY; dbstateptr->dbfile = open(gVSDBPath, O_RDONLY | O_CREAT, S_IRUSR | S_IWUSR); if (dbstateptr->dbfile == -1) { return errno; }; }; dbstateptr->signature = DBHANDLESIGNATURE; *DBHandlePtr = (VolumeStatusDBHandle)dbstateptr; ConvertVolumeStatusDB(*DBHandlePtr); return 0; } int ConvertVolumeStatusDB(VolumeStatusDBHandle DBHandle) { VSDBStatePtr dbstateptr = (VSDBStatePtr)DBHandle; struct VSDBEntry64 entry64; struct stat dbinfo; int result; u_int32_t iobuffersize; void *iobuffer = NULL; int i; if (dbstateptr->signature != DBHANDLESIGNATURE) return EINVAL; if ((result = LockDB(dbstateptr, LOCK_EX)) != 0) return result; lseek(dbstateptr->dbfile, 0, SEEK_SET); result = read(dbstateptr->dbfile, &entry64, sizeof(entry64)); if ((result != sizeof(entry64)) || (entry64.keySeparator != DBKEYSEPARATOR) || (entry64.space != DBBLANKSPACE) || (entry64.terminator != DBRECORDTERMINATOR)) { result = 0; goto ErrExit; } else { if ((result = stat(gVSDBPath, &dbinfo)) != 0) goto ErrExit; iobuffersize = dbinfo.st_size; iobuffer = malloc(iobuffersize); if (iobuffer == NULL) { result = ENOMEM; goto ErrExit; }; lseek(dbstateptr->dbfile, 0, SEEK_SET); result = read(dbstateptr->dbfile, iobuffer, iobuffersize); if (result != iobuffersize) { result = errno; goto ErrExit; }; if ((result = ftruncate(dbstateptr->dbfile, 0)) != 0) { goto ErrExit; }; for (i = 0; i < iobuffersize / sizeof(entry64); i++) { VolumeUUID volumeID; u_int32_t VolumeStatus; struct VSDBEntry dbentry; entry64 = *(((struct VSDBEntry64 *)iobuffer) + i); if ((entry64.keySeparator != DBKEYSEPARATOR) || (entry64.space != DBBLANKSPACE) || (entry64.terminator != DBRECORDTERMINATOR)) { continue; } ConvertVolumeUUIDString64ToUUID(entry64.key.uuid, &volumeID); VolumeStatus = ConvertHexStringToULong(entry64.record.statusFlags, sizeof(entry64.record.statusFlags)); FormatDBEntry(&volumeID, VolumeStatus, &dbentry); if ((result = AddVolumeRecord(dbstateptr, &dbentry)) != sizeof(dbentry)) { warnx("couldn't convert volume status database: %s", strerror(result)); goto ErrExit; }; }; fsync(dbstateptr->dbfile); result = 0; }; ErrExit: if (iobuffer) free(iobuffer); UnlockDB(dbstateptr); return result; } int GetVolumeStatusDBEntry(VolumeStatusDBHandle DBHandle, VolumeUUID *volumeID, u_int32_t *VolumeStatus) { VSDBStatePtr dbstateptr = (VSDBStatePtr)DBHandle; struct VSDBEntry dbentry; int result; if (dbstateptr->signature != DBHANDLESIGNATURE) return EINVAL; if ((result = LockDB(dbstateptr, LOCK_SH)) != 0) return result; if ((result = FindVolumeRecordByUUID(dbstateptr, volumeID, &dbentry, 0)) != 0) { goto ErrExit; }; *VolumeStatus = ConvertHexStringToULong(dbentry.record.statusFlags, sizeof(dbentry.record.statusFlags)); result = 0; ErrExit: UnlockDB(dbstateptr); return result; } int SetVolumeStatusDBEntry(VolumeStatusDBHandle DBHandle, VolumeUUID *volumeID, u_int32_t VolumeStatus) { VSDBStatePtr dbstateptr = (VSDBStatePtr)DBHandle; struct VSDBEntry dbentry; int result; if (dbstateptr->signature != DBHANDLESIGNATURE) return EINVAL; if (VolumeStatus & ~VOLUME_VALIDSTATUSBITS) return EINVAL; if ((result = LockDB(dbstateptr, LOCK_EX)) != 0) return result; FormatDBEntry(volumeID, VolumeStatus, &dbentry); if ((result = FindVolumeRecordByUUID(dbstateptr, volumeID, NULL, DBMARKPOSITION)) == 0) { result = UpdateVolumeRecord(dbstateptr, &dbentry); } else if (result == -1) { result = AddVolumeRecord(dbstateptr, &dbentry); } else { goto ErrExit; }; fsync(dbstateptr->dbfile); result = 0; ErrExit: UnlockDB(dbstateptr); return result; } int DeleteVolumeStatusDBEntry(VolumeStatusDBHandle DBHandle, VolumeUUID *volumeID) { VSDBStatePtr dbstateptr = (VSDBStatePtr)DBHandle; struct stat dbinfo; int result; u_int32_t iobuffersize; void *iobuffer = NULL; off_t dataoffset; u_int32_t iotransfersize; u_int32_t bytestransferred; if (dbstateptr->signature != DBHANDLESIGNATURE) return EINVAL; if ((result = LockDB(dbstateptr, LOCK_EX)) != 0) return result; if ((result = FindVolumeRecordByUUID(dbstateptr, volumeID, NULL, DBMARKPOSITION)) != 0) { if (result == -1) result = 0; /* Entry wasn't in the database to begin with? */ goto StdEdit; } else { if ((result = stat(gVSDBPath, &dbinfo)) != 0) goto ErrExit; if ((dbinfo.st_size - dbstateptr->recordPosition - sizeof(struct VSDBEntry)) <= MAXIOMALLOC) { iobuffersize = dbinfo.st_size - dbstateptr->recordPosition - sizeof(struct VSDBEntry); } else { iobuffersize = MAXIOMALLOC; }; if (iobuffersize > 0) { iobuffer = malloc(iobuffersize); if (iobuffer == NULL) { result = ENOMEM; goto ErrExit; }; dataoffset = dbstateptr->recordPosition + sizeof(struct VSDBEntry); do { iotransfersize = dbinfo.st_size - dataoffset; if (iotransfersize > 0) { if (iotransfersize > iobuffersize) iotransfersize = iobuffersize; lseek(dbstateptr->dbfile, dataoffset, SEEK_SET); bytestransferred = read(dbstateptr->dbfile, iobuffer, iotransfersize); if (bytestransferred != iotransfersize) { result = errno; goto ErrExit; }; lseek(dbstateptr->dbfile, dataoffset - (off_t)sizeof(struct VSDBEntry), SEEK_SET); bytestransferred = write(dbstateptr->dbfile, iobuffer, iotransfersize); if (bytestransferred != iotransfersize) { result = errno; goto ErrExit; }; dataoffset += (off_t)iotransfersize; }; } while (iotransfersize > 0); }; if ((result = ftruncate(dbstateptr->dbfile, dbinfo.st_size - (off_t)(sizeof(struct VSDBEntry)))) != 0) { goto ErrExit; }; fsync(dbstateptr->dbfile); result = 0; }; ErrExit: if (iobuffer) free(iobuffer); UnlockDB(dbstateptr); StdEdit: return result; } int CloseVolumeStatusDB(VolumeStatusDBHandle DBHandle) { VSDBStatePtr dbstateptr = (VSDBStatePtr)DBHandle; if (dbstateptr->signature != DBHANDLESIGNATURE) return EINVAL; dbstateptr->signature = 0; close(dbstateptr->dbfile); /* Nothing we can do about any errors... */ dbstateptr->dbfile = 0; free(dbstateptr); return 0; } /****************************************************************************** * * 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 * *****************************************************************************/ static int LockDB(VSDBStatePtr dbstateptr, int lockmode) { return flock(dbstateptr->dbfile, lockmode); } static int UnlockDB(VSDBStatePtr dbstateptr) { return flock(dbstateptr->dbfile, LOCK_UN); } static int FindVolumeRecordByUUID(VSDBStatePtr dbstateptr, VolumeUUID *volumeID, struct VSDBEntry *targetEntry, u_int32_t options) { struct VSDBKey searchkey; struct VSDBEntry dbentry; int result; FormatDBKey(volumeID, &searchkey); lseek(dbstateptr->dbfile, 0, SEEK_SET); do { result = GetVSDBEntry(dbstateptr, &dbentry); if ((result == 0) && (CompareVSDBKeys(&dbentry.key, &searchkey) == 0)) { if (targetEntry != NULL) { memcpy(targetEntry, &dbentry, sizeof(*targetEntry)); }; return 0; }; } while (result == 0); return -1; } static int AddVolumeRecord(VSDBStatePtr dbstateptr , struct VSDBEntry *dbentry) { lseek(dbstateptr->dbfile, 0, SEEK_END); return write(dbstateptr->dbfile, dbentry, sizeof(struct VSDBEntry)); } static int UpdateVolumeRecord(VSDBStatePtr dbstateptr, struct VSDBEntry *dbentry) { lseek(dbstateptr->dbfile, dbstateptr->recordPosition, SEEK_SET); return write(dbstateptr->dbfile, dbentry, sizeof(*dbentry)); } static int GetVSDBEntry(VSDBStatePtr dbstateptr, struct VSDBEntry *dbentry) { struct VSDBEntry entry; int result; dbstateptr->recordPosition = lseek(dbstateptr->dbfile, 0, SEEK_CUR); result = read(dbstateptr->dbfile, &entry, sizeof(entry)); if ((result != sizeof(entry)) || (entry.keySeparator != DBKEYSEPARATOR) || (entry.space != DBBLANKSPACE) || (entry.terminator != DBRECORDTERMINATOR)) { return -1; }; memcpy(dbentry, &entry, sizeof(*dbentry)); return 0; }; static int CompareVSDBKeys(struct VSDBKey *key1, struct VSDBKey *key2) { return memcmp(key1->uuidString, key2->uuidString, sizeof(key1->uuidString)); } /****************************************************************************** * * 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 * *****************************************************************************/ static void FormatULong(u_int32_t u, char *s) { u_int32_t d; int i; char *digitptr = s; for (i = 0; i < 8; ++i) { d = ((u & 0xF0000000) >> 28) & 0x0000000F; if (d < 10) { *digitptr++ = (char)(d + '0'); } else { *digitptr++ = (char)(d - 10 + 'A'); }; u = u << 4; }; } static void FormatDBKey(VolumeUUID *volumeID, struct VSDBKey *dbkey) { uuid_string_t uuid_str; uuid_unparse(volumeID->uuid, uuid_str); memcpy(dbkey->uuidString, uuid_str, sizeof(dbkey->uuidString)); } static void FormatDBRecord(u_int32_t volumeStatusFlags, struct VSDBRecord *dbrecord) { FormatULong(volumeStatusFlags, dbrecord->statusFlags); } static void FormatDBEntry(VolumeUUID *volumeID, u_int32_t volumeStatusFlags, struct VSDBEntry *dbentry) { FormatDBKey(volumeID, &dbentry->key); dbentry->keySeparator = DBKEYSEPARATOR; dbentry->space = DBBLANKSPACE; FormatDBRecord(volumeStatusFlags, &dbentry->record); dbentry->terminator = DBRECORDTERMINATOR; } static u_int32_t ConvertHexStringToULong(const char *hs, long maxdigits) { int i; char c; u_int32_t nextdigit; u_int32_t n; n = 0; for (i = 0; (i < 8) && ((c = hs[i]) != (char)0) ; ++i) { if ((c >= '0') && (c <= '9')) { nextdigit = c - '0'; } else if ((c >= 'A') && (c <= 'F')) { nextdigit = c - 'A' + 10; } else if ((c >= 'a') && (c <= 'f')) { nextdigit = c - 'a' + 10; } else { nextdigit = 0; }; n = (n << 4) + nextdigit; }; return n; }