/*
 * Copyright (c) 2000-2008 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@
 */
/*
 *	File: libkern/kernel_mach_header.c
 *
 *	Functions for accessing mach-o headers.
 *
 * NOTE:	This file supports only kernel mach headers at the present
 *		time; it's primary use is by kld, and all externally
 *		referenced routines at the present time operate against
 *		the kernel mach header _mh_execute_header, which is the
 *		header for the currently executing kernel.
 *
 */

#include <vm/vm_map.h>
#include <vm/vm_kern.h>
#include <libkern/kernel_mach_header.h>
#include <string.h>             // from libsa

/*
 * return the last address (first avail)
 *
 * This routine operates against the currently executing kernel only
 */
vm_offset_t
getlastaddr(void)
{
	kernel_segment_command_t        *sgp;
	vm_offset_t             last_addr = 0;
	kernel_mach_header_t *header = &_mh_execute_header;
	unsigned long i;

	sgp = (kernel_segment_command_t *)
	    ((uintptr_t)header + sizeof(kernel_mach_header_t));
	for (i = 0; i < header->ncmds; i++) {
		if (sgp->cmd == LC_SEGMENT_KERNEL) {
			if (sgp->vmaddr + sgp->vmsize > last_addr) {
				last_addr = sgp->vmaddr + sgp->vmsize;
			}
		}
		sgp = (kernel_segment_command_t *)((uintptr_t)sgp + sgp->cmdsize);
	}
	return last_addr;
}

/*
 * Find the specified load command in the Mach-O headers, and return
 * the command. If there is no such load command, NULL is returned.
 */
void *
getcommandfromheader(kernel_mach_header_t *mhp, uint32_t cmd)
{
	struct load_command *lcp;
	unsigned long i;

	lcp = (struct load_command *) (mhp + 1);
	for (i = 0; i < mhp->ncmds; i++) {
		if (lcp->cmd == cmd) {
			return (void *)lcp;
		}

		lcp = (struct load_command *)((uintptr_t)lcp + lcp->cmdsize);
	}

	return NULL;
}

/*
 * Find the UUID load command in the Mach-O headers, and return
 * the address of the UUID blob and size in "*size". If the
 * Mach-O image is missing a UUID, NULL is returned.
 */
void *
getuuidfromheader(kernel_mach_header_t *mhp, unsigned long *size)
{
	struct uuid_command *cmd = (struct uuid_command *)
	    getcommandfromheader(mhp, LC_UUID);

	if (cmd != NULL) {
		if (size) {
			*size = sizeof(cmd->uuid);
		}
		return cmd->uuid;
	}

	return NULL;
}

/*
 * This routine returns the a pointer to the data for the named section in the
 * named segment if it exist in the mach header passed to it.  Also it returns
 * the size of the section data indirectly through the pointer size.  Otherwise
 *  it returns zero for the pointer and the size.
 *
 * This routine can operate against any kernel mach header.
 */
void *
getsectdatafromheader(
	kernel_mach_header_t *mhp,
	const char *segname,
	const char *sectname,
	unsigned long *size)
{
	const kernel_section_t *sp;
	void *result;

	sp = getsectbynamefromheader(mhp, segname, sectname);
	if (sp == (kernel_section_t *)0) {
		*size = 0;
		return (char *)0;
	}
	*size = sp->size;
	result = (void *)sp->addr;
	return result;
}

/*
 * This routine returns the offset for the named section in the
 * named segment if it exist in the mach header passed to it. Otherwise
 *  it returns zero.
 *
 * This routine can operate against any kernel mach header.
 */
uint32_t
getsectoffsetfromheader(
	kernel_mach_header_t *mhp,
	const char *segname,
	const char *sectname)
{
	const kernel_section_t *sp;

	sp = getsectbynamefromheader(mhp, segname, sectname);
	if (sp == (kernel_section_t *)0) {
		return 0;
	}

	return sp->offset;
}

/*
 * This routine returns the a pointer to the data for the named segment
 * if it exist in the mach header passed to it.  Also it returns
 * the size of the segment data indirectly through the pointer size.
 * Otherwise it returns zero for the pointer and the size.
 */
void *
getsegdatafromheader(
	kernel_mach_header_t *mhp,
	const char *segname,
	unsigned long *size)
{
	const kernel_segment_command_t *sc;
	void *result;

	sc = getsegbynamefromheader(mhp, segname);
	if (sc == (kernel_segment_command_t *)0) {
		*size = 0;
		return (char *)0;
	}
	*size = sc->vmsize;
	result = (void *)sc->vmaddr;
	return result;
}

/*
 * This routine returns the section structure for the named section in the
 * named segment for the mach_header pointer passed to it if it exist.
 * Otherwise it returns zero.
 *
 * This routine can operate against any kernel mach header.
 */
kernel_section_t *
getsectbynamefromheader(
	kernel_mach_header_t *mhp,
	const char *segname,
	const char *sectname)
{
	kernel_segment_command_t *sgp;
	kernel_section_t *sp;
	unsigned long i, j;

	sgp = (kernel_segment_command_t *)
	    ((uintptr_t)mhp + sizeof(kernel_mach_header_t));
	for (i = 0; i < mhp->ncmds; i++) {
		if (sgp->cmd == LC_SEGMENT_KERNEL) {
			if (strncmp(sgp->segname, segname, sizeof(sgp->segname)) == 0 ||
			    mhp->filetype == MH_OBJECT) {
				sp = (kernel_section_t *)((uintptr_t)sgp +
				    sizeof(kernel_segment_command_t));
				for (j = 0; j < sgp->nsects; j++) {
					if (strncmp(sp->sectname, sectname,
					    sizeof(sp->sectname)) == 0 &&
					    strncmp(sp->segname, segname,
					    sizeof(sp->segname)) == 0) {
						return sp;
					}
					sp = (kernel_section_t *)((uintptr_t)sp +
					    sizeof(kernel_section_t));
				}
			}
		}
		sgp = (kernel_segment_command_t *)((uintptr_t)sgp + sgp->cmdsize);
	}
	return (kernel_section_t *)NULL;
}

/*
 * This routine can operate against any kernel mach header.
 */
kernel_segment_command_t *
getsegbynamefromheader(
	kernel_mach_header_t    *header,
	const char              *seg_name)
{
	kernel_segment_command_t *sgp;
	unsigned long i;

	sgp = (kernel_segment_command_t *)
	    ((uintptr_t)header + sizeof(kernel_mach_header_t));
	for (i = 0; i < header->ncmds; i++) {
		if (sgp->cmd == LC_SEGMENT_KERNEL
		    && !strncmp(sgp->segname, seg_name, sizeof(sgp->segname))) {
			return sgp;
		}
		sgp = (kernel_segment_command_t *)((uintptr_t)sgp + sgp->cmdsize);
	}
	return (kernel_segment_command_t *)NULL;
}

/*
 * Return the first segment_command in the header.
 */
kernel_segment_command_t *
firstseg(void)
{
	return firstsegfromheader(&_mh_execute_header);
}

kernel_segment_command_t *
firstsegfromheader(kernel_mach_header_t *header)
{
	u_int i = 0;
	kernel_segment_command_t *sgp = (kernel_segment_command_t *)
	    ((uintptr_t)header + sizeof(*header));

	for (i = 0; i < header->ncmds; i++) {
		if (sgp->cmd == LC_SEGMENT_KERNEL) {
			return sgp;
		}
		sgp = (kernel_segment_command_t *)((uintptr_t)sgp + sgp->cmdsize);
	}
	return (kernel_segment_command_t *)NULL;
}

/*
 * This routine operates against any kernel mach segment_command structure
 * pointer and the provided kernel header, to obtain the sequentially next
 * segment_command structure in that header.
 */
kernel_segment_command_t *
nextsegfromheader(
	kernel_mach_header_t    *header,
	kernel_segment_command_t        *seg)
{
	u_int i = 0;
	kernel_segment_command_t *sgp = (kernel_segment_command_t *)
	    ((uintptr_t)header + sizeof(*header));

	/* Find the index of the passed-in segment */
	for (i = 0; sgp != seg && i < header->ncmds; i++) {
		sgp = (kernel_segment_command_t *)((uintptr_t)sgp + sgp->cmdsize);
	}

	/* Increment to the next load command */
	i++;
	sgp = (kernel_segment_command_t *)((uintptr_t)sgp + sgp->cmdsize);

	/* Return the next segment command, if any */
	for (; i < header->ncmds; i++) {
		if (sgp->cmd == LC_SEGMENT_KERNEL) {
			return sgp;
		}

		sgp = (kernel_segment_command_t *)((uintptr_t)sgp + sgp->cmdsize);
	}

	return (kernel_segment_command_t *)NULL;
}


/*
 * Return the address of the named Mach-O segment from the currently
 * executing kernel kernel, or NULL.
 */
kernel_segment_command_t *
getsegbyname(const char *seg_name)
{
	return getsegbynamefromheader(&_mh_execute_header, seg_name);
}

/*
 * This routine returns the a pointer the section structure of the named
 * section in the named segment if it exists in the currently executing
 * kernel, which it is presumed to be linked into.  Otherwise it returns NULL.
 */
kernel_section_t *
getsectbyname(
	const char *segname,
	const char *sectname)
{
	return getsectbynamefromheader(
		(kernel_mach_header_t *)&_mh_execute_header, segname, sectname);
}

/*
 * This routine can operate against any kernel segment_command structure to
 * return the first kernel section immediately following that structure.  If
 * there are no sections associated with the segment_command structure, it
 * returns NULL.
 */
kernel_section_t *
firstsect(kernel_segment_command_t *sgp)
{
	if (!sgp || sgp->nsects == 0) {
		return (kernel_section_t *)NULL;
	}

	return (kernel_section_t *)(sgp + 1);
}

/*
 * This routine can operate against any kernel segment_command structure and
 * kernel section to return the next consecutive  kernel section immediately
 * following the kernel section provided.  If there are no sections following
 * the provided section, it returns NULL.
 */
kernel_section_t *
nextsect(kernel_segment_command_t *sgp, kernel_section_t *sp)
{
	kernel_section_t *fsp = firstsect(sgp);

	if (((uintptr_t)(sp - fsp) + 1) >= sgp->nsects) {
		return (kernel_section_t *)NULL;
	}

	return sp + 1;
}
