bsd.c   [plain text]


/*
 * Copyright (c) 2018 Apple Inc. All rights reserved.
 *
 * @APPLE_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. 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_LICENSE_HEADER_END@
 */
#include "internal.h"

#pragma mark Utilities

/*
 * Factored out from _get_parse_boot_arg_value for unit testing purposes
 */
static bool
_parse_boot_arg_value(char *argsbuff, const char *which, char *where, size_t max)
{
	bool found = false;

	char *token = NULL;
	char *argsstr = argsbuff;
	static const char seps[] = { ' ', '\t', 0 };
	while ((token = strsep(&argsstr, seps)) != NULL) {
		bool is_boolean = false;

		char *value = NULL;
		char *equals = strchr(token, '=');
		if (token[0] == '-') {
			/*
			 * Arguments whose names begins with "-" are booleans, so don't get
			 * key=value splitting.  Though I'd still discourage you from
			 * naming your option "-edge=case".
			 */
			is_boolean = true;
		} else if (equals) {
			equals[0] = '\0';
			value = &equals[1];
		} else {
			is_boolean = true;
		}

		if (strcmp(which, token) == 0) {
			/*
			 * Found it! Copy out the value as required.
			 */
			found = true;

			if (!where) {
				// Caller just wants to know whether the boot-arg exists.
			} else if (is_boolean || value == NULL) {
				strlcpy(where, "", max);
			} else {
				strlcpy(where, value, max);
			}

			break;
		}
	}

	return found;
}

/*
 * This is (very) loosely based on the implementation of
 * PE_parse_boot_argn() (or at least the parts where I was able to easily
 * decipher the policy).
 */
static bool
_get_boot_arg_value(const char *which, char *where, size_t max)
{
	bool found = false;
	__os_free char *argsbuff = NULL;
	size_t argsbuff_len = 0;
	errno_t error = sysctlbyname_get_data_np("kern.bootargs",
			(void **)&argsbuff, &argsbuff_len);

	if (!error) {
		found = _parse_boot_arg_value(argsbuff, which, where, max);
	}

	return found;
}

#pragma mark API
errno_t
sysctl_get_data_np(int mib[4], size_t mib_cnt, void **buff, size_t *buff_len)
{
	errno_t error = 0;
	int ret = 0;
	size_t needed = 0;
	void *mybuff = NULL;

	// We need to get the length of the parameter so we can allocate a buffer
	// that's large enough.
	ret = sysctl(mib, (unsigned int)mib_cnt, NULL, &needed, NULL, 0);
	if (ret) {
		error = errno;
		goto __out;
	}

	mybuff = malloc(needed);
	if (!mybuff) {
		error = errno;
		goto __out;
	}

	ret = sysctl(mib, (unsigned int)mib_cnt, mybuff, &needed, NULL, 0);
	if (ret) {
		// It's conceivable that some other process came along within this
		// window and modified the variable to be even larger than we'd
		// previously been told, but if that's the case, just give up.
		error = errno;
		goto __out;
	}

	*buff = mybuff;
	*buff_len = needed;

__out:
	if (error) {
		free(mybuff);
	}
	return error;
}

errno_t
sysctlbyname_get_data_np(const char *mibdesc, void **buff, size_t *buff_len)
{
	int ret = -1;
	int error = -1;
	int mib[4];
	size_t mib_cnt = countof(mib);

	ret = sysctlnametomib(mibdesc, mib, &mib_cnt);
	if (ret) {
		error = errno;
		goto __out;
	}

	error = sysctl_get_data_np(mib, mib_cnt, buff, buff_len);

__out:
	return error;
}

bool
os_parse_boot_arg_int(const char *which, int64_t *where)
{
	bool found = false;
	char buff[24] = {0};
	char *endptr = NULL;
	int64_t val = 0;

	found = _get_boot_arg_value(which, buff, sizeof(buff));
	if (!found || !where) {
		goto __out;
	}

	// A base of zero handles bases 8, 10, and 16.
	val = strtoll(buff, &endptr, 0);
	if (*endptr == 0) {
		*where = val;
	} else {
		// The boot-arg value was invalid, so say we didn't find it.
		found = false;
	}

__out:
	return found;
}

bool
os_parse_boot_arg_string(const char *which, char *where, size_t maxlen)
{
	return _get_boot_arg_value(which, where, maxlen);
}