/*-
 * SPDX-License-Identifier: BSD-2-Clause
 *
 * Copyright (c) 2011, 2012, 2013, 2015, 2016, 2019, Juniper Networks, Inc.
 * All rights reserved.
 *
 * Originally derived from:
 *	$NetBSD: kern_verifiedexec.c,v 1.7 2003/11/18 13:13:03 martin Exp $
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <sys/cdefs.h>

#include "opt_mac.h"

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/exec.h>
#include <sys/lock.h>
#include <sys/malloc.h>
#include <sys/mutex.h>
#include <sys/proc.h>
#include <sys/sbuf.h>
#ifdef MAC_VERIEXEC_DEBUG
#include <sys/syslog.h>
#endif
#include <sys/vnode.h>

#include "mac_veriexec.h"
#include "mac_veriexec_internal.h"

/**
 * @brief per-device meta-data storage
 */
struct veriexec_dev_list {
	dev_t fsid;	/**< file system identifier of the mount point */
	LIST_HEAD(filehead, mac_veriexec_file_info) file_head;
	    /**< list of per-file meta-data information */
	LIST_ENTRY(veriexec_dev_list) entries;
	    /**< next entries in the device list */
};

typedef LIST_HEAD(veriexec_devhead, veriexec_dev_list) veriexec_devhead_t;

/**
 * @brief Mutex to protect the meta-data store lists
 */
struct mtx ve_mutex;

/**
 * @brief Executables meta-data storage
 *
 * This is used to store the fingerprints for potentially-executable files.
 */
veriexec_devhead_t veriexec_dev_head;

/**
 * @brief Plain file meta-data storage
 *
 * This is used for files that are not allowed to be executed, but should
 * have fingerprint validation available.
 */
veriexec_devhead_t veriexec_file_dev_head;

/**
 * @internal
 * @brief Search the @p head meta-data list for the specified file identifier
 *     @p fileid in the file system identified by @p fsid
 *
 * If meta-data exists for file system identified by @p fsid, it has a
 * fingerprint list, and @p found_dev is not @c NULL then store true in the
 * location pointed to by @p found_dev
 *
 * @param head		meta-data list to search
 * @param fsid		file system identifier to look for
 * @param fileid	file to look for
 * @param gen		generation of file
 * @param found_dev	indicator that an entry for the file system was found
 *
 * @return A pointer to the meta-data inforation if meta-data exists for
 *     the specified file identifier, otherwise @c NULL
 */
static struct mac_veriexec_file_info *
get_veriexec_file(struct veriexec_devhead *head, dev_t fsid, long fileid,
    unsigned long gen, int *found_dev)
{
	struct veriexec_dev_list *lp;
	struct mac_veriexec_file_info *ip, *tip;

	ip = NULL;

	/* Initialize the value found_dev, if non-NULL */
	if (found_dev != NULL)
		*found_dev = 0;

	VERIEXEC_DEBUG(3, ("searching for file %ju.%lu on device %ju,"
	    " files=%d\n", (uintmax_t)fileid, gen, (uintmax_t)fsid,
	    (head == &veriexec_file_dev_head)));

	/* Get a lock to access the list */
	mtx_lock(&ve_mutex);

	/* First, look for the file system */
	for (lp = LIST_FIRST(head); lp != NULL; lp = LIST_NEXT(lp, entries))
		if (lp->fsid == fsid)
			break;

	/* We found the file system in the list */
	if (lp != NULL) {
		VERIEXEC_DEBUG(3, ("found matching dev number %ju\n",
		    (uintmax_t)lp->fsid));

		/* If found_dev is non-NULL, store true there */
		if (found_dev != NULL)
			*found_dev = 1;

		/* Next, look for the meta-data information for the file */
		LIST_FOREACH_SAFE(ip, &(lp->file_head), entries, tip) {
			if (ip->fileid == fileid) {
				if (ip->gen == gen)
					break;
				/* we need to garbage collect */
				LIST_REMOVE(ip, entries);
				if (ip->label)
					free(ip->label, M_VERIEXEC);
				free(ip, M_VERIEXEC);
			}
		}
	}

	/* Release the lock we obtained earlier */
	mtx_unlock(&ve_mutex);

	/* Return the meta-data information we found, if anything */
	return (ip);
}

/**
 * @internal
 * @brief Display the fingerprint for each entry in the device list
 *
 * @param sbp		sbuf to write output to
 * @param lp		pointer to device list
 */
static void
mac_veriexec_print_db_dev_list(struct sbuf *sbp, struct veriexec_dev_list *lp)
{
	struct mac_veriexec_file_info *ip;

#define FPB(i) (ip->fingerprint[i])
	for (ip = LIST_FIRST(&(lp->file_head)); ip != NULL;
	    ip = LIST_NEXT(ip, entries))
		sbuf_printf(sbp, "  %ld: %u %ld [%02x %02x %02x %02x %02x "
		    "%02x %02x %02x...]\n", ip->fileid, ip->flags, ip->gen,
		    FPB(0), FPB(1), FPB(2), FPB(3), FPB(4), FPB(5), FPB(6),
		    FPB(7));
}

/**
 * @internal
 * @brief Display the device list
 *
 * @param sbp		sbuf to write output to
 * @param head		pointer to head of the device list
 */
static void
mac_veriexec_print_db_head(struct sbuf *sbp, struct veriexec_devhead *head)
{
	struct veriexec_dev_list *lp;

	for (lp = LIST_FIRST(head); lp != NULL; lp = LIST_NEXT(lp, entries)) {
		sbuf_printf(sbp, " FS id: %ju\n", (uintmax_t)lp->fsid);
		mac_veriexec_print_db_dev_list(sbp, lp);
	}

}

/**
 * @internal
 * @brief Generate human-readable output for the current fingerprint database
 *
 * @param sbp	sbuf to write output to
 */
void
mac_veriexec_metadata_print_db(struct sbuf *sbp)
{
	struct {
		struct veriexec_devhead *h;
		const char *name;
	} fpdbs[] = {
		{ &veriexec_file_dev_head, "regular files" },
		{ &veriexec_dev_head, "executable files" },
	};
	int i;

	mtx_lock(&ve_mutex);
	for (i = 0; i < sizeof(fpdbs)/sizeof(fpdbs[0]); i++) {
		sbuf_printf(sbp, "%s fingerprint db:\n", fpdbs[i].name);
		mac_veriexec_print_db_head(sbp, fpdbs[i].h);
	}
	mtx_unlock(&ve_mutex);
}
/**
 * @brief Determine if the meta-data store has an entry for the specified file.
 *
 * @param fsid		file system identifier to look for
 * @param fileid	file to look for
 * @param gen		generation of file
 *
 * @return 1 if there is an entry in the meta-data store, 0 otherwise.
 */
int
mac_veriexec_metadata_has_file(dev_t fsid, long fileid, unsigned long gen)
{

	return (mac_veriexec_metadata_get_file_info(fsid, fileid, gen, NULL,
	    NULL, VERIEXEC_FILES_FIRST) == 0);
}

/**
 * @brief Search the list of devices looking for the one given, in order to
 *     release the resources used by it.
 *
 * If found, free all file entries for it, and remove it from the list.
 *
 * @note Called with @a ve_mutex held
 *
 * @param fsid		file system identifier to look for
 * @param head		meta-data list to search
 *
 * @return 0 if the device entry was freed, otherwise an error code
 */
static int
free_veriexec_dev(dev_t fsid, struct veriexec_devhead *head)
{
	struct veriexec_dev_list *lp;
	struct mac_veriexec_file_info *ip, *nip;

	/* Look for the file system */
	for (lp = LIST_FIRST(head); lp != NULL;
	     lp = LIST_NEXT(lp, entries))
		if (lp->fsid == fsid) break;

	/* If lp is NULL, we did not find it */
	if (lp == NULL)
		return ENOENT;

	/* Unhook lp, before we free it and its content */
	LIST_REMOVE(lp, entries);

	/* Release the lock */
	mtx_unlock(&ve_mutex);

	/* Free the file entries in the list */
	for (ip = LIST_FIRST(&(lp->file_head)); ip != NULL; ip = nip) {
		nip = LIST_NEXT(ip, entries);
		LIST_REMOVE(ip, entries);
		if (ip->label)
			free(ip->label, M_VERIEXEC);
		free(ip, M_VERIEXEC);
	}

	/* Free the meta-data entry for the device */
	free(lp, M_VERIEXEC);

	/* Re-acquire the lock */
	mtx_lock(&ve_mutex);
	return 0;
}

/**
 * @brief Search the list of devices looking for the one given.
 *
 * If it is not in the list then add it.
 *
 * @note Called with @a ve_mutex held
 *
 * @param fsid		file system identifier to look for
 * @param head		meta-data list to search
 *
 * @return A pointer to the meta-data entry for the device, if found or added,
 *     otherwise @c NULL
 */
static struct veriexec_dev_list *
find_veriexec_dev(dev_t fsid, struct veriexec_devhead *head)
{
	struct veriexec_dev_list *lp;
	struct veriexec_dev_list *np = NULL;

search:
	/* Look for the file system */
	for (lp = LIST_FIRST(head); lp != NULL;
	     lp = LIST_NEXT(lp, entries))
		if (lp->fsid == fsid) break;

	if (lp == NULL) {
		if (np == NULL) {
			/*
			 * If pointer is null then entry not there,
			 * add a new one, first try to malloc while
			 * we hold mutex - should work most of the time.
			 */
			np = malloc(sizeof(struct veriexec_dev_list),
			    M_VERIEXEC, M_NOWAIT);
			if (np == NULL) {
				/*
				 * So much for that plan, dop the mutex
				 * and repeat...
				 */
				mtx_unlock(&ve_mutex);
				np = malloc(sizeof(struct veriexec_dev_list),
				    M_VERIEXEC, M_WAITOK);
				mtx_lock(&ve_mutex);
				/*
				 * Repeat the search, in case someone
				 * added this while we slept.
				 */
				goto search;
			}
		}
		if (np) {
			/* Add the entry to the list */
			lp = np;
			LIST_INIT(&(lp->file_head));
			lp->fsid = fsid;
			LIST_INSERT_HEAD(head, lp, entries);
		}
	} else if (np) {
		/*
		 * Someone else did it while we slept.
		 */
		mtx_unlock(&ve_mutex);
		free(np, M_VERIEXEC);
		mtx_lock(&ve_mutex);
	}

	return (lp);
}

/**
 * @internal
 * @brief Allocate and initialize label record with the provided data.
 *
 * @param labelp	Location to store the initialized label
 * @param src		Pointer to label string to copy
 * @param srclen	Length of label string to copy
 *
 * @return Length of resulting label
 *
 * @note Called with ve_mutex locked.
 */
static size_t
mac_veriexec_init_label(char **labelp, size_t labellen, char *src,
    size_t srclen)
{
	char *label;

	label = *labelp;
	if (labellen < srclen) {
		mtx_unlock(&ve_mutex);
		if (label != NULL)
			free(label, M_VERIEXEC);
		label = malloc(srclen, M_VERIEXEC, M_WAITOK);
		mtx_lock(&ve_mutex);
		labellen = srclen;
		*labelp = label;
	}
	memcpy(label, src, srclen);
	return labellen;
}

/**
 * @brief When a device is unmounted, we want to toss the signatures recorded
 *     against it.
 *
 * We are being called from unmount() with the root vnode just before it is
 * freed.
 *
 * @param fsid		file system identifier to look for
 * @param td		calling thread
 *
 * @return 0 on success, otherwise an error code.
 */
int
mac_veriexec_metadata_unmounted(dev_t fsid, struct thread *td)
{
    int error;

    /*
     * The device can have entries on both lists.
     */
    mtx_lock(&ve_mutex);
    error = free_veriexec_dev(fsid, &veriexec_dev_head);
    if (error && error != ENOENT) {
	    mtx_unlock(&ve_mutex);
	    return error;
    }
    error = free_veriexec_dev(fsid, &veriexec_file_dev_head);
    mtx_unlock(&ve_mutex);
    if (error && error != ENOENT) {
	    return error;
    }
    return 0;
}

/**
 * @brief Return the flags assigned to the file identified by file system
 * 	  identifier @p fsid and file identifier @p fileid.
 *
 * @param fsid		file system identifier
 * @param fileid	file identifier within the file system
 * @param gen		generation of file
 * @param flags		pointer to location to store the flags
 * @param check_files	if 1, check the files list first, otherwise check the
 * 			exectuables list first
 *
 * @return 0 on success, otherwise an error code.
 */
int
mac_veriexec_metadata_get_file_flags(dev_t fsid, long fileid, unsigned long gen,
    int *flags, int check_files)
{
	struct mac_veriexec_file_info *ip;
	int error;

	error = mac_veriexec_metadata_get_file_info(fsid, fileid, gen, NULL,
	    &ip, check_files);
	if (error != 0)
		return (error);

	*flags = ip->flags;
	return (0);
}

/**
 * @brief get the files for the specified process
 *
 * @param cred		credentials to use
 * @param p		process to get the flags for
 * @param flags		where to store the flags
 * @param check_files	if 1, check the files list first, otherwise check the
 * 			exectuables list first
 *
 * @return 0 if the process has an entry in the meta-data store, otherwise an
 *     error code
 */
int
mac_veriexec_metadata_get_executable_flags(struct ucred *cred, struct proc *p,
    int *flags, int check_files)
{
	struct vnode *proc_vn;
	struct vattr vap;
	int error;

	/* Get the text vnode for the process */
	proc_vn = p->p_textvp;
	if (proc_vn == NULL)
		return EINVAL;

	/* Get vnode attributes */
	error = VOP_GETATTR(proc_vn, &vap, cred);
	if (error)
		return error;

	error = mac_veriexec_metadata_get_file_flags(vap.va_fsid,
	    vap.va_fileid, vap.va_gen, flags,
	    (check_files == VERIEXEC_FILES_FIRST));

	return (error);
}

/**
 * @brief Ensure the fingerprint status for the vnode @p vp is assigned to its
 *     MAC label.
 *
 * @param vp		vnode to check
 * @param vap		vnode attributes to use
 * @param td		calling thread
 * @param check_files	if 1, check the files list first, otherwise check the
 * 			exectuables list first
 *
 * @return 0 on success, otherwise an error code.
 */
int
mac_veriexec_metadata_fetch_fingerprint_status(struct vnode *vp,
    struct vattr *vap, struct thread *td, int check_files)
{
	unsigned char digest[MAXFINGERPRINTLEN];
	struct mac_veriexec_file_info *ip;
	int error, found_dev;
	fingerprint_status_t status;

	error = 0;
	ip = NULL;

	status = mac_veriexec_get_fingerprint_status(vp);
	if (status == FINGERPRINT_INVALID || status == FINGERPRINT_NODEV) {
		found_dev = 0;
		error = mac_veriexec_metadata_get_file_info(vap->va_fsid,
		    vap->va_fileid, vap->va_gen, &found_dev, &ip, check_files);
		if (error != 0) {
			status = (found_dev) ? FINGERPRINT_NOENTRY :
			    FINGERPRINT_NODEV;
			VERIEXEC_DEBUG(3,
			    ("fingerprint status is %d for dev %ju, file "
			    "%ju.%lu\n", status, (uintmax_t)vap->va_fsid,
			    (uintmax_t)vap->va_fileid, vap->va_gen));
		} else {
			/*
			 * evaluate and compare fingerprint
			 */
			error = mac_veriexec_fingerprint_check_vnode(vp, ip,
			    td, vap->va_size, digest);
			switch (error) {
			case 0:
				/* Process flags */
				if ((ip->flags & VERIEXEC_INDIRECT))
					status = FINGERPRINT_INDIRECT;
				else if ((ip->flags & VERIEXEC_FILE))
					status = FINGERPRINT_FILE;
				else
					status = FINGERPRINT_VALID;
				VERIEXEC_DEBUG(2,
				    ("%sfingerprint matches for dev %ju, file "
				    "%ju.%lu\n",
				     (status == FINGERPRINT_INDIRECT) ?
				     "indirect " :
				     (status == FINGERPRINT_FILE) ?
				     "file " : "", (uintmax_t)vap->va_fsid,
				     (uintmax_t)vap->va_fileid, vap->va_gen));
				break;

			case EAUTH:
#ifdef MAC_VERIEXEC_DEBUG
				{
					char have[MAXFINGERPRINTLEN * 2 + 1];
					char want[MAXFINGERPRINTLEN * 2 + 1];
					int i, len;

					len = ip->ops->digest_len;
					for (i = 0; i < len; i++) {
						sprintf(&want[i * 2], "%02x",
						    ip->fingerprint[i]);
						sprintf(&have[i * 2], "%02x",
						    digest[i]);
					}
					log(LOG_ERR, MAC_VERIEXEC_FULLNAME
					    ": fingerprint for dev %ju, file "
					    "%ju.%lu %s != %s\n",
					    (uintmax_t)vap->va_fsid,
					    (uintmax_t)vap->va_fileid,
					    vap->va_gen,
					    have, want);
				}
#endif
				status = FINGERPRINT_NOMATCH;
				break;
			default:
				VERIEXEC_DEBUG(2,
				    ("fingerprint status error %d\n", error));
				break;
			}
		}
		mac_veriexec_set_fingerprint_status(vp, status);
	}
	return (error);
}

/**
 * Return label if we have one
 *
 * @param fsid         file system identifier to look for
 * @param fileid       file to look for
 * @param gen          generation of file
 * @param check_files  look at non-executable files?
 *
 * @return A pointer to the label or @c NULL
 */
const char *
mac_veriexec_metadata_get_file_label(dev_t fsid, long fileid,
    unsigned long gen, int check_files)
{
	struct mac_veriexec_file_info *ip;
	int error;

	error = mac_veriexec_metadata_get_file_info(fsid, fileid, gen, NULL,
	    &ip, check_files);
	if (error)
		return (NULL);

	return ((ip->flags & VERIEXEC_LABEL) != 0 ? ip->label : NULL);
}

/**
 * Add a file and its fingerprint to the list of files attached
 * to the device @p fsid.
 *
 * Only add the entry if it is not already on the list.
 *
 * @note Called with @a ve_mutex held
 *
 * @param file_dev	if 1, the entry should be added on the file list,
 * 			otherwise it should be added on the executable list
 * @param fsid		file system identifier of device
 * @param fileid	file to add
 * @param gen		generation of file
 * @param fingerprint	fingerprint to add to the store
 * @param flags		flags to set in the store
 * @param fp_type	digest type
 * @param override	if 1, override any values already stored
 *
 * @return 0 on success, otherwise an error code.
 */
int
mac_veriexec_metadata_add_file(int file_dev, dev_t fsid, long fileid,
    unsigned long gen, unsigned char fingerprint[MAXFINGERPRINTLEN],
    char *label, size_t labellen, int flags, const char *fp_type, int override)
{
	struct mac_veriexec_fpops *fpops;
	struct veriexec_dev_list *lp;
	struct veriexec_devhead *head;
	struct mac_veriexec_file_info *ip;
	struct mac_veriexec_file_info *np = NULL;

	/* Label and labellen must be set if VERIEXEC_LABEL is set */
	if ((flags & VERIEXEC_LABEL) != 0 && (label == NULL || labellen == 0))
		return (EINVAL);

	/* Look up the device entry */
	if (file_dev)
		head = &veriexec_file_dev_head;
	else
		head = &veriexec_dev_head;
	lp = find_veriexec_dev(fsid, head);

	/* Look up the fingerprint operations for the digest type */
	fpops = mac_veriexec_fingerprint_lookup_ops(fp_type);
	if (fpops == NULL)
		return (EOPNOTSUPP);

search:
	for (ip = LIST_FIRST(&(lp->file_head)); ip != NULL;
	     ip = LIST_NEXT(ip, entries)) {
		  /* check for a dupe file in the list, skip if an entry
		   * exists for this file except for when the flags contains
		   * VERIEXEC_INDIRECT, always set the flags when it is so
		   * we don't get a hole caused by conflicting flags on
		   * hardlinked files.  XXX maybe we should validate
		   * fingerprint is same and complain if it is not...
		   */
		if (ip->fileid == fileid && ip->gen == gen) {
			if (override) {
				/*
				 * for a signed load we allow overrides,
				 * otherwise fingerpints needed for pkg loads
				 * can fail (the files are on temp device).
				 */
				ip->flags = flags;
				ip->ops = fpops;
				memcpy(ip->fingerprint, fingerprint,
				    fpops->digest_len);
				if (flags & VERIEXEC_LABEL) {
					ip->labellen = mac_veriexec_init_label(
					    &ip->label, ip->labellen, label,
					    labellen);
				} else if (ip->labellen > 0) {
					free(ip->label, M_VERIEXEC);
					ip->labellen = 0;
					ip->label = NULL;
				}
			} else if ((flags & (VERIEXEC_INDIRECT|VERIEXEC_FILE)))
				ip->flags |= flags;

			if (np) {
				/* unlikely but... we don't need it now. */
				mtx_unlock(&ve_mutex);
				free(np, M_VERIEXEC);
				mtx_lock(&ve_mutex);
			}
			return (0);
		}
	}

	/*
	 * We may have been past here before...
	 */
	if (np == NULL) {
		/*
		 * We first try with mutex held and nowait.
		 */
		np = malloc(sizeof(struct mac_veriexec_file_info), M_VERIEXEC,
		    M_NOWAIT);
		if (np == NULL) {
			/*
			 * It was worth a try, now
			 * drop mutex while we malloc.
			 */
			mtx_unlock(&ve_mutex);
			np = malloc(sizeof(struct mac_veriexec_file_info),
			    M_VERIEXEC, M_WAITOK);
			mtx_lock(&ve_mutex);
			/*
			 * We now have to repeat our search!
			 */
			goto search;
		}
	}

	/* Set up the meta-data entry */
	ip = np;
	ip->flags = flags;
	ip->ops = fpops;
	ip->fileid = fileid;
	ip->gen = gen;
	memcpy(ip->fingerprint, fingerprint, fpops->digest_len);
	if (flags & VERIEXEC_LABEL)
		ip->labellen = mac_veriexec_init_label(&ip->label,
		    ip->labellen, label, labellen);
	else {
		ip->label = NULL;
		ip->labellen = 0;
	}

	VERIEXEC_DEBUG(3, ("add file %ju.%lu (files=%d)\n",
	    (uintmax_t)ip->fileid,
	    ip->gen, file_dev));

	/* Add the entry to the list */
	LIST_INSERT_HEAD(&(lp->file_head), ip, entries);
#ifdef DEBUG_VERIEXEC_FINGERPRINT
	{
		off_t offset;

		printf("Stored %s fingerprint:\n", fp_type);
		for (offset = 0; offset < fpops->digest_len; offset++)
			printf("%02x", fingerprint[offset]);
		printf("\n");
	}
#endif
	return (0);
}

/**
 * @brief Search the meta-data store for information on the specified file.
 *
 * @param fsid          file system identifier to look for
 * @param fileid        file to look for
 * @param gen           generation of file
 * @param found_dev	indicator that an entry for the file system was found
 * @param ipp           pointer to location to store the info pointer
 * @param check_files   if 1, check the files list first, otherwise check the
 *                      exectuables list first
 *
 * @return A pointer to the meta-data inforation if meta-data exists for
 *     the specified file identifier, otherwise @c NULL
 */
int
mac_veriexec_metadata_get_file_info(dev_t fsid, long fileid, unsigned long gen,
    int *found_dev, struct mac_veriexec_file_info **ipp, int check_files)
{
	struct veriexec_devhead *search[3];
	struct mac_veriexec_file_info *ip;
	int x;

	/* Determine the order of the lists to search */
	if (check_files) {
		search[0] = &veriexec_file_dev_head;
		search[1] = &veriexec_dev_head;
	} else {
		search[0] = &veriexec_dev_head;
		search[1] = &veriexec_file_dev_head;
	}
	search[2] = NULL;

	VERIEXEC_DEBUG(3, ("%s: searching for dev %#jx, file %lu.%lu\n",
	    __func__, (uintmax_t)fsid, fileid, gen));

	/* Search for the specified file */
	for (ip = NULL, x = 0; ip == NULL && search[x]; x++)
		ip = get_veriexec_file(search[x], fsid, fileid, gen, found_dev);

	if (ipp != NULL)
		*ipp = ip;
	if (ip == NULL)
		return (ENOENT);
	return (0);
}

/**
 * @brief Intialize the meta-data store
 */
void
mac_veriexec_metadata_init(void)
{

	mtx_init(&ve_mutex, "veriexec lock", NULL, MTX_DEF);
	LIST_INIT(&veriexec_dev_head);
	LIST_INIT(&veriexec_file_dev_head);
}