CloverBootloader/CloverPackage/utils/partutil/partutil.c
2019-09-03 12:58:42 +03:00

617 lines
19 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* partutil.c
* partutil
*
* Created by JrCs on December 28th, 2014.
* Copyright (c) 2014-2015 JrCs. All rights reserved.
*/
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <paths.h>
#include <sys/param.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <getopt.h>
#include <IOKit/IOKitLib.h>
#include <IOKit/IOBSD.h>
#include <IOKit/usb/IOUSBLib.h>
#include <IOKit/storage/IOCDMedia.h>
#include <IOKit/storage/IOMedia.h>
#include <IOKit/storage/IOCDTypes.h>
#include <IOKit/storage/IOMediaBSDClient.h>
#include <CoreFoundation/CoreFoundation.h>
#include <DiskArbitration/DiskArbitration.h>
#include "partutil.h"
static char const diskStr[] = "disk";
static __used char const copyright[] = "Copyright 2014-2015 JrCs.";
int verbosity = 0;
typedef enum {
search_uuid=300, // must be > 255
search_undefined
} searchType;
typedef enum {
query_fstype=search_undefined+1,
query_bsdname,
query_mountpoint,
query_volumename,
query_uuid,
query_blocksize,
query_partitionscheme,
query_pbrtype,
query_wholedisk,
query_contenttype,
query_dump,
query_undefined
} queryType;
static char const fat16ID[] = "FAT16 ";
static char const fat32ID[] = "FAT32 ";
static char const exfatID[] = "EXFAT ";
static char const fat16[] = "fat16";
static char const fat32[] = "fat32";
static char const msdos[] = "msdos";
static char const hfs[] = "hfs";
static char const exfat[] = "exfat";
#pragma mark -
#pragma mark DiskArbitration and PBR Helpers
#pragma mark -
static
bool isUUID(const char* string) {
if (strlen(string) == 36 &&
string[8] == '-' && string[13] == '-' &&
string[18] == '-' && string[23] == '-')
return true;
return false;
}
static
char const* toBSDName(char const* pathName)
{
assert(pathName);
char const* bsdName = pathName;
if (strncmp(pathName, _PATH_DEV, sizeof(_PATH_DEV)-1) == 0) {
// Remove /dev/
bsdName=&pathName[sizeof(_PATH_DEV)-1];
}
if (bsdName[0] == 'r') {
// Remove the r (raw device)
bsdName=&bsdName[1];
}
// the devicename must start by the string "disk"
if (strncmp(bsdName, diskStr, sizeof(diskStr)-1) != 0) {
fprintf(stderr,"Error: invalid device name '%s'\n", bsdName);
exit(EXIT_FAILURE);
}
return bsdName;
}
static
bool getPBRType(char const* pathName, char *answer, size_t answer_maxsize) {
const unsigned int bytes_to_read = 4096;
unsigned char buffer[bytes_to_read];
char rawPathName[ MAXPATHLEN ];
if (geteuid() != 0) {
fprintf(stderr, "Error: this program must be run as root to get Filesystem Type on this platform !\n");
return false;
}
assert(pathName);
snprintf(rawPathName,MAXPATHLEN, "%sr%s", _PATH_DEV, toBSDName(pathName));
int fd = open(rawPathName, O_RDONLY);
if (fd < 0) {
fprintf(stderr, "Error: unable to open %s: %s\n", rawPathName, strerror(errno));
return false;
}
ssize_t rc = read(fd, (void *)buffer, bytes_to_read);
close(fd);
if (rc != bytes_to_read) {
fprintf(stderr, "Error: unable to read from %s: %s\n", rawPathName, strerror(errno));
return false;
}
uint16_t fat_bytesPerSector = OSSwapLittleToHostInt16(*(uint16_t const*)&buffer[0xb]);
uint8_t fat_sectorsPerCluster = buffer[0xd];
if (!(fat_bytesPerSector & (fat_bytesPerSector - 1U)) &&
fat_bytesPerSector >= 0x200U && fat_bytesPerSector <= 0x1000U &&
fat_sectorsPerCluster && !(fat_sectorsPerCluster & (fat_sectorsPerCluster - 1U)))
{
if (memcmp(&buffer[0x52], fat32ID, sizeof(fat32ID)-1) == 0) {
strlcpy(answer, fat32, answer_maxsize);
return true;
}
if (memcmp(&buffer[0x36], fat16ID, sizeof(fat16ID)-1) == 0) {
strlcpy(answer, fat16, answer_maxsize);
return true;
}
fprintf(stderr, "Error: unknown %s format\n", msdos);
return false;
}
uint16_t hfs_sig = OSSwapBigToHostInt16(*(uint16_t const*)&buffer[1024]);
if (hfs_sig == 0x4244 /*'BD'*/ ||
hfs_sig == 0x482B /*'H+'*/ ||
hfs_sig == 0x4858 /*'HX'*/) {
strlcpy(answer, hfs, answer_maxsize);
return true;
}
if (memcmp(&buffer[0x3], exfatID, sizeof(exfatID)) == 0) {
strlcpy(answer, exfat, answer_maxsize);
return true;
}
return false;
}
static
bool getFSTypeFromPBR(char const* pathName, char *answer, size_t answer_maxsize) {
bool isSuccess = getPBRType(pathName, answer, answer_maxsize);
if (isSuccess &&
(strncmp(answer, fat32, answer_maxsize) == 0 ||
strncmp(answer, fat16, answer_maxsize) == 0))
strlcpy(answer, msdos, answer_maxsize); // return msdos for fat32 or fat16
return isSuccess;
}
static
int getDASessionAndDisk(char const* pathName, DASessionRef* pSession, DADiskRef* pDisk)
{
DASessionRef session;
DADiskRef disk;
assert(pathName);
session = DASessionCreate(kCFAllocatorDefault);
if (!session) {
fprintf(stderr, "DASessionCreate returned NULL\n");
return -1;
}
disk = DADiskCreateFromBSDName(kCFAllocatorDefault, session, toBSDName(pathName));
if (!disk) {
CFRelease(session);
fprintf(stderr, "DADiskCreateFromBSDName(%s) returned NULL\n", pathName);
return -1;
}
if (pDisk)
*pDisk = disk;
else
CFRelease(disk);
if (pSession)
*pSession = session;
else
CFRelease(session);
return 0;
}
static
bool queryDevice(char const* deviceName, queryType query, char *answer, size_t answer_maxsize)
{
struct stat buf;
DADiskRef disk;
CFDictionaryRef descDict;
CFBooleanRef b_ref;
bool result = false;
int isMediaWhole = 0;
assert(deviceName);
char devFilePath[ MAXPATHLEN ];
char *deviceFilePath = &devFilePath[0];
strlcpy( deviceFilePath, _PATH_DEV, sizeof( devFilePath ) );
strlcat( deviceFilePath, deviceName, sizeof( devFilePath ) );
if (stat(deviceFilePath, &buf) < 0) {
fprintf(stderr, "Error: stat failed on %s: %s\n", deviceFilePath, strerror(errno));
return false;
}
if (!(buf.st_mode & (S_IFCHR | S_IFBLK))) {
fprintf(stderr, "Error: %s is not a block or character special device\n", deviceFilePath);
return false;
}
if (getDASessionAndDisk(deviceName, NULL, &disk) < 0)
return false;
descDict = DADiskCopyDescription(disk);
CFRelease(disk);
if (!descDict) {
return false;
}
if (CFDictionaryGetValueIfPresent(descDict, kDADiskDescriptionMediaWholeKey, (void const**) &b_ref) &&
CFBooleanGetValue(b_ref))
isMediaWhole = 1;
// If query partition scheme, the device must be a whole disk
if (query == query_partitionscheme && !isMediaWhole) {
fprintf(stderr, "Error: device must be a whole disk to query about partition scheme !\n");
CFRelease(descDict);
return false;
}
CFStringRef key = NULL;
switch (query) {
case query_fstype:
key = kDADiskDescriptionVolumeKindKey;
break;
case query_bsdname:
key = kDADiskDescriptionMediaBSDNameKey;
break;
case query_mountpoint:
key = kDADiskDescriptionVolumePathKey;
break;
case query_volumename:
key = kDADiskDescriptionVolumeNameKey;
break;
case query_uuid:
key = kDADiskDescriptionMediaUUIDKey;
break;
case query_blocksize:
key = kDADiskDescriptionMediaBlockSizeKey;
break;
case query_partitionscheme:
key = kDADiskDescriptionMediaContentKey;
break;
case query_wholedisk:
key = kDADiskDescriptionMediaBSDUnitKey;
break;
case query_contenttype:
key = kDADiskDescriptionMediaContentKey;
break;
case query_dump:
CFShow(descDict);
CFRelease(descDict);
return EXIT_SUCCESS;
break;
case query_pbrtype: // never used here
case query_undefined:
break;
}
if (!key) {
CFRelease(descDict);
return false;
}
CFTypeRef valueRef;
if (CFDictionaryGetValueIfPresent(descDict, key, (void const**) &valueRef)) {
// Get the OF variable's type.
CFTypeID typeID = CFGetTypeID(valueRef);
CFStringRef cfstr_value = NULL;
if (typeID == CFStringGetTypeID())
cfstr_value = CFStringCreateCopy(NULL, valueRef); // same string
else if (typeID == CFUUIDGetTypeID())
cfstr_value = CFUUIDCreateString(NULL, valueRef); // convert CFUUIDRef to CFStringRef
else if (typeID == CFNumberGetTypeID())
cfstr_value = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@"), valueRef); // convert CFNumber to CFStringRef
else if (typeID == CFURLGetTypeID())
cfstr_value = CFURLCopyFileSystemPath ( valueRef, kCFURLPOSIXPathStyle ); // convert CFURL to CFStringRef
if (cfstr_value) {
result = CFStringGetCString(cfstr_value, answer, answer_maxsize, kCFStringEncodingUTF8);
CFRelease(cfstr_value);
}
}
CFRelease(descDict);
if (query == query_wholedisk && result == true) {
// append the string "disk" after the BSD unit number
char bsd_unit_number[answer_maxsize];
strlcpy(bsd_unit_number, answer, sizeof(bsd_unit_number));
snprintf(answer, answer_maxsize, "disk%s", bsd_unit_number);
}
if (result == false && query == query_fstype) {
// Try to find the fstype by analysing the PBR of the partition
return getFSTypeFromPBR(deviceName, answer, answer_maxsize);
}
return result;
}
#pragma mark -
#pragma mark Search
#pragma mark -
static
bool doSearch(searchType search, char const* searchValue, char *answer, size_t answer_maxsize)
{
mach_port_t masterPort;
kern_return_t kernResult;
CFMutableDictionaryRef classesToMatch;
io_iterator_t mediaIterator;
assert(searchValue);
kernResult = IOMasterPort( MACH_PORT_NULL, &masterPort );
if ( kernResult != KERN_SUCCESS ) {
fprintf(stderr, "IOMasterPort returned %d\n", kernResult );
return false;
}
classesToMatch = IOServiceMatching( kIOMediaClass ); // All Storage Media
if ( classesToMatch == NULL ) {
fprintf( stderr, "IOServiceMatching returned a NULL dictionary.\n" );
return false;
}
CFDictionarySetValue( classesToMatch,
CFSTR( kIOMediaLeafKey ), kCFBooleanTrue ); // Is a leaf device
CFStringRef searchCFString = CFStringCreateWithCString(NULL, searchValue, kCFStringEncodingASCII);
switch (search) {
case search_uuid:
if ( ! isUUID(searchValue) ) {
fprintf(stderr, "Error: invalid UUID value '%s'\n", searchValue);
CFRelease(searchCFString);
CFRelease(classesToMatch); // Not use so release it
return false;
}
CFMutableStringRef tmpMutableStr = CFStringCreateMutableCopy(0, 36, searchCFString);
CFStringUppercase(tmpMutableStr, 0); // Convert to Uppercase
CFDictionarySetValue( classesToMatch, CFSTR( kIOMediaUUIDKey ), tmpMutableStr);
CFRelease(tmpMutableStr);
break;
case search_undefined:
CFRelease(searchCFString);
CFRelease(classesToMatch); // Not use so release it
return false;
break;
}
CFRelease(searchCFString);
// classesToMatch is consume by IOServiceGetMatchingServices
kernResult = IOServiceGetMatchingServices( masterPort,
classesToMatch, &mediaIterator );
if ( kernResult != KERN_SUCCESS ) {
fprintf(stderr, "No Block Storage media found.\n kernResult = %d\n", kernResult );
return false;
}
io_service_t service;
CFMutableDictionaryRef serviceProperties = NULL;
while ((service = IOIteratorNext( mediaIterator ))) {
kernResult = IORegistryEntryCreateCFProperties(service, &serviceProperties, NULL, 0);
if ( kernResult != KERN_SUCCESS ) {
fprintf(stderr, "Error: IORegistryEntryCreateCFProperties returned %d\n", kernResult );
IOObjectRelease( service );
return false;
}
CFStringRef s_ref;
// get the BSO device name
if (CFDictionaryGetValueIfPresent(serviceProperties, CFSTR( kIOBSDNameKey ), (void const**) &s_ref)) {
CFStringGetCString(s_ref, answer, answer_maxsize, kCFStringEncodingUTF8);
CFRelease(serviceProperties);
IOObjectRelease( service );
return true;
}
CFRelease(serviceProperties);
IOObjectRelease( service );
}
return false;
}
#pragma mark -
#pragma mark Usage
#pragma mark -
//
// display version string
//
void print_version(void) {
printf ("%s " VERSION_S "\n%s\n", PROGNAME_S, copyright);
}
//
// display usage
//
static void
usage (int status)
{
if (status)
fprintf (stderr, "Try ``" PROGNAME_S " --help'' for more information.\n");
else {
print_version();
printf ("\n\
Usage: " PROGNAME_S " [QUERY OPTION] [DEVICE|UUID]\n\
" PROGNAME_S " [SEARCH OPTION]\n\
\n\
Query options:\n\
\t--show-fstype display the filesytem type of the partition\n\
\t--show-bsdname display the device name of the partition\n\
\t--show-mountpoint display the mount point of the partition\n\
\t--show-volumename display the volume name of the partition\n\
\t--show-uuid display the UUID of the partition\n\
\t--show-blocksize display the prefer blocksize of the partition\n\
\t--show-partitionscheme display the partition scheme of a disk\n\
\t--show-contenttype display the content type of the device\n\
\t--show-pbrtype display the filesystem type from the PBR of the device\n\
\t--show-wholedisk display the whole disk of the device\n\
\t--dump dump properties of the partition\n\
\n\
Search options: \n\
\t--search-uuid UUID search a partition with the specific UUID\n\
\n\
Other options:\n\
\t-h, --help display this message and exit\n\
\t-V, --version print version information and exit\n\
\t-v, --verbose print verbose messages\n\
\n\
example: " PROGNAME_S " --show-fstype disk0s4\n\
" PROGNAME_S " --show-bsdname 6A9017D9-2B9E-4786-B0A5-A75BD2264239\n\
" PROGNAME_S " --show-blocksize disk0s4\n\
" PROGNAME_S " --search-uuid 2C97F84A-F488-4917-A312-5D64BAE5BCFC\n");
}
exit (status);
}
#pragma mark -
#pragma mark Main
#pragma mark -
// Options
static struct option options[] =
{
{"search-uuid", required_argument, 0, search_uuid},
{"show-fstype", no_argument, 0, query_fstype},
{"show-bsdname", no_argument, 0, query_bsdname},
{"show-mountpoint", no_argument, 0, query_mountpoint},
{"show-volumename", no_argument, 0, query_volumename},
{"show-uuid", no_argument, 0, query_uuid},
{"show-blocksize", no_argument, 0, query_blocksize},
{"show-partitionscheme", no_argument, 0, query_partitionscheme},
{"show-contenttype", no_argument, 0, query_contenttype},
{"show-pbrtype", no_argument, 0, query_pbrtype},
{"show-wholedisk", no_argument, 0, query_wholedisk},
{"dump", no_argument, 0, query_dump},
{"help", no_argument, 0, 'h'},
{"version", no_argument, 0, 'V'},
{"verbose", no_argument, 0, 'v'},
{0, 0, 0, 0}
};
int main(int argc, char* const argv[])
{
queryType query = query_undefined;
searchType search = search_undefined;
char const *searchValue = NULL;
char answer[64] = "\0";
bool isSuccess = false;
/* Check for options. */
while (1) {
int c = getopt_long (argc, argv, "d:r:hVv", options, 0);
if (c == -1)
break;
else
switch (c) {
case search_uuid:
if (search != search_undefined) {
fprintf(stderr, "Error: only one search can be done !\n");
exit(EXIT_FAILURE);
}
if (query != query_undefined) {
fprintf(stderr, "Error: can't mix query and search !\n");
exit(EXIT_FAILURE);
}
search = c;
searchValue = optarg;
break;
case query_fstype:
case query_bsdname:
case query_mountpoint:
case query_volumename:
case query_uuid:
case query_blocksize:
case query_partitionscheme:
case query_contenttype:
case query_pbrtype:
case query_wholedisk:
case query_dump:
if (query != query_undefined) {
fprintf(stderr, "Error: only one query can be done !\n");
exit(EXIT_FAILURE);
}
if (search != search_undefined) {
fprintf(stderr, "Error: can't mix query and search !\n");
exit(EXIT_FAILURE);
}
query = c;
break;
case 'h':
usage (0);
break;
case 'V':
print_version();
return 0;
case 'v':
verbosity++;
break;
default:
usage (1);
break;
}
}
argc -= optind;
/* check arguments. */
if (query == query_undefined && search == search_undefined)
usage(EXIT_SUCCESS);
if (query != query_undefined && argc < 1) {
fprintf (stderr, "Error: you must specify a device for the query !\n");
usage(EXIT_FAILURE);
}
if ( (query != query_undefined && argc != 1) ||
(search != search_undefined && argc != 0)) {
fprintf(stderr, "Error: too many arguments !\n");
usage(EXIT_FAILURE);
}
if (search != search_undefined) {
isSuccess = doSearch(search, searchValue, answer, sizeof(answer));
}
else {
char const* argument = argv[optind++];
char device[ MAXPATHLEN ];
if (strlen(argument) >= 30) { // It's must be a uuid
if (!isUUID(argument)) {
fprintf(stderr, "Error: invalid UUID value '%s'\n", argument);
return EXIT_FAILURE;
}
// it's an UUID try to get the devicename
isSuccess = doSearch(search_uuid, argument, answer, sizeof(answer));
if (!isSuccess) {
fprintf(stderr,"Error: can't find device with UUID '%s'\n",argument);
return EXIT_FAILURE;
}
strlcpy(device, answer, sizeof(device));
}
else {
strlcpy(device, toBSDName(argument), sizeof(device));
}
if (query == query_pbrtype)
isSuccess = getPBRType(device, answer, sizeof(answer));
else
isSuccess = queryDevice(device, query, answer, sizeof(answer));
}
if (isSuccess)
printf("%s\n", answer);
return isSuccess ? EXIT_SUCCESS : EXIT_FAILURE;
}