1/*
2 * Copyright (c) 2000-2012 Apple Computer, Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * The contents of this file constitute Original Code as defined in and
7 * are subject to the Apple Public Source License Version 1.1 (the
8 * "License").  You may not use this file except in compliance with the
9 * License.  Please obtain a copy of the License at
10 * http://www.apple.com/publicsource and read it before using this file.
11 *
12 * This Original Code and all software distributed under the License are
13 * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER
14 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
15 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT.  Please see the
17 * License for the specific language governing rights and limitations
18 * under the License.
19 *
20 * @APPLE_LICENSE_HEADER_END@
21 */
22/*
23cc -o nvram nvram.c -framework CoreFoundation -framework IOKit -Wall
24*/
25
26#include <stdio.h>
27#include <IOKit/IOKitLib.h>
28#include <IOKit/IOKitKeys.h>
29#include <CoreFoundation/CoreFoundation.h>
30#include <err.h>
31#include <mach/mach_error.h>
32
33// Prototypes
34static void UsageMessage(char *message);
35static void ParseFile(char *fileName);
36static void ParseXMLFile(char *fileName);
37static void SetOrGetOFVariable(char *str);
38static kern_return_t GetOFVariable(char *name, CFStringRef *nameRef,
39				   CFTypeRef *valueRef);
40static kern_return_t SetOFVariable(char *name, char *value);
41static void DeleteOFVariable(char *name);
42static void PrintOFVariables(void);
43static void PrintOFVariable(const void *key,const void *value,void *context);
44static void SetOFVariableFromFile(const void *key, const void *value, void *context);
45static void ClearOFVariables(void);
46static void ClearOFVariable(const void *key,const void *value,void *context);
47static CFTypeRef ConvertValueToCFTypeRef(CFTypeID typeID, char *value);
48
49static void NVRamSyncNow(char *name);
50
51// Global Variables
52static char                *gToolName;
53static io_registry_entry_t gOptionsRef;
54static bool                gUseXML;
55
56
57int main(int argc, char **argv)
58{
59  long                cnt;
60  char                *str, errorMessage[256];
61  kern_return_t       result;
62  mach_port_t         masterPort;
63
64  // Get the name of the command.
65  gToolName = strrchr(argv[0], '/');
66  if (gToolName != 0) gToolName++;
67  else gToolName = argv[0];
68
69  result = IOMasterPort(bootstrap_port, &masterPort);
70  if (result != KERN_SUCCESS) {
71    errx(1, "Error getting the IOMaster port: %s",
72        mach_error_string(result));
73  }
74
75  gOptionsRef = IORegistryEntryFromPath(masterPort, "IODeviceTree:/options");
76  if (gOptionsRef == 0) {
77    errx(1, "nvram is not supported on this system");
78  }
79
80  for (cnt = 1; cnt < argc; cnt++) {
81    str = argv[cnt];
82    if (str[0] == '-' && str[1] != 0) {
83      // Parse the options.
84      for (str += 1 ; *str; str++) {
85	switch (*str) {
86	case 'p' :
87	  PrintOFVariables();
88	  break;
89
90	case 'x' :
91          gUseXML = true;
92          break;
93
94	case 'f':
95	  cnt++;
96	  if (cnt < argc && *argv[cnt] != '-') {
97	    ParseFile(argv[cnt]);
98	  } else {
99	    UsageMessage("missing filename");
100	  }
101	  break;
102
103	case 'd':
104	  cnt++;
105	  if (cnt < argc && *argv[cnt] != '-') {
106	    DeleteOFVariable(argv[cnt]);
107	  } else {
108	    UsageMessage("missing name");
109	  }
110	  break;
111
112	case 'c':
113	  ClearOFVariables();
114	  break;
115
116	default:
117	  strcpy(errorMessage, "no such option as --");
118	  errorMessage[strlen(errorMessage)-1] = *str;
119	  UsageMessage(errorMessage);
120	}
121      }
122    } else {
123      // Other arguments will be firmware variable requests.
124      SetOrGetOFVariable(str);
125    }
126  }
127
128  IOObjectRelease(gOptionsRef);
129
130  return 0;
131}
132
133// UsageMessage(message)
134//
135//   Print the usage information and exit.
136//
137static void UsageMessage(char *message)
138{
139  warnx("(usage: %s)", message);
140
141  printf("%s [-x] [-p] [-f filename] [-d name] [-c] name[=value] ...\n", gToolName);
142  printf("\t-x         use XML format for printing or reading variables\n");
143  printf("\t           (must appear before -p or -f)\n");
144  printf("\t-p         print all firmware variables\n");
145  printf("\t-f         set firmware variables from a text file\n");
146  printf("\t-d         delete the named variable\n");
147  printf("\t-c         delete all variables\n");
148  printf("\tname=value set named variable\n");
149  printf("\tname       print variable\n");
150  printf("Note that arguments and options are executed in order.\n");
151
152  exit(1);
153}
154
155
156// States for ParseFile.
157enum {
158  kFirstColumn = 0,
159  kScanComment,
160  kFindName,
161  kCollectName,
162  kFindValue,
163  kCollectValue,
164  kContinueValue,
165  kSetenv,
166
167  kMaxStringSize = 0x800,
168  kMaxNameSize = 0x100
169};
170
171
172// ParseFile(fileName)
173//
174//   Open and parse the specified file.
175//
176static void ParseFile(char *fileName)
177{
178  long state, tc, ni = 0, vi = 0;
179  char name[kMaxNameSize];
180  char value[kMaxStringSize];
181  FILE *patches;
182  kern_return_t kret;
183
184  if (gUseXML) {
185    ParseXMLFile(fileName);
186    return;
187  }
188
189  patches = fopen(fileName, "r");
190  if (patches == 0) {
191    err(1, "Couldn't open patch file - '%s'", fileName);
192  }
193
194  state = kFirstColumn;
195  while ((tc = getc(patches)) != EOF) {
196    if(ni==(kMaxNameSize-1))
197      errx(1, "Name exceeded max length of %d", kMaxNameSize);
198    if(vi==(kMaxStringSize-1))
199      errx(1, "Value exceeded max length of %d", kMaxStringSize);
200    switch (state) {
201    case kFirstColumn :
202      ni = 0;
203      vi = 0;
204      if (tc == '#') {
205	state = kScanComment;
206      } else if (tc == '\n') {
207	// state stays kFirstColumn.
208      } else if (isspace(tc)) {
209	state = kFindName;
210      } else {
211	state = kCollectName;
212	name[ni++] = tc;
213      }
214      break;
215
216    case kScanComment :
217      if (tc == '\n') {
218	state = kFirstColumn;
219      } else {
220	// state stays kScanComment.
221      }
222      break;
223
224    case kFindName :
225      if (tc == '\n') {
226	state = kFirstColumn;
227      } else if (isspace(tc)) {
228	// state stays kFindName.
229      } else {
230	state = kCollectName;
231	name[ni++] = tc;
232      }
233      break;
234
235    case kCollectName :
236      if (tc == '\n') {
237	name[ni] = 0;
238	warnx("Name must be followed by white space - '%s'", name);
239	state = kFirstColumn;
240      } else if (isspace(tc)) {
241	state = kFindValue;
242      } else {
243	name[ni++] = tc;
244	// state staus kCollectName.
245      }
246      break;
247
248    case kFindValue :
249    case kContinueValue :
250      if (tc == '\n') {
251	state = kSetenv;
252      } else if (isspace(tc)) {
253	// state stays kFindValue or kContinueValue.
254      } else {
255	state = kCollectValue;
256	value[vi++] = tc;
257      }
258      break;
259
260    case kCollectValue :
261      if (tc == '\n') {
262	if (value[vi-1] == '\\') {
263	  value[vi-1] = '\r';
264	  state = kContinueValue;
265	} else {
266	  state = kSetenv;
267	}
268      } else {
269	// state stays kCollectValue.
270	value[vi++] = tc;
271      }
272      break;
273    }
274
275    if (state == kSetenv) {
276      name[ni] = 0;
277      value[vi] = 0;
278      if ((kret = SetOFVariable(name, value)) != KERN_SUCCESS) {
279        errx(1, "Error setting variable - '%s': %s", name,
280             mach_error_string(kret));
281      }
282      state = kFirstColumn;
283    }
284  }
285
286  if (state != kFirstColumn) {
287    errx(1, "Last line ended abruptly");
288  }
289}
290
291
292// ParseXMLFile(fileName)
293//
294//   Open and parse the specified file in XML format,
295//   and set variables appropriately.
296//
297static void ParseXMLFile(char *fileName)
298{
299        CFPropertyListRef plist;
300        CFURLRef fileURL = NULL;
301        CFStringRef filePath = NULL;
302        CFStringRef errorString = NULL;
303        CFDataRef data = NULL;
304        SInt32 errorCode = 0;
305
306        filePath = CFStringCreateWithCString(kCFAllocatorDefault, fileName, kCFStringEncodingUTF8);
307        if (filePath == NULL) {
308          errx(1, "Could not create file path string");
309        }
310
311        // Create a URL that specifies the file we will create to
312        // hold the XML data.
313        fileURL = CFURLCreateWithFileSystemPath( kCFAllocatorDefault,
314                                                 filePath,
315                                                 kCFURLPOSIXPathStyle,
316                                                 false /* not a directory */ );
317        if (fileURL == NULL) {
318          errx(1, "Could not create file path URL");
319        }
320
321        CFRelease(filePath);
322
323        if (! CFURLCreateDataAndPropertiesFromResource(
324                    kCFAllocatorDefault,
325                    fileURL,
326                    &data,
327                    NULL,
328                    NULL,
329                    &errorCode) || data == NULL ) {
330          errx(1, "Error reading XML file (%d)", (int)errorCode);
331        }
332
333        CFRelease(fileURL);
334
335        plist = CFPropertyListCreateFromXMLData(kCFAllocatorDefault,
336                                                data,
337                                                kCFPropertyListImmutable,
338                                                &errorString);
339
340        CFRelease(data);
341
342        if (plist == NULL) {
343          errx(1, "Error parsing XML file");
344        }
345
346        if (errorString != NULL) {
347          errx(1, "Error parsing XML file: %s", CFStringGetCStringPtr(errorString, kCFStringEncodingUTF8));
348        }
349
350        CFDictionaryApplyFunction(plist, &SetOFVariableFromFile, 0);
351
352        CFRelease(plist);
353}
354
355// SetOrGetOFVariable(str)
356//
357//   Parse the input string, then set or get the specified
358//   firmware variable.
359//
360static void SetOrGetOFVariable(char *str)
361{
362  long          set = 0;
363  char          *name;
364  char          *value;
365  CFStringRef   nameRef;
366  CFTypeRef     valueRef;
367  kern_return_t result;
368
369  // OF variable name is first.
370  name = str;
371
372  // Find the equal sign for set
373  while (*str) {
374    if (*str == '=') {
375      set = 1;
376      *str++ = '\0';
377      break;
378    }
379    str++;
380  }
381
382  if (set == 1) {
383    // On sets, the OF variable's value follows the equal sign.
384    value = str;
385
386    result = SetOFVariable(name, value);
387	NVRamSyncNow(name);			/* Try syncing the new data to device, best effort! */
388    if (result != KERN_SUCCESS) {
389      errx(1, "Error setting variable - '%s': %s", name,
390           mach_error_string(result));
391    }
392  } else {
393    result = GetOFVariable(name, &nameRef, &valueRef);
394    if (result != KERN_SUCCESS) {
395      errx(1, "Error getting variable - '%s': %s", name,
396           mach_error_string(result));
397    }
398
399    PrintOFVariable(nameRef, valueRef, 0);
400    CFRelease(nameRef);
401    CFRelease(valueRef);
402  }
403}
404
405
406// GetOFVariable(name, nameRef, valueRef)
407//
408//   Get the named firmware variable.
409//   Return it and it's symbol in valueRef and nameRef.
410//
411static kern_return_t GetOFVariable(char *name, CFStringRef *nameRef,
412				   CFTypeRef *valueRef)
413{
414  *nameRef = CFStringCreateWithCString(kCFAllocatorDefault, name,
415				       kCFStringEncodingUTF8);
416  if (*nameRef == 0) {
417    errx(1, "Error creating CFString for key %s", name);
418  }
419
420  *valueRef = IORegistryEntryCreateCFProperty(gOptionsRef, *nameRef, 0, 0);
421  if (*valueRef == 0) return kIOReturnNotFound;
422
423  return KERN_SUCCESS;
424}
425
426
427// SetOFVariable(name, value)
428//
429//   Set or create an firmware variable with name and value.
430//
431static kern_return_t SetOFVariable(char *name, char *value)
432{
433  CFStringRef   nameRef;
434  CFTypeRef     valueRef;
435  CFTypeID      typeID;
436  kern_return_t result = KERN_SUCCESS;
437
438  nameRef = CFStringCreateWithCString(kCFAllocatorDefault, name,
439				      kCFStringEncodingUTF8);
440  if (nameRef == 0) {
441    errx(1, "Error creating CFString for key %s", name);
442  }
443
444  valueRef = IORegistryEntryCreateCFProperty(gOptionsRef, nameRef, 0, 0);
445  if (valueRef) {
446    typeID = CFGetTypeID(valueRef);
447    CFRelease(valueRef);
448
449    valueRef = ConvertValueToCFTypeRef(typeID, value);
450    if (valueRef == 0) {
451      errx(1, "Error creating CFTypeRef for value %s", value);
452    }  result = IORegistryEntrySetCFProperty(gOptionsRef, nameRef, valueRef);
453  } else {
454    while (1) {
455      // In the default case, try data, string, number, then boolean.
456
457      valueRef = ConvertValueToCFTypeRef(CFDataGetTypeID(), value);
458      if (valueRef != 0) {
459	result = IORegistryEntrySetCFProperty(gOptionsRef, nameRef, valueRef);
460	if (result == KERN_SUCCESS) break;
461      }
462
463      valueRef = ConvertValueToCFTypeRef(CFStringGetTypeID(), value);
464      if (valueRef != 0) {
465	result = IORegistryEntrySetCFProperty(gOptionsRef, nameRef, valueRef);
466	if (result == KERN_SUCCESS) break;
467      }
468
469      valueRef = ConvertValueToCFTypeRef(CFNumberGetTypeID(), value);
470      if (valueRef != 0) {
471	result = IORegistryEntrySetCFProperty(gOptionsRef, nameRef, valueRef);
472	if (result == KERN_SUCCESS) break;
473      }
474
475      valueRef = ConvertValueToCFTypeRef(CFBooleanGetTypeID(), value);
476      if (valueRef != 0) {
477	result = IORegistryEntrySetCFProperty(gOptionsRef, nameRef, valueRef);
478	if (result == KERN_SUCCESS) break;
479      }
480
481      break;
482    }
483  }
484
485  CFRelease(nameRef);
486
487  return result;
488}
489
490
491// DeleteOFVariable(name)
492//
493//   Delete the named firmware variable.
494//
495//
496static void DeleteOFVariable(char *name)
497{
498  SetOFVariable(kIONVRAMDeletePropertyKey, name);
499}
500
501static void NVRamSyncNow(char *name)
502{
503  SetOFVariable(kIONVRAMSyncNowPropertyKey, name);
504}
505
506// PrintOFVariables()
507//
508//   Print all of the firmware variables.
509//
510static void PrintOFVariables()
511{
512  kern_return_t          result;
513  CFMutableDictionaryRef dict;
514
515  result = IORegistryEntryCreateCFProperties(gOptionsRef, &dict, 0, 0);
516  if (result != KERN_SUCCESS) {
517    errx(1, "Error getting the firmware variables: %s", mach_error_string(result));
518  }
519
520  if (gUseXML) {
521    CFDataRef data;
522
523    data = CFPropertyListCreateXMLData( kCFAllocatorDefault, dict );
524    if (data == NULL) {
525      errx(1, "Error converting variables to xml");
526    }
527
528    fwrite(CFDataGetBytePtr(data), sizeof(UInt8), CFDataGetLength(data), stdout);
529
530    CFRelease(data);
531
532  } else {
533
534    CFDictionaryApplyFunction(dict, &PrintOFVariable, 0);
535
536  }
537
538  CFRelease(dict);
539}
540
541// PrintOFVariable(key, value, context)
542//
543//   Print the given firmware variable.
544//
545static void PrintOFVariable(const void *key, const void *value, void *context)
546{
547  long          cnt, cnt2;
548  CFIndex       nameLen;
549  char          *nameBuffer = 0;
550  const char    *nameString;
551  char          numberBuffer[10];
552  const uint8_t *dataPtr;
553  uint8_t       dataChar;
554  char          *dataBuffer = 0;
555  CFIndex       valueLen;
556  char          *valueBuffer = 0;
557  const char    *valueString = 0;
558  uint32_t      number, length;
559  CFTypeID      typeID;
560
561  // Get the OF variable's name.
562  nameLen = CFStringGetLength(key) + 1;
563  nameBuffer = malloc(nameLen);
564  if( nameBuffer && CFStringGetCString(key, nameBuffer, nameLen, kCFStringEncodingUTF8) )
565    nameString = nameBuffer;
566  else {
567    warnx("Unable to convert property name to C string");
568    nameString = "<UNPRINTABLE>";
569  }
570
571  // Get the OF variable's type.
572  typeID = CFGetTypeID(value);
573
574  if (typeID == CFBooleanGetTypeID()) {
575    if (CFBooleanGetValue(value)) valueString = "true";
576    else valueString = "false";
577  } else if (typeID == CFNumberGetTypeID()) {
578    CFNumberGetValue(value, kCFNumberSInt32Type, &number);
579    if (number == 0xFFFFFFFF) sprintf(numberBuffer, "-1");
580    else if (number < 1000) sprintf(numberBuffer, "%d", number);
581    else sprintf(numberBuffer, "0x%x", number);
582    valueString = numberBuffer;
583  } else if (typeID == CFStringGetTypeID()) {
584    valueLen = CFStringGetLength(value) + 1;
585    valueBuffer = malloc(valueLen + 1);
586    if ( valueBuffer && CFStringGetCString(value, valueBuffer, valueLen, kCFStringEncodingUTF8) )
587      valueString = valueBuffer;
588    else {
589      warnx("Unable to convert value to C string");
590      valueString = "<UNPRINTABLE>";
591    }
592  } else if (typeID == CFDataGetTypeID()) {
593    length = CFDataGetLength(value);
594    if (length == 0) valueString = "";
595    else {
596      dataBuffer = malloc(length * 3 + 1);
597      if (dataBuffer != 0) {
598	dataPtr = CFDataGetBytePtr(value);
599	for (cnt = cnt2 = 0; cnt < length; cnt++) {
600	  dataChar = dataPtr[cnt];
601	  if (isprint(dataChar) && dataChar != '%') {
602	    dataBuffer[cnt2++] = dataChar;
603	  } else {
604	    sprintf(dataBuffer + cnt2, "%%%02x", dataChar);
605	    cnt2 += 3;
606	  }
607	}
608	dataBuffer[cnt2] = '\0';
609	valueString = dataBuffer;
610      }
611    }
612  } else {
613    valueString="<INVALID>";
614  }
615
616  if ((nameString != 0) && (valueString != 0))
617    printf("%s\t%s\n", nameString, valueString);
618
619  if (dataBuffer != 0) free(dataBuffer);
620  if (nameBuffer != 0) free(nameBuffer);
621  if (valueBuffer != 0) free(valueBuffer);
622}
623
624// ClearOFVariables()
625//
626//   Deletes all OF variables
627//
628static void ClearOFVariables(void)
629{
630    kern_return_t          result;
631    CFMutableDictionaryRef dict;
632
633    result = IORegistryEntryCreateCFProperties(gOptionsRef, &dict, 0, 0);
634    if (result != KERN_SUCCESS) {
635      errx(1, "Error getting the firmware variables: %s", mach_error_string(result));
636    }
637    CFDictionaryApplyFunction(dict, &ClearOFVariable, 0);
638
639    CFRelease(dict);
640}
641
642static void ClearOFVariable(const void *key, const void *value, void *context)
643{
644  kern_return_t result;
645  result = IORegistryEntrySetCFProperty(gOptionsRef,
646                                        CFSTR(kIONVRAMDeletePropertyKey), key);
647  if (result != KERN_SUCCESS) {
648    errx(1, "Error clearing firmware variables: %s", mach_error_string(result));
649  }
650}
651
652// ConvertValueToCFTypeRef(typeID, value)
653//
654//   Convert the value into a CFType given the typeID.
655//
656static CFTypeRef ConvertValueToCFTypeRef(CFTypeID typeID, char *value)
657{
658  CFTypeRef     valueRef = 0;
659  long          cnt, cnt2, length;
660  unsigned long number, tmp;
661
662  if (typeID == CFBooleanGetTypeID()) {
663    if (!strcmp("true", value)) valueRef = kCFBooleanTrue;
664    else if (!strcmp("false", value)) valueRef = kCFBooleanFalse;
665  } else if (typeID == CFNumberGetTypeID()) {
666    number = strtol(value, 0, 0);
667    valueRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type,
668			      &number);
669  } else if (typeID == CFStringGetTypeID()) {
670    valueRef = CFStringCreateWithCString(kCFAllocatorDefault, value,
671					 kCFStringEncodingUTF8);
672  } else if (typeID == CFDataGetTypeID()) {
673    length = strlen(value);
674    for (cnt = cnt2 = 0; cnt < length; cnt++, cnt2++) {
675      if (value[cnt] == '%') {
676	if (!ishexnumber(value[cnt + 1]) ||
677	    !ishexnumber(value[cnt + 2])) return 0;
678	number = toupper(value[++cnt]) - '0';
679	if (number > 9) number -= 7;
680	tmp = toupper(value[++cnt]) - '0';
681	if (tmp > 9) tmp -= 7;
682	number = (number << 4) + tmp;
683	value[cnt2] = number;
684      } else value[cnt2] = value[cnt];
685    }
686    valueRef = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, (const UInt8 *)value,
687					   cnt2, kCFAllocatorDefault);
688  } else return 0;
689
690  return valueRef;
691}
692
693static void SetOFVariableFromFile(const void *key, const void *value, void *context)
694{
695  kern_return_t result;
696
697  result = IORegistryEntrySetCFProperty(gOptionsRef, key, value);
698  if ( result != KERN_SUCCESS ) {
699          int nameLen;
700          char *nameBuffer;
701          char *nameString;
702
703          // Get the variable's name.
704          nameLen = CFStringGetLength(key) + 1;
705          nameBuffer = malloc(nameLen);
706          if( nameBuffer && CFStringGetCString(key, nameBuffer, nameLen, kCFStringEncodingUTF8) )
707                  nameString = nameBuffer;
708          else {
709                  warnx("Unable to convert property name to C string");
710                  nameString = "<UNPRINTABLE>";
711          }
712          errx(1, "Error setting variable - '%s': %s", nameString,
713               mach_error_string(result));
714  }
715}
716