diff options
Diffstat (limited to 'diskdev_cmds/vsdbutil.tproj')
-rw-r--r-- | diskdev_cmds/vsdbutil.tproj/com.apple.vsdbutil.plist | 21 | ||||
-rw-r--r-- | diskdev_cmds/vsdbutil.tproj/vsdbutil.8 | 49 | ||||
-rw-r--r-- | diskdev_cmds/vsdbutil.tproj/vsdbutil_main.c | 1213 |
3 files changed, 1283 insertions, 0 deletions
diff --git a/diskdev_cmds/vsdbutil.tproj/com.apple.vsdbutil.plist b/diskdev_cmds/vsdbutil.tproj/com.apple.vsdbutil.plist new file mode 100644 index 0000000..18bf10e --- /dev/null +++ b/diskdev_cmds/vsdbutil.tproj/com.apple.vsdbutil.plist @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>Label</key> + <string>com.apple.vsdbutil</string> + <key>ProgramArguments</key> + <array> + <string>/usr/sbin/vsdbutil</string> + <string>-i</string> + </array> + <key>KeepAlive</key> + <dict> + <key>PathState</key> + <dict> + <key>/var/db/volinfo.database</key> + <false/> + </dict> + </dict> +</dict> +</plist> diff --git a/diskdev_cmds/vsdbutil.tproj/vsdbutil.8 b/diskdev_cmds/vsdbutil.tproj/vsdbutil.8 new file mode 100644 index 0000000..1be7d10 --- /dev/null +++ b/diskdev_cmds/vsdbutil.tproj/vsdbutil.8 @@ -0,0 +1,49 @@ +.Dd December 19, 2001 +.Dt VSDBUTIL 8 +.Os Darwin +.Sh NAME +.Nm vsdbutil +.Nd manipulates the volume status DB. +.Sh SYNOPSIS +.Nm +.Op Fl a Ar path +.Pp +.Nm +.Op Fl c Ar path +.Op Fl d Ar path +.Op Fl i +.Pp +.Nm +.Op Fl h +.Sh DESCRIPTION +.Nm +manipulates the volume status DB. +The following options are available: +.Bl -tag -width -indent +.It Fl a +adopts (activates) on-disk ownership on the specified path +.It Fl c +checks the status of the ownership usage on the specified path +.It Fl d +disowns (deactivates) the on-disk ownership on the specified path +.It Fl i +initializes the ownership database to include all mounted HFS+ and APFS volumes +.It Fl h +prints out a simple help message +.El +.Pp +The +.Nm +command is deprecated; using a volume UUID in +.Xr fstab 5 +is preferred. +.Sh FILES +.Bl -tag -width "/Users/joeuser/Library/really_long_file_name" -compact +.It Pa /var/db/volinfo.database +Database of volumes managed via +.Nm . +.El +.Sh SEE ALSO +.Xr diskutil 8 , +.Xr mount 8 , +.Xr fstab 5 diff --git a/diskdev_cmds/vsdbutil.tproj/vsdbutil_main.c b/diskdev_cmds/vsdbutil.tproj/vsdbutil_main.c new file mode 100644 index 0000000..c9647fe --- /dev/null +++ b/diskdev_cmds/vsdbutil.tproj/vsdbutil_main.c @@ -0,0 +1,1213 @@ +/* + * 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 <sys/types.h> +#include <sys/param.h> +#include <sys/mount.h> +#include <sys/stat.h> +#include <sys/sysctl.h> +#include <sys/time.h> +#include <sys/ucred.h> +#include <sys/resource.h> +#include <sys/vmmeter.h> +#include <sys/wait.h> +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <fcntl.h> +#include <err.h> +#include <errno.h> +#include <dirent.h> +#include <strings.h> + +#include <sys/attr.h> +#include <uuid/uuid.h> +#include <System/uuid/namespace.h> + +// 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 <CommonCrypto/CommonDigest.h> +#include <libkern/OSByteOrder.h> + +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; +} |