507 lines
13 KiB
C
507 lines
13 KiB
C
/*
|
|
* Copyright (c) 2000-2016 Apple Inc. All rights reserved.
|
|
*
|
|
* @APPLE_OSREFERENCE_LICENSE_HEADER_START@
|
|
*
|
|
* This file contains Original Code and/or Modifications of Original Code
|
|
* as defined in and that are subject to the Apple Public Source License
|
|
* Version 2.0 (the 'License'). You may not use this file except in
|
|
* compliance with the License. The rights granted to you under the License
|
|
* may not be used to create, or enable the creation or redistribution of,
|
|
* unlawful or unlicensed copies of an Apple operating system, or to
|
|
* circumvent, violate, or enable the circumvention or violation of, any
|
|
* terms of an Apple operating system software license agreement.
|
|
*
|
|
* Please obtain a copy of the License at
|
|
* http://www.opensource.apple.com/apsl/ and read it before using this file.
|
|
*
|
|
* The 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, QUIET ENJOYMENT OR NON-INFRINGEMENT.
|
|
* Please see the License for the specific language governing rights and
|
|
* limitations under the License.
|
|
*
|
|
* @APPLE_OSREFERENCE_LICENSE_HEADER_END@
|
|
*/
|
|
#ifdef KERNEL
|
|
#include <sys/systm.h>
|
|
#include <libkern/OSKextLib.h>
|
|
#include <libkern/OSKextLibPrivate.h>
|
|
#else
|
|
#include <libc.h>
|
|
#include <libkern/OSKextLib.h>
|
|
#include <System/libkern/OSKextLibPrivate.h>
|
|
#endif /* KERNEL */
|
|
|
|
#include <libkern/OSKextLibPrivate.h>
|
|
|
|
#define VERS_MAJOR_DIGITS (4)
|
|
#define VERS_MINOR_DIGITS (4)
|
|
#define VERS_REVISION_DIGITS (4)
|
|
#define VERS_STAGE_DIGITS (1)
|
|
#define VERS_STAGE_LEVEL_DIGITS (3)
|
|
|
|
#define VERS_MAJOR_MAX (9999)
|
|
#define VERS_STAGE_LEVEL_MAX (255)
|
|
|
|
#define VERS_MAJOR_MULT (1000000000000)
|
|
#define VERS_MINOR_MULT (100000000)
|
|
#define VERS_REVISION_MULT (10000)
|
|
#define VERS_STAGE_MULT (1000)
|
|
|
|
|
|
typedef enum {
|
|
kOSKextVersionStageInvalid = 0,
|
|
kOSKextVersionStageDevelopment = 1,
|
|
kOSKextVersionStageAlpha = 3,
|
|
kOSKextVersionStageBeta = 5,
|
|
kOSKextVersionStageCandidate = 7,
|
|
kOSKextVersionStageRelease = 9,
|
|
} OSKextVersionStage;
|
|
|
|
|
|
/*********************************************************************
|
|
*********************************************************************/
|
|
static int
|
|
__vers_isdigit(char c)
|
|
{
|
|
return c == '0' ||
|
|
c == '1' || c == '2' || c == '3' ||
|
|
c == '4' || c == '5' || c == '6' ||
|
|
c == '7' || c == '8' || c == '9';
|
|
}
|
|
|
|
/*********************************************************************
|
|
*********************************************************************/
|
|
static int
|
|
__vers_isspace(char c)
|
|
{
|
|
return c == ' ' ||
|
|
c == '\t' ||
|
|
c == '\r' ||
|
|
c == '\n';
|
|
}
|
|
|
|
/*********************************************************************
|
|
*********************************************************************/
|
|
static int
|
|
__vers_digit_for_char(char c)
|
|
{
|
|
switch (c) {
|
|
case '0': return 0;
|
|
case '1': return 1;
|
|
case '2': return 2;
|
|
case '3': return 3;
|
|
case '4': return 4;
|
|
case '5': return 5;
|
|
case '6': return 6;
|
|
case '7': return 7;
|
|
case '8': return 8;
|
|
case '9': return 9;
|
|
default: return -1;
|
|
}
|
|
}
|
|
|
|
/*********************************************************************
|
|
*********************************************************************/
|
|
static int
|
|
__VERS_isreleasestate(char c)
|
|
{
|
|
return c == 'd' || c == 'a' || c == 'b' || c == 'f';
|
|
}
|
|
|
|
|
|
/*********************************************************************
|
|
*********************************************************************/
|
|
static OSKextVersionStage
|
|
__OSKextVersionStageForString(const char ** string_p)
|
|
{
|
|
const char * string;
|
|
|
|
if (!string_p || !*string_p) {
|
|
return kOSKextVersionStageInvalid;
|
|
}
|
|
|
|
string = *string_p;
|
|
|
|
if (__vers_isspace(string[0]) || string[0] == '\0') {
|
|
return kOSKextVersionStageRelease;
|
|
} else {
|
|
switch (string[0]) {
|
|
case 'd':
|
|
if (__vers_isdigit(string[1])) {
|
|
*string_p = &string[1];
|
|
return kOSKextVersionStageDevelopment;
|
|
}
|
|
break;
|
|
case 'a':
|
|
if (__vers_isdigit(string[1])) {
|
|
*string_p = &string[1];
|
|
return kOSKextVersionStageAlpha;
|
|
}
|
|
break;
|
|
case 'b':
|
|
if (__vers_isdigit(string[1])) {
|
|
*string_p = &string[1];
|
|
return kOSKextVersionStageBeta;
|
|
}
|
|
break;
|
|
case 'f':
|
|
if (__vers_isdigit(string[1])) {
|
|
*string_p = &string[1];
|
|
return kOSKextVersionStageCandidate;
|
|
} else if (string[1] == 'c' && __vers_isdigit(string[2])) {
|
|
*string_p = &string[2];
|
|
return kOSKextVersionStageCandidate;
|
|
} else {
|
|
return kOSKextVersionStageInvalid;
|
|
}
|
|
default:
|
|
return kOSKextVersionStageInvalid;
|
|
}
|
|
}
|
|
|
|
return kOSKextVersionStageInvalid;
|
|
}
|
|
|
|
/*********************************************************************
|
|
*********************************************************************/
|
|
static const char *
|
|
__OSKextVersionStringForStage(OSKextVersion stage)
|
|
{
|
|
switch (stage) {
|
|
default:
|
|
OS_FALLTHROUGH;
|
|
case kOSKextVersionStageInvalid: return NULL;
|
|
case kOSKextVersionStageDevelopment: return "d";
|
|
case kOSKextVersionStageAlpha: return "a";
|
|
case kOSKextVersionStageBeta: return "b";
|
|
case kOSKextVersionStageCandidate: return "f";
|
|
case kOSKextVersionStageRelease: return "";
|
|
}
|
|
}
|
|
|
|
/*********************************************************************
|
|
*********************************************************************/
|
|
OSKextVersion
|
|
OSKextParseVersionString(const char * versionString)
|
|
{
|
|
OSKextVersion result = -1;
|
|
int vers_digit = -1;
|
|
int num_digits_scanned = 0;
|
|
OSKextVersion vers_major = 0;
|
|
OSKextVersion vers_minor = 0;
|
|
OSKextVersion vers_revision = 0;
|
|
OSKextVersionStage vers_stage = 0;
|
|
OSKextVersion vers_stage_level = 0;
|
|
const char * current_char_p;
|
|
|
|
if (!versionString || *versionString == '\0') {
|
|
return -1;
|
|
}
|
|
|
|
current_char_p = (const char *)&versionString[0];
|
|
|
|
/*****
|
|
* Check for an initial digit of the major release number.
|
|
*/
|
|
vers_major = __vers_digit_for_char(*current_char_p);
|
|
if (vers_major < 0) {
|
|
return -1;
|
|
}
|
|
|
|
current_char_p++;
|
|
num_digits_scanned = 1;
|
|
|
|
/* Complete scan for major version number. Legal characters are
|
|
* any digit, period, any buildstage letter.
|
|
*/
|
|
while (num_digits_scanned < VERS_MAJOR_DIGITS) {
|
|
if (__vers_isspace(*current_char_p) || *current_char_p == '\0') {
|
|
vers_stage = kOSKextVersionStageRelease;
|
|
goto finish;
|
|
} else if (__vers_isdigit(*current_char_p)) {
|
|
vers_digit = __vers_digit_for_char(*current_char_p);
|
|
if (vers_digit < 0) {
|
|
return -1;
|
|
}
|
|
vers_major = (vers_major) * 10 + vers_digit;
|
|
current_char_p++;
|
|
num_digits_scanned++;
|
|
} else if (__VERS_isreleasestate(*current_char_p)) {
|
|
goto release_state;
|
|
} else if (*current_char_p == '.') {
|
|
current_char_p++;
|
|
goto minor_version;
|
|
} else {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* Check for too many digits.
|
|
*/
|
|
if (num_digits_scanned == VERS_MAJOR_DIGITS) {
|
|
if (*current_char_p == '.') {
|
|
current_char_p++;
|
|
} else if (__vers_isdigit(*current_char_p)) {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
minor_version:
|
|
|
|
num_digits_scanned = 0;
|
|
|
|
/* Scan for minor version number. Legal characters are
|
|
* any digit, period, any buildstage letter.
|
|
*/
|
|
while (num_digits_scanned < VERS_MINOR_DIGITS) {
|
|
if (__vers_isspace(*current_char_p) || *current_char_p == '\0') {
|
|
vers_stage = kOSKextVersionStageRelease;
|
|
goto finish;
|
|
} else if (__vers_isdigit(*current_char_p)) {
|
|
vers_digit = __vers_digit_for_char(*current_char_p);
|
|
if (vers_digit < 0) {
|
|
return -1;
|
|
}
|
|
vers_minor = (vers_minor) * 10 + vers_digit;
|
|
current_char_p++;
|
|
num_digits_scanned++;
|
|
} else if (__VERS_isreleasestate(*current_char_p)) {
|
|
goto release_state;
|
|
} else if (*current_char_p == '.') {
|
|
current_char_p++;
|
|
goto revision;
|
|
} else {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* Check for too many digits.
|
|
*/
|
|
if (num_digits_scanned == VERS_MINOR_DIGITS) {
|
|
if (*current_char_p == '.') {
|
|
current_char_p++;
|
|
} else if (__vers_isdigit(*current_char_p)) {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
revision:
|
|
|
|
num_digits_scanned = 0;
|
|
|
|
/* Scan for revision version number. Legal characters are
|
|
* any digit, any buildstage letter (NOT PERIOD).
|
|
*/
|
|
while (num_digits_scanned < VERS_REVISION_DIGITS) {
|
|
if (__vers_isspace(*current_char_p) || *current_char_p == '\0') {
|
|
vers_stage = kOSKextVersionStageRelease;
|
|
goto finish;
|
|
} else if (__vers_isdigit(*current_char_p)) {
|
|
vers_digit = __vers_digit_for_char(*current_char_p);
|
|
if (vers_digit < 0) {
|
|
return -1;
|
|
}
|
|
vers_revision = (vers_revision) * 10 + vers_digit;
|
|
current_char_p++;
|
|
num_digits_scanned++;
|
|
} else if (__VERS_isreleasestate(*current_char_p)) {
|
|
goto release_state;
|
|
} else {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* Check for too many digits.
|
|
*/
|
|
if (num_digits_scanned == VERS_REVISION_DIGITS) {
|
|
if (*current_char_p == '.') {
|
|
current_char_p++;
|
|
} else if (__vers_isdigit(*current_char_p)) {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
release_state:
|
|
|
|
/*****
|
|
* Check for the release state.
|
|
*/
|
|
if (__vers_isspace(*current_char_p) || *current_char_p == '\0') {
|
|
vers_stage = kOSKextVersionStageRelease;
|
|
goto finish;
|
|
} else {
|
|
vers_stage = __OSKextVersionStageForString(¤t_char_p);
|
|
if (vers_stage == kOSKextVersionStageInvalid) {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
|
|
// stage level
|
|
|
|
num_digits_scanned = 0;
|
|
|
|
/* Scan for stage level number. Legal characters are
|
|
* any digit only.
|
|
*/
|
|
while (num_digits_scanned < VERS_STAGE_LEVEL_DIGITS) {
|
|
if (__vers_isspace(*current_char_p) || *current_char_p == '\0') {
|
|
if (num_digits_scanned) {
|
|
goto finish;
|
|
} else {
|
|
return -1;
|
|
}
|
|
} else if (__vers_isdigit(*current_char_p)) {
|
|
vers_digit = __vers_digit_for_char(*current_char_p);
|
|
if (vers_digit < 0) {
|
|
return -1;
|
|
}
|
|
vers_stage_level = (vers_stage_level) * 10 + vers_digit;
|
|
current_char_p++;
|
|
num_digits_scanned++;
|
|
} else {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* Check for too many digits.
|
|
*/
|
|
if ((num_digits_scanned == VERS_STAGE_LEVEL_DIGITS) &&
|
|
!(__vers_isspace(*current_char_p) || (*current_char_p == '\0'))) {
|
|
return -1;
|
|
}
|
|
|
|
if (vers_stage_level > VERS_STAGE_LEVEL_MAX) {
|
|
return -1;
|
|
}
|
|
|
|
finish:
|
|
|
|
if (vers_stage == kOSKextVersionStageCandidate && vers_stage_level == 0) {
|
|
return -1;
|
|
}
|
|
|
|
result = (vers_major * VERS_MAJOR_MULT) +
|
|
(vers_minor * VERS_MINOR_MULT) +
|
|
(vers_revision * VERS_REVISION_MULT) +
|
|
(vers_stage * VERS_STAGE_MULT) +
|
|
vers_stage_level;
|
|
|
|
return result;
|
|
}
|
|
|
|
/*********************************************************************
|
|
* This function must be safe to call in panic context.
|
|
*********************************************************************/
|
|
Boolean
|
|
OSKextVersionGetString(
|
|
OSKextVersion aVersion,
|
|
char * buffer,
|
|
uint32_t bufferLength)
|
|
{
|
|
int cpos = 0;
|
|
OSKextVersion vers_major = 0;
|
|
OSKextVersion vers_minor = 0;
|
|
OSKextVersion vers_revision = 0;
|
|
OSKextVersion vers_stage = 0;
|
|
OSKextVersion vers_stage_level = 0;
|
|
const char * stage_string = NULL;// don't free
|
|
|
|
/* No buffer or length less than longest possible vers string,
|
|
* return 0.
|
|
*/
|
|
if (!buffer || bufferLength < kOSKextVersionMaxLength) {
|
|
return FALSE;
|
|
}
|
|
|
|
bzero(buffer, bufferLength * sizeof(char));
|
|
|
|
if (aVersion < 0) {
|
|
strlcpy(buffer, "(invalid)", bufferLength);
|
|
return TRUE;
|
|
}
|
|
if (aVersion == 0) {
|
|
strlcpy(buffer, "(missing)", bufferLength);
|
|
return TRUE;
|
|
}
|
|
|
|
vers_major = aVersion / VERS_MAJOR_MULT;
|
|
if (vers_major > VERS_MAJOR_MAX) {
|
|
strlcpy(buffer, "(invalid)", bufferLength);
|
|
return TRUE;
|
|
}
|
|
|
|
vers_minor = aVersion - (vers_major * VERS_MAJOR_MULT);
|
|
vers_minor /= VERS_MINOR_MULT;
|
|
|
|
vers_revision = aVersion -
|
|
((vers_major * VERS_MAJOR_MULT) + (vers_minor * VERS_MINOR_MULT));
|
|
vers_revision /= VERS_REVISION_MULT;
|
|
|
|
vers_stage = aVersion -
|
|
((vers_major * VERS_MAJOR_MULT) + (vers_minor * VERS_MINOR_MULT) +
|
|
(vers_revision * VERS_REVISION_MULT));
|
|
vers_stage /= VERS_STAGE_MULT;
|
|
|
|
vers_stage_level = aVersion -
|
|
((vers_major * VERS_MAJOR_MULT) + (vers_minor * VERS_MINOR_MULT) +
|
|
(vers_revision * VERS_REVISION_MULT) + (vers_stage * VERS_STAGE_MULT));
|
|
if (vers_stage_level > VERS_STAGE_LEVEL_MAX) {
|
|
strlcpy(buffer, "(invalid)", bufferLength);
|
|
return TRUE;
|
|
}
|
|
|
|
cpos = scnprintf(buffer, bufferLength, "%u", (uint32_t)vers_major);
|
|
|
|
/* Always include the minor version; it just looks weird without.
|
|
*/
|
|
buffer[cpos] = '.';
|
|
cpos++;
|
|
cpos += scnprintf(buffer + cpos, bufferLength - cpos, "%u", (uint32_t)vers_minor);
|
|
|
|
/* The revision is displayed only if nonzero.
|
|
*/
|
|
if (vers_revision) {
|
|
buffer[cpos] = '.';
|
|
cpos++;
|
|
cpos += scnprintf(buffer + cpos, bufferLength - cpos, "%u",
|
|
(uint32_t)vers_revision);
|
|
}
|
|
|
|
stage_string = __OSKextVersionStringForStage(vers_stage);
|
|
if (!stage_string) {
|
|
strlcpy(buffer, "(invalid)", bufferLength);
|
|
return TRUE;
|
|
}
|
|
if (stage_string[0]) {
|
|
strlcat(buffer, stage_string, bufferLength);
|
|
cpos += strlen(stage_string);
|
|
}
|
|
|
|
if (vers_stage < kOSKextVersionStageRelease) {
|
|
snprintf(buffer + cpos, bufferLength - cpos, "%u", (uint32_t)vers_stage_level);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/*********************************************************************
|
|
*********************************************************************/
|
|
#ifndef KERNEL
|
|
OSKextVersion
|
|
OSKextParseVersionCFString(CFStringRef versionString)
|
|
{
|
|
OSKextVersion result = -1;
|
|
char versBuffer[kOSKextVersionMaxLength];
|
|
|
|
if (CFStringGetCString(versionString, versBuffer,
|
|
sizeof(versBuffer), kCFStringEncodingASCII)) {
|
|
result = OSKextParseVersionString(versBuffer);
|
|
}
|
|
return result;
|
|
}
|
|
#endif
|