This is xnu-11215.1.10. See this file in:
#include <darwintest.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "nvram_helper.h"
// Ascii value of 'A' (65) - Ascii value of '9' (57)
#define ASCII_OFFSET 7
#define NVRAM_BYTE_LEN 3
// NVRAM helper functions from https://stashweb.sd.apple.com/projects/COREOS/repos/system_cmds/browse/nvram.tproj/nvram.c
/**
* @brief Print the given firmware variable.
*/
static void
PrintVariable(const void *key, const void *value)
{
if (CFGetTypeID(key) != CFStringGetTypeID()) {
printf("Variable name passed in isn't a string");
return;
}
long cnt, cnt2;
CFIndex nameLen;
char *nameBuffer = 0;
const char *nameString;
char numberBuffer[10];
const uint8_t *dataPtr;
uint8_t dataChar;
char *dataBuffer = 0;
CFIndex valueLen;
char *valueBuffer = 0;
const char *valueString = 0;
uint32_t number;
long length;
CFTypeID typeID;
// Get the variable's name.
nameLen = CFStringGetMaximumSizeForEncoding(CFStringGetLength(key), kCFStringEncodingUTF8) + 1;
nameBuffer = malloc(nameLen);
if (nameBuffer && CFStringGetCString(key, nameBuffer, nameLen, kCFStringEncodingUTF8)) {
nameString = nameBuffer;
} else {
printf("Unable to convert property name to C string");
nameString = "<UNPRINTABLE>";
}
// Get the variable's type.
typeID = CFGetTypeID(value);
if (typeID == CFBooleanGetTypeID()) {
if (CFBooleanGetValue(value)) {
valueString = "true";
} else {
valueString = "false";
}
} else if (typeID == CFNumberGetTypeID()) {
CFNumberGetValue(value, kCFNumberSInt32Type, &number);
if (number == 0xFFFFFFFF) {
sprintf(numberBuffer, "-1");
} else {
sprintf(numberBuffer, "0x%x", number);
}
valueString = numberBuffer;
} else if (typeID == CFStringGetTypeID()) {
valueLen = CFStringGetMaximumSizeForEncoding(CFStringGetLength(value),
kCFStringEncodingUTF8) +
1;
valueBuffer = malloc(valueLen + 1);
if (valueBuffer && CFStringGetCString(value, valueBuffer, valueLen, kCFStringEncodingUTF8)) {
valueString = valueBuffer;
} else {
printf("Unable to convert value to C string");
valueString = "<UNPRINTABLE>";
}
} else if (typeID == CFDataGetTypeID()) {
length = CFDataGetLength(value);
if (length == 0) {
valueString = "";
} else {
dataBuffer = malloc(length * NVRAM_BYTE_LEN + NVRAM_BYTE_LEN);
if (dataBuffer != 0) {
dataPtr = CFDataGetBytePtr(value);
cnt = cnt2 = 0;
for (; cnt < length; cnt++) {
dataChar = dataPtr[cnt];
if (isprint(dataChar) && dataChar != '%') {
dataBuffer[cnt2++] = dataChar;
} else {
sprintf(dataBuffer + cnt2, "%%%02x", dataChar);
cnt2 += NVRAM_BYTE_LEN;
}
}
dataBuffer[cnt2] = '\0';
valueString = dataBuffer;
}
}
} else {
valueString = "<INVALID>";
}
if ((nameString != 0) && (valueString != 0)) {
printf("%s\t%s\n", nameString, valueString);
}
if (dataBuffer != 0) {
free(dataBuffer);
}
if (nameBuffer != 0) {
free(nameBuffer);
}
if (valueBuffer != 0) {
free(valueBuffer);
}
}
/**
* @brief Convert the value into a CFType given the typeID
*/
static CFTypeRef
ConvertValueToCFTypeRef(CFTypeID typeID, const char *value)
{
CFTypeRef valueRef = 0;
long cnt, cnt2, length;
unsigned long number, tmp;
if (typeID == CFBooleanGetTypeID()) {
if (value == NULL) {
return valueRef;
}
if (!strcmp("true", value)) {
valueRef = kCFBooleanTrue;
} else if (!strcmp("false", value)) {
valueRef = kCFBooleanFalse;
}
} else if (typeID == CFNumberGetTypeID()) {
number = strtol(value, 0, 0);
valueRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type,
&number);
} else if (typeID == CFStringGetTypeID()) {
valueRef = CFStringCreateWithCString(kCFAllocatorDefault, value,
kCFStringEncodingUTF8);
} else if (typeID == CFDataGetTypeID()) {
if (value == NULL) {
length = 0;
} else {
length = strlen(value);
}
char valueCopy[length + 1];
for (cnt = cnt2 = 0; cnt < length; cnt++, cnt2++) {
if (value[cnt] == '%') {
if ((cnt + 2 > length) ||
!ishexnumber(value[cnt + 1]) ||
!ishexnumber(value[cnt + 2])) {
return 0;
}
number = toupper(value[++cnt]) - '0';
if (number > 9) {
number -= ASCII_OFFSET;
}
tmp = toupper(value[++cnt]) - '0';
if (tmp > 9) {
tmp -= ASCII_OFFSET;
}
number = (number << 4) + tmp;
valueCopy[cnt2] = number;
} else {
valueCopy[cnt2] = value[cnt];
}
}
valueRef = CFDataCreate(kCFAllocatorDefault, (const UInt8 *)valueCopy, cnt2);
} else {
return 0;
}
return valueRef;
}
/**
* @brief Prints the variable. returns kIOReturnNotFound if not found
*/
static kern_return_t
GetVariable(const char *name, io_registry_entry_t optionsRef)
{
CFStringRef nameRef = NULL;
CFTypeRef valueRef = NULL;
nameRef = CFStringCreateWithCString(kCFAllocatorDefault, name,
kCFStringEncodingUTF8);
if (nameRef == NULL) {
printf("Error creating CFString for key %s", name);
return KERN_FAILURE;
}
valueRef = IORegistryEntryCreateCFProperty(optionsRef, nameRef, 0, 0);
if (valueRef == NULL) {
CFRelease(nameRef);
return kIOReturnNotFound;
}
PrintVariable(nameRef, valueRef);
CFRelease(nameRef);
CFRelease(valueRef);
return KERN_SUCCESS;
}
/**
* @brief Returns variable type. 0xFF if variable doesn't exist or on error creating CFString
*/
CFTypeID
GetVarType(const char *name, io_registry_entry_t optionsRef)
{
CFStringRef nameRef = NULL;
CFTypeRef valueRef = NULL;
CFTypeID typeID = 0;
nameRef = CFStringCreateWithCString(kCFAllocatorDefault, name,
kCFStringEncodingUTF8);
if (nameRef != NULL) {
valueRef = IORegistryEntryCreateCFProperty(optionsRef, nameRef, 0, 0);
CFRelease(nameRef);
if (valueRef != NULL) {
typeID = CFGetTypeID(valueRef);
CFRelease(valueRef);
}
}
return typeID;
}
/**
* @brief Set the named variable with the value passed in
*/
static kern_return_t
SetVariable(const char *name, const char *value, io_registry_entry_t optionsRef)
{
CFStringRef nameRef;
CFTypeRef valueRef;
CFTypeID typeID;
kern_return_t result = KERN_FAILURE;
nameRef = CFStringCreateWithCString(kCFAllocatorDefault, name,
kCFStringEncodingUTF8);
if (nameRef == 0) {
printf("Error creating CFString for key %s", name);
return result;
}
valueRef = IORegistryEntryCreateCFProperty(optionsRef, nameRef, 0, 0);
if (valueRef) {
typeID = CFGetTypeID(valueRef);
CFRelease(valueRef);
valueRef = ConvertValueToCFTypeRef(typeID, value);
if (valueRef == 0) {
printf("Error creating CFTypeRef for value %s", value);
return result;
}
result = IORegistryEntrySetCFProperty(optionsRef, nameRef, valueRef);
} else {
// if it's one of the delete/sync keys, it has to be an OSString since the "value" will be the variable name.
if (strncmp(name, kIONVRAMDeletePropertyKey, strlen(kIONVRAMDeletePropertyKey)) == 0 ||
strncmp(name, kIONVRAMDeletePropertyKeyWRet, strlen(kIONVRAMDeletePropertyKeyWRet)) == 0 ||
strncmp(name, kIONVRAMSyncNowPropertyKey, strlen(kIONVRAMSyncNowPropertyKey)) == 0) {
valueRef = ConvertValueToCFTypeRef(CFStringGetTypeID(), value);
if (valueRef != 0) {
result = IORegistryEntrySetCFProperty(optionsRef, nameRef, valueRef);
}
} else {
// In the default case, try data, string, number, then boolean.
CFTypeID types[] = {CFDataGetTypeID(),
CFStringGetTypeID(), CFNumberGetTypeID(), CFBooleanGetTypeID()};
for (unsigned long i = 0; i < sizeof(types) / sizeof(types[0]); i++) {
valueRef = ConvertValueToCFTypeRef(types[i], value);
if (valueRef != 0) {
result = IORegistryEntrySetCFProperty(optionsRef, nameRef, valueRef);
if (result == KERN_SUCCESS || result == kIOReturnNoMemory || result == kIOReturnNoSpace) {
break;
}
}
}
}
}
CFRelease(nameRef);
return result;
}
/**
* @brief Delete named variable
*/
static kern_return_t
DeleteVariable(const char *name, io_registry_entry_t optionsRef)
{
// Since delete always returns ok, read to make sure it is deleted.
if (SetVariable(kIONVRAMDeletePropertyKey, name, optionsRef) == KERN_SUCCESS) {
if (GetVariable(name, optionsRef) == kIOReturnNotFound) {
return KERN_SUCCESS;
}
}
return KERN_FAILURE;
}
/**
* @brief Delete named variable with return code
*/
static kern_return_t
DeleteVariableWRet(const char *name, io_registry_entry_t optionsRef)
{
return SetVariable(kIONVRAMDeletePropertyKeyWRet, name, optionsRef);
}
/**
* @brief Sync to nvram store
*/
static kern_return_t
SyncNVRAM(const char *name, io_registry_entry_t optionsRef)
{
return SetVariable(name, name, optionsRef);
}
/**
* @brief Get the Options object
*/
io_registry_entry_t
CreateOptionsRef(void)
{
io_registry_entry_t optionsRef = IORegistryEntryFromPath(kIOMainPortDefault, "IODeviceTree:/options");
T_ASSERT_NE(optionsRef, IO_OBJECT_NULL, "Got options");
return optionsRef;
}
/**
* @brief Release option object passed in
*/
void
ReleaseOptionsRef(io_registry_entry_t optionsRef)
{
if (optionsRef != IO_OBJECT_NULL) {
IOObjectRelease(optionsRef);
}
}
static const char *
GetOpString(nvram_op op)
{
switch (op) {
case OP_GET:
return "read";
case OP_SET:
return "write";
case OP_DEL:
case OP_DEL_RET:
return "delete";
case OP_RES:
return "reset";
case OP_OBL:
return "obliterate";
case OP_SYN:
return "sync";
default:
return "unknown";
}
}
static const char *
GetRetString(kern_return_t ret)
{
switch (ret) {
case KERN_SUCCESS:
return "success";
case KERN_FAILURE:
return "failure";
case kIOReturnNotPrivileged:
return "not privileged";
case kIOReturnError:
return "general error";
default:
return "unknown";
}
}
/**
* @brief Tests get/set/delete/reset variable
*/
void
TestVarOp(nvram_op op, const char *var, const char *val, kern_return_t exp_ret, io_registry_entry_t optionsRef)
{
kern_return_t ret = KERN_FAILURE;
if (var == NULL && (op != OP_RES)) {
return;
}
switch (op) {
case OP_SET:
ret = SetVariable(var, val, optionsRef);
break;
case OP_GET:
ret = GetVariable(var, optionsRef);
break;
case OP_DEL:
ret = DeleteVariable(var, optionsRef);
break;
case OP_DEL_RET:
ret = DeleteVariableWRet(var, optionsRef);
break;
case OP_RES:
ret = SetVariable("ResetNVRam", "1", optionsRef);
break;
case OP_SYN:
ret = SyncNVRAM(var, optionsRef);
break;
case OP_OBL:
// Obliterate NVram (system guid deletes all variables in system region, common guid deletes all non-system variables)
ret = SetVariable(var, "1", optionsRef);
break;
default:
T_FAIL("TestVarOp: Invalid NVRAM operation %d\n", op);
return;
}
// Use kIOReturnInvalid as don't care about return value.
if (exp_ret == kIOReturnInvalid) {
T_PASS("Operation %s for variable %s returned %s(%#x) but doesn't have an expected return\n", GetOpString(op), var, GetRetString(ret), ret);
return;
}
// Allow passing in a value other than KERN_SUCCESS || KERN_FAILURE to assert against
// otherwise remain as pass/fail
if ((exp_ret == KERN_SUCCESS) || (exp_ret == KERN_FAILURE)) {
if (ret != KERN_SUCCESS) {
ret = KERN_FAILURE;
}
}
T_ASSERT_EQ(ret, exp_ret, "Operation %s for variable %s returned %s(%#x) as expected\n", GetOpString(op), var, GetRetString(ret), ret);
}