mirror of
https://github.com/CloverHackyColor/CloverBootloader.git
synced 2024-11-30 12:43:41 +01:00
7c0aa811ec
Signed-off-by: Sergey Isakov <isakov-sl@bk.ru>
617 lines
19 KiB
C
617 lines
19 KiB
C
/*
|
||
* 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;
|
||
} |