/*
 * Copyright (c) 2000-2020 Apple Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 *
 * The contents of this file constitute Original Code as defined in and
 * are subject to the Apple Public Source License Version 1.1 (the
 * "License").  You may not use this file except in compliance with the
 * License.  Please obtain a copy of the License at
 * http://www.apple.com/publicsource and read it before using this file.
 *
 * This 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 OR NON-INFRINGEMENT.  Please see the
 * License for the specific language governing rights and limitations
 * under the License.
 *
 * @APPLE_LICENSE_HEADER_END@
 */
/*
clang nvram2.c -o nvram3 -framework CoreFoundation -framework IOKit -mmacosx-version-min=10.13
*/
// https://github.com/apple-oss-distributions/system_cmds/blob/13d383ddb305dc1672243e5384ba18b58949cc2c/nvram.tproj/nvram.c


//#include "assumes.h"
#include <stdio.h>
#include <IOKit/IOKitLib.h>
#include <IOKit/IOKitKeys.h>
//#include <IOKit/IOKitKeysPrivate.h>
#include <CoreFoundation/CoreFoundation.h>
#include <err.h>
#include <mach/mach_error.h>
#include <sys/stat.h>

#define TARGET_OS_BRIDGE 0

// properties found in the registry root
#define kIOConsoleUsersKey		"IOConsoleUsers"		/* value is OSArray */
#define kIOMaximumMappedIOByteCountKey  "IOMaximumMappedIOByteCount"    /* value is OSNumber */

// properties found in the console user dict

#define kIOConsoleSessionIDKey		"kCGSSessionIDKey"		/* value is OSNumber */

#define kIOConsoleSessionUserNameKey	"kCGSSessionUserNameKey"	/* value is OSString */
#define kIOConsoleSessionUIDKey		    "kCGSSessionUserIDKey"		/* value is OSNumber */
#define kIOConsoleSessionConsoleSetKey	"kCGSSessionConsoleSetKey"	/* value is OSNumber */
#define kIOConsoleSessionOnConsoleKey	"kCGSSessionOnConsoleKey"	/* value is OSBoolean */

// IOResources property
#define kIOConsoleUsersSeedKey		"IOConsoleUsersSeed"		/* value is OSNumber */

#define kIOKernelHasSafeSleep		1
#define kIONVRAMForceSyncNowPropertyKey		"IONVRAM-FORCESYNCNOW-PROPERTY"

#define  gEfiGlobalVariableGuid "8BE4DF61-93CA-11D2-AA0D-00E098032B8C"
#define  gAppleNvramGuid  "4D1EDE05-38C7-4A6A-9CC6-4BCCA8B38C14"
#define  gAppleSecureBootGuid  "94B73556-2197-4702-82A8-3E1337DAFBFB"
#define  gAppleBootGuid  "7C436110-AB2A-4BBB-A880-FE41995C9F82"
#define  gAppleNetworkGuid  "36C28AB5-6566-4C50-9EBD-CBB920F83843"

// Prototypes
static void UsageMessage(const char *message);
static void ParseFile(const char *fileName);
static void ParseXMLFile(const char *fileName);
static void SetOrGetOFVariable(char *str);
static kern_return_t GetOFVariable(const char *name, CFStringRef *nameRef,
                                   CFTypeRef *valueRef);
static kern_return_t SetOFVariable(const char *name, const char *value);
static void DeleteOFVariable(const char *name);
static void PrintOFVariables(void);
static void PrintOFLargestVariables(void);
static void PrintOFVariable(const void *key,const void *value,void *context);
static void SetOFVariableFromFile(const void *key, const void *value, void *context);
static int ClearOFVariables(void);
static void ClearOFVariable(const void *key,const void *value,void *context);
static CFTypeRef ConvertValueToCFTypeRef(CFTypeID typeID, const char *value);

static void NVRamSyncNow(void);

// Global Variables
static io_registry_entry_t gOptionsRef;
static io_registry_entry_t gSystemOptionsRef;
static io_registry_entry_t gSelectedOptionsRef;
static bool                gUseXML;
static bool                gPrintInHex;
static bool                gUseForceSync;

#if TARGET_OS_BRIDGE /* Stuff for nvram bridge -> intel */
#include <dlfcn.h>
#include <libMacEFIManager/MacEFIHostInterfaceAPI.h>

static kern_return_t LinkMacNVRAMSymbols(void);
static kern_return_t GetMacOFVariable(char *name, char **value);
static kern_return_t SetMacOFVariable(char *name, char *value);
static kern_return_t DeleteMacOFVariable(char *name);

static bool gBridgeToIntel;
static void *gDL_handle;
static void *gNvramInterface;

static void (*hostInterfaceInitialize_fptr)(void);
static void *(*createNvramHostInterface_fptr)(const char *handle);
static kern_return_t (*destroyNvramHostInterface_fptr)(void *interface);
static kern_return_t (*getNVRAMVariable_fptr)(void *interface, char *name, char **buffer, uint32_t *size);
static kern_return_t (*setNVRAMVariable_fptr)(void *interface, char *name, char *buffer);
static kern_return_t (*deleteNVRAMVariable_fptr)(void *interface, char *name);
static void (*hostInterfaceDeinitialize_fptr)(void); /* may not need? */

#endif /* TARGET_OS_BRIDGE */

int main(int argc, char **argv)
{
  long                cnt;
  char                *str, errorMessage[256];
  kern_return_t       result;
  mach_port_t         mainPort;
  int                 argcount = 0;


//#if defined(MAC_OS_VERSION_12_0)
//    result = IOMainPort(bootstrap_port, &mainPort);
//#else
	result = IOMasterPort(bootstrap_port, &mainPort);
//#endif
  if (result != KERN_SUCCESS) {
    errx(1, "Error getting the IOMainPort: %s",
        mach_error_string(result));
  }

  gOptionsRef = IORegistryEntryFromPath(mainPort, "IODeviceTree:/options");
  if (gOptionsRef == 0) {
    errx(1, "nvram is not supported on this system");
  }

  gSystemOptionsRef = IORegistryEntryFromPath(mainPort, "IOService:/options/options-system");

  gSelectedOptionsRef = gOptionsRef;

  for (cnt = 1; cnt < argc; cnt++) {
    str = argv[cnt];
    if (str[0] == '-' && str[1] != 0) {
      // Parse the options.
      for (str += 1 ; *str; str++) {
        switch (*str) {
          case 'p' :
#if TARGET_OS_BRIDGE
            if (gBridgeToIntel) {
              fprintf(stderr, "-p not supported for Mac NVRAM store.\n");
              return 1;
            }
#endif
            PrintOFVariables();
            break;

          case 'h' :
            UsageMessage("");
            break;

          case 'x' :
            if (gPrintInHex) {
                fprintf(stderr, "-x not supported with -X hex mode.\n");
                return 1;
            }
            gUseXML = true;
            break;
                
          case 'X' :
            if (gUseXML) {
                fprintf(stderr, "-X hex mode not supported with -x XMLmode.\n");
                return 1;
            }
            gPrintInHex = true;
            break;

          case 'f':
#if TARGET_OS_BRIDGE
            if (gBridgeToIntel) {
              fprintf(stderr, "-f not supported for Mac NVRAM store.\n");
              return 1;
            }
#endif
            cnt++;
            if (cnt < argc && *argv[cnt] != '-') {
              ParseFile(argv[cnt]);
            } else {
              UsageMessage("missing filename");
            }
            break;

          case 'd':
            cnt++;
            if (cnt < argc && *argv[cnt] != '-') {
#if TARGET_OS_BRIDGE
              if (gBridgeToIntel) {
                if ((result = DeleteMacOFVariable(argv[cnt])) != KERN_SUCCESS) {
                  errx(1, "Error deleting variable - '%s': %s (0x%08x)", argv[cnt],
                          mach_error_string(result), result);
                }
              }
              else
#endif
              {
                DeleteOFVariable(argv[cnt]);
              }
            } else {
                UsageMessage("missing name");
            }
            break;

          case 'c':
#if TARGET_OS_BRIDGE
            if (gBridgeToIntel) {
              fprintf(stderr, "-c not supported for Mac NVRAM store.\n");
              return 1;
            }
#endif
            result = ClearOFVariables();
            break;
          case 's':
            // -s option is unadvertised -- advises the kernel more forcibly to
            // commit the variable to nonvolatile storage
            gUseForceSync = true;
            break;
#if TARGET_OS_BRIDGE
          case 'm':
            // used to set nvram variables on the Intel side
            // from the ARM side (Bridge -> Mac)
            fprintf(stdout, "Using Mac NVRAM store.\n");

            LinkMacNVRAMSymbols();
            gBridgeToIntel = true;
            break;
#endif

          case 'z':
            // -z option is unadvertised -- attempts to use the options-system node
            // to write to the system NVRAM region if available
            if (gSystemOptionsRef) {
              fprintf(stderr, "Selecting options-system node.\n");
              gSelectedOptionsRef = gSystemOptionsRef;
            } else {
              fprintf(stderr, "No options-system node, using options.\n");
            }
            break;

          default:
            strcpy(errorMessage, "no such option as --");
            errorMessage[strlen(errorMessage)-1] = *str;
            UsageMessage(errorMessage);
          }
        }
      } else {
        // Other arguments will be firmware variable requests.
        argcount++;
        SetOrGetOFVariable(str);
      }
  }

  // radar:25206371
  if (argcount == 0 && gUseForceSync == true) {
      NVRamSyncNow();
  }

  if (argc == 1) {
      UsageMessage("no arguments specified");
  }

  IOObjectRelease(gOptionsRef);

  if (gSystemOptionsRef) {
    IOObjectRelease(gSystemOptionsRef);
  }

  return result;
}

// UsageMessage(message)
//
//   Print the usage information and exit.
//
static void UsageMessage(const char *message)
{
  warnx("(usage: %s)", message);

  printf("nvram [-x|-X] [-p] [-f filename] [-d name] [-c] name[=value] ...\n");
  printf("\t-x         use XML format for printing or reading variables\n");
  printf("\t           (must appear before -p or -f)\n");
  printf("\t-X         use HEX format for printing or reading variables\n");
  printf("\t           (must appear before -p or -f)\n");
  printf("\t-p         print all firmware variables\n");
  printf("\t-f         set firmware variables from a text file\n");
  printf("\t-d         delete the named variable\n");
  printf("\t-c         delete all variables\n");
#if TARGET_OS_BRIDGE
  printf("\t-m         set nvram variables on macOS from bridgeOS\n");
#endif
  printf("\tname=value set named variable\n");
  printf("\tname       print variable\n");
  printf("Note that arguments and options are executed in order.\n");

  exit(1);
}


// States for ParseFile.
enum {
  kFirstColumn = 0,
  kScanComment,
  kFindName,
  kCollectName,
  kFindValue,
  kCollectValue,
  kContinueValue,
  kSetenv,

  kMaxStringSize = 0x800,
  kMaxNameSize = 0x100
};


// ParseFile(fileName)
//
//   Open and parse the specified file.
//
static void ParseFile(const char *fileName)
{
  long state, ni = 0, vi = 0;
  int tc;
  char name[kMaxNameSize];
  char value[kMaxStringSize];
  FILE *patches;
  kern_return_t kret;

  if (gUseXML) {
    ParseXMLFile(fileName);
    return;
  }

  patches = fopen(fileName, "r");
  if (patches == 0) {
    err(1, "Couldn't open patch file - '%s'", fileName);
  }

  state = kFirstColumn;
  while ((tc = getc(patches)) != EOF) {
    if(ni==(kMaxNameSize-1))
      errx(1, "Name exceeded max length of %d", kMaxNameSize);
    if(vi==(kMaxStringSize-1))
      errx(1, "Value exceeded max length of %d", kMaxStringSize);
    switch (state) {
    case kFirstColumn :
      ni = 0;
      vi = 0;
      if (tc == '#') {
        state = kScanComment;
      } else if (tc == '\n') {
        // state stays kFirstColumn.
      } else if (isspace(tc)) {
        state = kFindName;
      } else {
        state = kCollectName;
        name[ni++] = tc;
      }
      break;

    case kScanComment :
      if (tc == '\n') {
        state = kFirstColumn;
      } else {
        // state stays kScanComment.
      }
      break;

    case kFindName :
      if (tc == '\n') {
        state = kFirstColumn;
      } else if (isspace(tc)) {
        // state stays kFindName.
      } else {
        state = kCollectName;
        name[ni++] = tc;
      }
      break;

    case kCollectName :
      if (tc == '\n') {
        name[ni] = 0;
        warnx("Name must be followed by white space - '%s'", name);
        state = kFirstColumn;
      } else if (isspace(tc)) {
        state = kFindValue;
      } else {
        name[ni++] = tc;
        // state staus kCollectName.
      }
      break;

    case kFindValue :
    case kContinueValue :
      if (tc == '\n') {
        state = kSetenv;
      } else if (isspace(tc)) {
        // state stays kFindValue or kContinueValue.
      } else {
        state = kCollectValue;
        value[vi++] = tc;
      }
      break;

    case kCollectValue :
      if (tc == '\n') {
        if (value[vi-1] == '\\') {
          value[vi-1] = '\r';
          state = kContinueValue;
        } else {
          state = kSetenv;
        }
      } else {
        // state stays kCollectValue.
        value[vi++] = tc;
      }
      break;
    }

    if (state == kSetenv) {
      name[ni] = 0;
      value[vi] = 0;
      if ((kret = SetOFVariable(name, value)) != KERN_SUCCESS) {
        errx(1, "Error setting variable - '%s': %s", name,
             mach_error_string(kret));
      }
      state = kFirstColumn;
    }
  }

  if (state != kFirstColumn) {
    errx(1, "Last line ended abruptly");
  }
}

// ParseXMLFile(fileName)
//
//   Open and parse the specified file in XML format,
//   and set variables appropriately.
//
static void ParseXMLFile(const char *fileName)
{
  CFPropertyListRef plist;
  int fd;
  struct stat sb;
  char *buffer;
  CFReadStreamRef stream;
  CFPropertyListFormat format = kCFPropertyListBinaryFormat_v1_0;

  fd = open(fileName, O_RDONLY | O_NOFOLLOW, S_IFREG);
  if (fd == -1) {
      errx(1, "Could not open %s: %s", fileName, strerror(errno));
  }

  if (fstat(fd, &sb) == -1) {
      errx(1, "Could not fstat %s: %s", fileName, strerror(errno));
  }

  if (sb.st_size > UINT32_MAX) {
      errx(1, "too big for our purposes");
  }

  buffer = malloc((size_t)sb.st_size);
  if (buffer == NULL) {
      errx(1, "Could not allocate buffer");
  }

  if (read(fd, buffer, (size_t)sb.st_size) != sb.st_size) {
      errx(1, "Could not read %s: %s", fileName, strerror(errno));
  }

  close(fd);

  stream = CFReadStreamCreateWithBytesNoCopy(kCFAllocatorDefault,
          (const UInt8 *)buffer,
          (CFIndex)sb.st_size,
          kCFAllocatorNull);
  if (stream == NULL) {
      errx(1, "Could not create stream from serialized data");
  }

  if (!CFReadStreamOpen(stream)) {
      errx(1, "Could not open the stream");
  }

  plist = CFPropertyListCreateWithStream(kCFAllocatorDefault,
          stream,
          (CFIndex)sb.st_size,
          kCFPropertyListImmutable,
          &format,
          NULL);

  if (plist == NULL) {
      errx(1, "Error parsing XML file");
  }

  CFReadStreamClose(stream);

  CFRelease(stream);

  free(buffer);

  CFDictionaryApplyFunction(plist, &SetOFVariableFromFile, 0);

  CFRelease(plist);
}

// SetOrGetOFVariable(str)
//
//   Parse the input string, then set, append or get
//   the specified firmware variable.
//
static void SetOrGetOFVariable(char *str)
{
  long               set             = 0;
  long               append          = 0;
  long               remove          = 0;
  const char         *name           = NULL;
  char               *value          = NULL;
  char               *substring      = NULL;
  char               *tmp            = NULL;
  CFStringRef        nameRef         = NULL;
  CFTypeRef          valueRef        = NULL;
  size_t             len             = 0;
  CFMutableDataRef   mutableValueRef = NULL;
  kern_return_t      result;

  // OF variable name is first.
  name = str;

  // Find the equal sign for set, += for append, -= for trim
  while (*str) {
    if (*str == '+' && *(str+1) == '=') {
      append = 1;
      *str++ = '\0';
      *str++ = '\0';
      break;
    }
      
    if (*str == '-' && *(str+1) == '=') {
      remove = 1;
      *str++ = '\0';
      *str++ = '\0';
      break;
    }

    if (*str == '=') {
      set = 1;
      *str++ = '\0';
      break;
    }
    str++;
  }

  // Read the current value if appending or if no =/+=
  if (append == 1 || remove == 1 || (set == 0 && append == 0 && remove == 0)) {
#if TARGET_OS_BRIDGE
    if (gBridgeToIntel) {
      result = GetMacOFVariable(name, &value);
      if (result != KERN_SUCCESS) {
        errx(1, "Error getting variable - '%s': %s", name,
             mach_error_string(result));
      }
      nameRef = CFStringCreateWithCString(kCFAllocatorDefault, name, kCFStringEncodingUTF8);
      valueRef = CFStringCreateWithCString(kCFAllocatorDefault, value, kCFStringEncodingUTF8);
      free(value);
    }
    else
#endif
    {
      result = GetOFVariable(name, &nameRef, &valueRef);
      if (result != KERN_SUCCESS) {
        errx(1, "Error getting variable - '%s': %s", name,
             mach_error_string(result));
      }
    }
  }

  if (set == 1) {
    // On sets, the OF variable's value follows the equal sign.
    value = str;
  }

  if (append == 1) {
    // On append, the value to append follows the += substring
    if(CFGetTypeID(valueRef) == CFStringGetTypeID()) {
      len  = strlen(str);
      len += CFStringGetMaximumSizeForEncoding(CFStringGetLength(valueRef),
                                               kCFStringEncodingUTF8);
      tmp = calloc(len + 1, 1);
      if ( (tmp == NULL)
        || (CFStringGetCString(valueRef, tmp, len, kCFStringEncodingUTF8) == false)) {
        errx(1, "allocation failed");
      }
      
      value = tmp;
      strcpy(value + strlen(value), str);
    } else if(CFGetTypeID(valueRef) == CFDataGetTypeID()) {
      mutableValueRef = CFDataCreateMutableCopy(kCFAllocatorDefault, 
                                                CFDataGetLength(valueRef) + 
                                                strlen(str) + 1, 
                                                valueRef);
      CFDataAppendBytes(mutableValueRef, (const UInt8 *)str, strlen(str) + 1);
      value = (char *)CFDataGetBytePtr(mutableValueRef);
    }
    else {
      errx(1, "Unsupported named variable CFTypeID");
    }
  }

  if (remove == 1) {
    // On remove, the value to remove follows the -= substring
    if(CFGetTypeID(valueRef) == CFStringGetTypeID()) {
      len = CFStringGetMaximumSizeForEncoding(CFStringGetLength(valueRef),
                                               kCFStringEncodingUTF8);
      tmp = calloc(len + 1, 1);
      if(  (tmp == NULL)
        || (CFStringGetCString(valueRef, tmp, len, kCFStringEncodingUTF8) == false)) {
        errx(1, "failed to allocate string");
      }
    
      value = tmp;
    } else if(CFGetTypeID(valueRef) == CFDataGetTypeID()) {
      value = (char *)CFDataGetBytePtr(valueRef);
    } else {
      errx(1, "Unsupported named variable CFTypeID");
    }

    substring = strstr(value, str);
    if (substring == NULL) {
      errx(1, "substring %s not found in %s\n", str, value);
    }

    len = strlen(str);
    memmove(substring, substring + len, strlen(substring + len) + 1);
  }

  if (set == 1 || append == 1 || remove == 1) {
#if TARGET_OS_BRIDGE
    if (gBridgeToIntel) {
      result = SetMacOFVariable(name, value);
        if (result != KERN_SUCCESS) {
          errx(1, "Error setting variable - '%s': %s", name,
               mach_error_string(result));
        }
    }
    else
#endif
    {
      result = SetOFVariable(name, value);
      // Try syncing the new data to device, best effort!
      NVRamSyncNow();
      if (result != KERN_SUCCESS) {
        fprintf(stderr, "Error setting variable - '%s': %s.\n", name,
           mach_error_string(result));
        if (result == kIOReturnNoMemory) {
          PrintOFLargestVariables();
        }
        exit(1);
      }
    }
  } else {
    PrintOFVariable(nameRef, valueRef, 0);
  }
  if (nameRef) CFRelease(nameRef);
  if (valueRef) CFRelease(valueRef);
  if (tmp) free(tmp);
  if (mutableValueRef) CFRelease(mutableValueRef);
}

#if TARGET_OS_BRIDGE
static kern_return_t LinkMacNVRAMSymbols()
{
  gDL_handle = dlopen("libMacEFIHostInterface.dylib", RTLD_LAZY);
  if (gDL_handle == NULL) {
    errx(errno, "Failed to dlopen libMacEFIHostInterface.dylib");
    return KERN_FAILURE; /* NOTREACHED */
  }

  hostInterfaceInitialize_fptr = dlsym(gDL_handle, "hostInterfaceInitialize");
  if (hostInterfaceInitialize_fptr == NULL) {
    errx(errno, "failed to link hostInterfaceInitialize");
  }
  createNvramHostInterface_fptr = dlsym(gDL_handle, "createNvramHostInterface");
  if (createNvramHostInterface_fptr == NULL) {
    errx(errno, "failed to link createNvramHostInterface");
  }
  destroyNvramHostInterface_fptr = dlsym(gDL_handle, "destroyNvramHostInterface");
  if (destroyNvramHostInterface_fptr == NULL) {
    errx(errno, "failed to link destroyNvramHostInterface");
  }
  getNVRAMVariable_fptr = dlsym(gDL_handle, "getNVRAMVariable");
  if (getNVRAMVariable_fptr == NULL) {
    errx(errno, "failed to link getNVRAMVariable");
  }
  setNVRAMVariable_fptr = dlsym(gDL_handle, "setNVRAMVariable");
  if (setNVRAMVariable_fptr == NULL) {
    errx(errno, "failed to link setNVRAMVariable");
  }
  deleteNVRAMVariable_fptr = dlsym(gDL_handle, "deleteNVRAMVariable");
  if (deleteNVRAMVariable_fptr == NULL) {
      errx(errno, "failed to link deleteNVRAMVariable");
  }
  hostInterfaceDeinitialize_fptr = dlsym(gDL_handle, "hostInterfaceDeinitialize");
  if (hostInterfaceDeinitialize_fptr == NULL) {
    errx(errno, "failed to link hostInterfaceDeinitialize");
  }

  /* also do the initialization */
  hostInterfaceInitialize_fptr();
  gNvramInterface = createNvramHostInterface_fptr(NULL);

  return KERN_SUCCESS;
}
#endif

// GetOFVariable(name, nameRef, valueRef)
//
//   Get the named firmware variable.
//   Return it and it's symbol in valueRef and nameRef.
//
static kern_return_t GetOFVariable(const char *name, CFStringRef *nameRef,
                                   CFTypeRef *valueRef)
{
  *nameRef = CFStringCreateWithCString(kCFAllocatorDefault, name,
                                       kCFStringEncodingUTF8);
  if (*nameRef == 0) {
    errx(1, "Error creating CFString for key %s", name);
  }

  *valueRef = IORegistryEntryCreateCFProperty(gSelectedOptionsRef, *nameRef, 0, 0);
  if (*valueRef == 0) return kIOReturnNotFound;

  return KERN_SUCCESS;
}

#if TARGET_OS_BRIDGE
// GetMacOFVariable(name, value)
//
// Get the named firmware variable from the Intel side.
// Return the value in value
//
static kern_return_t GetMacOFVariable(char *name, char **value)
{
  uint32_t value_size;

  return getNVRAMVariable_fptr(gNvramInterface, name, value, &value_size);
}
#endif

// SetOFVariable(name, value)
//
//   Set or create an firmware variable with name and value.
//
static kern_return_t SetOFVariable(const char *name, const char *value)
{
  CFStringRef   nameRef;
  CFTypeRef     valueRef;
  CFTypeID      typeID;
  kern_return_t result = KERN_SUCCESS;

  nameRef = CFStringCreateWithCString(kCFAllocatorDefault, name,
                                        kCFStringEncodingUTF8);
  if (nameRef == 0) {
    errx(1, "Error creating CFString for key %s", name);
  }

  valueRef = IORegistryEntryCreateCFProperty(gSelectedOptionsRef, nameRef, 0, 0);
  if (valueRef) {
    typeID = CFGetTypeID(valueRef);
    CFRelease(valueRef);
    valueRef = ConvertValueToCFTypeRef(typeID, value);
    if (valueRef == 0) {
      errx(1, "Error creating CFTypeRef for value %s", value);
    }
    result = IORegistryEntrySetCFProperty(gSelectedOptionsRef, nameRef, valueRef);
  } else {
    // In the default case, try data, string, number, then boolean.
    CFTypeID types[] = {CFDataGetTypeID(),
    CFStringGetTypeID(), CFNumberGetTypeID(),CFBooleanGetTypeID() };
    for (int i = 0; i < sizeof(types)/sizeof(types[0]); i++) {
      valueRef = ConvertValueToCFTypeRef(types[i], value);
      if (valueRef != 0) {
        result = IORegistryEntrySetCFProperty(gSelectedOptionsRef, nameRef, valueRef);
        if (result == KERN_SUCCESS || result == kIOReturnNoMemory || result ==  kIOReturnNoSpace) {
          break;
        }
      }
    }
  }

  CFRelease(nameRef);

  return result;
}

#if TARGET_OS_BRIDGE
static kern_return_t SetMacOFVariable(char *name, char *value)
{
  return setNVRAMVariable_fptr(gNvramInterface, name, value);
}
#endif

// DeleteOFVariable(name)
//
//   Delete the named firmware variable.
//
static void DeleteOFVariable(const char *name)
{
  SetOFVariable(kIONVRAMDeletePropertyKey, name);
}

#if TARGET_OS_BRIDGE
static kern_return_t DeleteMacOFVariable(char *name)
{
  return deleteNVRAMVariable_fptr(gNvramInterface, name);
}
#endif

static void NVRamSyncNow(void)
{
  if (!gUseForceSync) {
    SetOFVariable(kIONVRAMSyncNowPropertyKey, kIONVRAMSyncNowPropertyKey);
  } else {
    SetOFVariable(kIONVRAMForceSyncNowPropertyKey, kIONVRAMForceSyncNowPropertyKey);
  }
}

CFDictionaryRef CreateMyDictionary(void) {

    // root
    CFMutableDictionaryRef dict0 = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
                                                            &kCFTypeDictionaryKeyCallBacks,
                                                            &kCFTypeDictionaryValueCallBacks);

    //   APPLE_BOOT_VARIABLE_GUID
    //   Print all of the firmware variables.
    CFMutableDictionaryRef dict1;
    kern_return_t          result;
    result = IORegistryEntryCreateCFProperties(gOptionsRef, &dict1, 0, 0);
    if (result != KERN_SUCCESS) {
        errx(1, "Error getting the firmware variables: %s", mach_error_string(result));
    }
    CFDictionarySetValue(dict0, CFSTR(gAppleBootGuid), dict1);
    CFRelease(dict1);

    CFMutableDictionaryRef dict2 = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks,
                                                             &kCFTypeDictionaryValueCallBacks);
    CFDictionarySetValue(dict0, CFSTR(gEfiGlobalVariableGuid), dict2);

    //   EFI_GLOBAL_VARIABLE_GUID
    //   Print the given firmware variable.
    CFStringRef   nameRef;
    CFTypeRef     valueRef;

    const char *key[32];
    char name[64];
    CFStringRef   var;
    int i = 0;
    int n = 0; // num of keys in this GUID

    key[n++] = "Boot0080";
    key[n++] = "BootOrder";
    key[n++] = "BootNext";
    key[n++] = "Boot0081";
    key[n++] = "Boot0082";
    key[n++] = "Boot0083";
    key[n++] = "BootCurrent";

    for ( i = 0; i < n; i++ ) {
        snprintf(name, sizeof(name), "%s:%s", gEfiGlobalVariableGuid, key[i]);
        var = CFStringCreateWithCString(NULL, key[i], kCFStringEncodingUTF8);
        result = GetOFVariable(name, &nameRef, &valueRef);
        if (result == KERN_SUCCESS) {
            CFDictionaryAddValue (dict2, var, valueRef);
            CFRelease(valueRef);
        }
        CFRelease(nameRef);
    }
    CFRelease(dict2);

    //  APPLE_WIRELESS_NETWORK_VARIABLE_GUID
    CFMutableDictionaryRef dict3 = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks,
                                                             &kCFTypeDictionaryValueCallBacks);
    CFDictionarySetValue(dict0, CFSTR(gAppleNetworkGuid), dict3);
    n = 0; // num of keys in this GUID
    key[n++] = "current-network";
    key[n++] = "preferred-count";
    key[n++] = "preferred-networks";
    

    for ( i = 0; i < n; i++ ) {
        snprintf(name, sizeof(name), "%s:%s", gAppleNetworkGuid, key[i]);
        var = CFStringCreateWithCString(NULL, key[i], kCFStringEncodingUTF8);
        result = GetOFVariable(name, &nameRef, &valueRef);
        if (result == KERN_SUCCESS) {
            CFDictionaryAddValue (dict3, var, valueRef);
            CFRelease(valueRef);
        }
        CFRelease(nameRef);
    }
    CFRelease(dict3);
  
     //  APPLE_SECURE_BOOT_VARIABLE_GUID
    CFMutableDictionaryRef dict4 = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks,
                                                             &kCFTypeDictionaryValueCallBacks);
    CFDictionarySetValue(dict0, CFSTR(gAppleSecureBootGuid), dict4);
    n = 0; // num of keys in this GUID
    key[n++] = "ApECID";
    key[n++] = "ApChipID";
    key[n++] = "ApBoardID";
    key[n++] = "ApSecurityDomain";
    key[n++] = "ApProductionStatus";
    key[n++] = "ApSecurityMode";
    key[n++] = "EffectiveProductionStatus";
    key[n++] = "EffectiveSecurityMode";
    key[n++] = "CertificateEpoch";
    key[n++] = "MixNMatchPreventionStatus";
    key[n++] = "CryptoDigestMethod";
    key[n++] = "HardwareModel";
    key[n++] = "InternalUseOnlyUnit";
    

    for ( i = 0; i < n; i++ ) {
        snprintf(name, sizeof(name), "%s:%s", gAppleSecureBootGuid, key[i]);
        var = CFStringCreateWithCString(NULL, key[i], kCFStringEncodingUTF8);
        result = GetOFVariable(name, &nameRef, &valueRef);
        if (result == KERN_SUCCESS) {
            CFDictionaryAddValue (dict4, var, valueRef);
            CFRelease(valueRef);
        }
        CFRelease(nameRef);
    }
    CFRelease(dict4);   
    
    CFMutableDictionaryRef dict5 = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks,
                                                             &kCFTypeDictionaryValueCallBacks);                                                            
    CFDictionarySetValue(dict0, CFSTR(gAppleNvramGuid), dict5);
    n=0;
    key[n++] = "gfx-saved-config-restore-status";
    key[n++] = "SkipLogo";
    key[n++] = "IASCurrentInstallPhase";
    key[n++] = "IASInstallPhaseList";
    for ( i = 0; i < n; i++ ) {
        snprintf(name, sizeof(name), "%s:%s", gAppleNvramGuid, key[i]);
        var = CFStringCreateWithCString(NULL, key[i], kCFStringEncodingUTF8);
        result = GetOFVariable(name, &nameRef, &valueRef);
        if (result == KERN_SUCCESS) {
            CFDictionaryAddValue (dict5, var, valueRef);
            CFRelease(valueRef);
        }
        CFRelease(nameRef);
    }
    CFRelease(dict5);  

    return dict0;
}

// PrintOFVariables()
//
//   Print all of the firmware variables.
//
static void PrintOFVariables(void)
{
  kern_return_t          result;
  CFMutableDictionaryRef dict;

  result = IORegistryEntryCreateCFProperties(gSelectedOptionsRef, &dict, 0, 0);
  if (result != KERN_SUCCESS) {
    errx(1, "Error getting the firmware variables: %s", mach_error_string(result));
  }

  if (gUseXML) {
    CFDataRef data;

	CFPropertyListRef propertyList = CreateMyDictionary();
	data = CFPropertyListCreateData( kCFAllocatorDefault, propertyList, kCFPropertyListXMLFormat_v1_0, 0, NULL );
 //   data = CFPropertyListCreateData( kCFAllocatorDefault, dict, kCFPropertyListXMLFormat_v1_0, 0, NULL );
    if (data == NULL) {
      errx(1, "Error converting variables to xml");
    }

    fwrite(CFDataGetBytePtr(data), sizeof(UInt8), CFDataGetLength(data), stdout);

    CFRelease(data);

  } else {

    CFDictionaryApplyFunction(dict, &PrintOFVariable, 0);

  }

  CFRelease(dict);
}

static size_t
ElementSize(const void *value)
{
  CFTypeID typeId = CFGetTypeID(value);
  if (typeId == CFStringGetTypeID()) {
    return CFStringGetLength(value);
  }
  else if (typeId == CFDataGetTypeID()) {
    return CFDataGetLength(value);
  }
  else if (typeId == CFNumberGetTypeID()) {
    return 8;
  }
  return 0;
}

// PrintOFLargestVariables()
//
//   Print the LARGEST_COUNT largest
//   nvram variables.
//
void
PrintOFLargestVariables(void)
{
#define LARGEST_COUNT 5
  kern_return_t          result;
  CFMutableDictionaryRef dict;
  long                   count;
  void                   **keys, **values;
  size_t                 *indices;

  result = IORegistryEntryCreateCFProperties(gOptionsRef, &dict, 0, 0);
  if (result != KERN_SUCCESS) {
    errx(1, "Error getting the firmware variables: %s", mach_error_string(result));
  }

  count = CFDictionaryGetCount(dict);

  keys = calloc(count, sizeof(void *));
  //os_assert(keys != NULL);
  values = calloc(count, sizeof(void *));
  //os_assert(values != NULL);

  indices = calloc(count, sizeof(*indices));
  //os_assert(indices != NULL);
  for (size_t i = 0; i < count; i++) {
    indices[i] = i;
  }

  CFDictionaryGetKeysAndValues(dict, (const void **)keys, (const void **)values);

  qsort_b(indices, count, sizeof(*indices), ^(const void *a, const void *b) {
    return (int)(((int64_t)ElementSize(values[*(size_t*)b]) - (int64_t)(ElementSize(values[*(size_t*)a]))));
  });

  fprintf(stderr, "key\tbytes\n");
  for (long i = 0; i < count && i < LARGEST_COUNT; i++) {
    size_t size = ElementSize(values[indices[i]]);
    if (size > 0) {
      fprintf(stderr, "%s\t%zu\n", CFStringGetCStringPtr(keys[indices[i]], kCFStringEncodingUTF8
  ), size);
    }
  }

  free(keys);
  free(values);
  free(indices);
  CFRelease(dict);
}

// PrintOFVariable(key, value, context)
//
//   Print the given firmware variable.
//
static void PrintOFVariable(const void *key, const void *value, void *context)
{
  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;

  if (gUseXML) {
    CFDataRef data;
    CFDictionaryRef dict = CFDictionaryCreate(kCFAllocatorDefault, &key, &value, 1,
                                              &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
    if (dict == NULL) {
      errx(1, "Error creating dictionary for variable value");
    }

    data = CFPropertyListCreateData( kCFAllocatorDefault, dict, kCFPropertyListXMLFormat_v1_0, 0, NULL );
    if (data == NULL) {
      errx(1, "Error creating xml plist for variable");
    }

    fwrite(CFDataGetBytePtr(data), sizeof(UInt8), CFDataGetLength(data), stdout);

    CFRelease(dict);
    CFRelease(data);
    return;
  }

  // Get the OF variable's name.
  nameLen = CFStringGetMaximumSizeForEncoding(CFStringGetLength(key),
      kCFStringEncodingUTF8) + 1;
  nameBuffer = malloc(nameLen);
  if( nameBuffer && CFStringGetCString(key, nameBuffer, nameLen, kCFStringEncodingUTF8) )
    nameString = nameBuffer;
  else {
    warnx("Unable to convert property name to C string");
    nameString = "<UNPRINTABLE>";
  }

  // Get the OF 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 if (!gPrintInHex && number < 1000) sprintf(numberBuffer, "%d", number);
    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 {
      warnx("Unable to convert value to C string");
      valueString = "<UNPRINTABLE>";
    }
  } else if (typeID == CFDataGetTypeID()) {
    length = CFDataGetLength(value);
    if (length == 0) valueString = "";
    else {
      dataBuffer = malloc(length * 3 + 3);
      if (dataBuffer != 0) {
        dataPtr = CFDataGetBytePtr(value);
        cnt = cnt2 = 0;
        if (gPrintInHex) {
            sprintf(dataBuffer, "0x");
            cnt2 += 2;
        }
        for (; cnt < length; cnt++) {
          dataChar = dataPtr[cnt];
          if (gPrintInHex) {
              sprintf(dataBuffer + cnt2, "%02x", dataChar);
              cnt2 += 2;
          } else if (!gPrintInHex && isprint(dataChar) && dataChar != '%') {
            dataBuffer[cnt2++] = dataChar;
          } else {
            sprintf(dataBuffer + cnt2, "%%%02x", dataChar);
            cnt2 += 3;
          }
        }
        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);
}

// ClearOFVariables()
//
//   Deletes all OF variables
//
static int ClearOFVariables(void)
{
    kern_return_t          result;
    CFMutableDictionaryRef dict;

    result = IORegistryEntryCreateCFProperties(gSelectedOptionsRef, &dict, 0, 0);
    if (result != KERN_SUCCESS) {
      errx(1, "Error getting the firmware variables: %s", mach_error_string(result));
    }
    CFDictionaryApplyFunction(dict, &ClearOFVariable, &result);

    CFRelease(dict);

    return result;
}

static void ClearOFVariable(const void *key, const void *value, void *context)
{
  kern_return_t result;
  result = IORegistryEntrySetCFProperty(gSelectedOptionsRef,
                                        CFSTR(kIONVRAMDeletePropertyKey), key);
  if (result != KERN_SUCCESS) {
    assert(CFGetTypeID(key) == CFStringGetTypeID());
    const char *keyStr = CFStringGetCStringPtr(key, kCFStringEncodingUTF8);
    char *keyBuffer = NULL;
    size_t keyBufferLen = 0;
    if (!keyStr) {
      keyBufferLen = CFStringGetMaximumSizeForEncoding(CFStringGetLength(key), kCFStringEncodingUTF8) + 1;
      keyBuffer = (char *)malloc(keyBufferLen);
      if (keyBuffer != NULL && CFStringGetCString(key, keyBuffer, keyBufferLen, kCFStringEncodingUTF8)) {
        keyStr = keyBuffer;
      } else {
        warnx("Unable to convert property name to C string");
        keyStr = "<UNPRINTABLE>";
      }
    }

    warnx("Error clearing firmware variable %s: %s", keyStr, mach_error_string(result));
    if (keyBuffer) {
      free(keyBuffer);
    }

    if (context) {
      *(int *)context = result;
    }
  }
}

// ConvertValueToCFTypeRef(typeID, value)
//
//   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 (!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()) {
      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 -= 7;
          tmp = toupper(value[++cnt]) - '0';
          if (tmp > 9) tmp -= 7;
          number = (number << 4) + tmp;
          valueCopy[cnt2] = number;
        } else valueCopy[cnt2] = value[cnt];
      }
      valueRef = CFDataCreate(kCFAllocatorDefault, (const UInt8 *)valueCopy, cnt2);
    } else return 0;

    return valueRef;
}

static void SetOFVariableFromFile(const void *key, const void *value, void *context)
{
  kern_return_t result;

  result = IORegistryEntrySetCFProperty(gSelectedOptionsRef, key, value);
  if ( result != KERN_SUCCESS ) {
    long nameLen;
    char *nameBuffer;
    char *nameString;

    // Get the variable's name.
    nameLen = CFStringGetMaximumSizeForEncoding(CFStringGetLength(key),
        kCFStringEncodingUTF8) + 1;
    nameBuffer = malloc(nameLen);
    if( nameBuffer && CFStringGetCString(key, nameBuffer, nameLen, kCFStringEncodingUTF8) )
            nameString = nameBuffer;
    else {
            warnx("Unable to convert property name to C string");
            nameString = "<UNPRINTABLE>";
    }
    errx(1, "Error setting variable - '%s': %s", nameString,
         mach_error_string(result));
  }
}
