#include <sys/param.h>
#include <sys/sysctl.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/attr.h>
#include <sys/mount.h>
#include <sys/xattr.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "BootCache.h"
static inline const char* uuid_string(uuid_t uuid)
{
static uuid_string_t uuidString;
uuid_unparse(uuid, uuidString);
return (char*)uuidString;
}
void BC_free_playlist(struct BC_playlist *pc) {
if (pc) {
if (pc->p_mounts)
free(pc->p_mounts);
if (pc->p_entries)
free(pc->p_entries);
free(pc);
}
}
void BC_free_history(struct BC_history *hc) {
if (hc) {
if (hc->h_mounts)
free(hc->h_mounts);
if (hc->h_entries)
free(hc->h_entries);
free(hc);
}
}
int
BC_read_playlist(const char *pfname, struct BC_playlist **ppc)
{
struct BC_playlist *pc;
struct BC_playlist_header ph;
int error, fd;
fd = -1;
pc = NULL;
*ppc = NULL;
if (pfname == NULL) {
warnx("No playlist path provided");
error = EINVAL;
goto out;
}
if ((fd = open(pfname, O_RDONLY)) == -1) {
warnx("Unable to open %s: %d %s", pfname, errno, strerror(errno));
error = errno;
goto out;
}
if (read(fd, &ph, sizeof(ph)) != sizeof(ph)) {
warnx("could not read header from %s", pfname);
error = EINVAL;
goto out;
}
if (ph.ph_magic != PH_MAGIC) {
warnx("bad playlist magic");
error = EINVAL;
goto out;
}
if ((pc = calloc(1, sizeof(*pc))) == NULL) {
warnx("Unable to allocate playlist structure");
error = errno;
goto out;
}
pc->p_nmounts = ph.ph_nmounts;
pc->p_nentries = ph.ph_nentries;
if ((pc->p_mounts = malloc(sizeof(*pc->p_mounts) * pc->p_nmounts)) == NULL) {
warnx("could not allocate memory for playlist mounts");
error = errno;
goto out;
}
if (read(fd, pc->p_mounts, sizeof(*pc->p_mounts) * pc->p_nmounts) != (sizeof(*pc->p_mounts) * pc->p_nmounts)) {
warnx("could not read playlist mounts");
error = EINVAL;
goto out;
}
if ((pc->p_entries = malloc(sizeof(*pc->p_entries) * pc->p_nentries)) == NULL) {
warnx("could not allocate memory for playlist data");
error = errno;
goto out;
}
if (read(fd, pc->p_entries, (sizeof(*pc->p_entries) * pc->p_nentries)) != (sizeof(*pc->p_entries) * pc->p_nentries)) {
warnx("could not read playlist data");
error = EINVAL;
goto out;
}
if ((error = BC_verify_playlist(pc)) != 0) {
goto out;
}
*ppc = pc;
out:
if (fd != -1)
close(fd);
if (error) {
PC_FREE_ZERO(pc);
}
return(error);
}
int
BC_write_playlist(const char *pfname, const struct BC_playlist *pc)
{
struct BC_playlist_header ph;
char *tfname;
int error, fd;
tfname = NULL;
fd = -1;
if ((error = BC_verify_playlist(pc)) != 0) {
goto out;
}
size_t tpathlen = strlen(pfname) + 8;
if (tpathlen > MAXPATHLEN) {
warnx("playlist filename too long");
error = ENAMETOOLONG;
goto out;
}
if ((tfname = malloc(tpathlen)) == NULL) {
warnx("could not allocate %lu bytes for playlist filename", strlen(pfname));
error = errno;
goto out;
}
snprintf(tfname, tpathlen, "%s.XXXXXX", pfname);
if ((fd = mkstemp(tfname)) < 0) {
warnx("could not create temporary playlist file");
error = errno;
goto out;
}
ph.ph_magic = 0;
ph.ph_nmounts = 0;
ph.ph_nentries = 0;
if (write(fd, &ph, sizeof(ph)) != sizeof(ph)) {
warnx("could not write initial header to temporary playlist file");
error = errno;
goto out;
}
if (write(fd, pc->p_mounts, pc->p_nmounts * sizeof(*pc->p_mounts)) != (pc->p_nmounts * sizeof(*pc->p_mounts))) {
warnx("could not write mounts to temporary playlist file: %d %s", errno, strerror(errno));
error = errno;
goto out;
}
if (write(fd, pc->p_entries, pc->p_nentries * sizeof(*pc->p_entries)) != (pc->p_nentries * sizeof(*pc->p_entries))) {
warnx("could not write entries to temporary playlist file");
error = errno;
goto out;
}
ph.ph_magic = PH_MAGIC;
ph.ph_nmounts = pc->p_nmounts;
ph.ph_nentries = pc->p_nentries;
if (lseek(fd, 0, SEEK_SET) != 0) {
warnx("could not seek on temporary playlist file");
error = errno;
goto out;
}
if (write(fd, &ph, sizeof(ph)) != sizeof(ph)) {
warnx("could not write header to temporary playlist file");
error = errno;
goto out;
}
close(fd);
fd = -1;
if (rename((const char *)tfname, pfname) != 0) {
warnx("could not save playlist file %s->%s", tfname, pfname);
error = errno;
goto out;
}
free(tfname);
tfname = NULL;
out:
if (tfname != NULL) {
unlink(tfname);
free(tfname);
}
if (fd != -1)
close(fd);
return(error);
}
int
BC_merge_playlists(struct BC_playlist* pa, const struct BC_playlist* pb)
{
int mount_idx_b, mount_idx_a, entry_idx;
if ((pa->p_mounts = reallocf(pa->p_mounts, sizeof(*pa->p_mounts) * (pa->p_nmounts + pb->p_nmounts))) == NULL)
return(ENOMEM);
for (mount_idx_b = 0; mount_idx_b < pb->p_nmounts; mount_idx_b++) {
for (mount_idx_a = 0; mount_idx_a < pa->p_nmounts; mount_idx_a++) {
if (0 == uuid_compare(pa->p_mounts[mount_idx_a].pm_uuid, pb->p_mounts[mount_idx_b].pm_uuid)) {
break;
}
}
if (mount_idx_a == pa->p_nmounts) {
pa->p_nmounts++;
pa->p_mounts[mount_idx_a].pm_nentries = 0;
uuid_copy(pa->p_mounts[mount_idx_a].pm_uuid, pb->p_mounts[mount_idx_b].pm_uuid);
}
pa->p_mounts[mount_idx_a].pm_nentries += pb->p_mounts[mount_idx_b].pm_nentries;
}
if ((pa->p_entries = reallocf(pa->p_entries, sizeof(*pa->p_entries) * (pa->p_nentries + pb->p_nentries))) == NULL)
return(ENOMEM);
memcpy(pa->p_entries + pa->p_nentries, pb->p_entries, pb->p_nentries * sizeof(*pb->p_entries));
for (entry_idx = pa->p_nentries; entry_idx < (pa->p_nentries + pb->p_nentries); entry_idx++) {
mount_idx_b = pa->p_entries[entry_idx].pe_mount_idx;
for (mount_idx_a = 0; mount_idx_a < pa->p_nmounts; mount_idx_a++) {
if (0 == uuid_compare(pa->p_mounts[mount_idx_a].pm_uuid, pb->p_mounts[mount_idx_b].pm_uuid)) {
pa->p_entries[entry_idx].pe_mount_idx = mount_idx_a;
break;
}
}
if (mount_idx_a == pa->p_nmounts) {
warnx("Mounts not merged properly for entry %d (mount %d)", entry_idx, mount_idx_b);
return(1);
}
}
pa->p_nentries += pb->p_nentries;
#ifdef BOOTCACHE_ENTRIES_SORTED_BY_DISK_OFFSET
BC_sort_playlist(pa);
#endif
BC_coalesce_playlist(pa);
return BC_verify_playlist(pa);
}
static int
compare_playlist_entry(const void *vfirst, const void *vsecond)
{
const struct BC_playlist_entry *first, *second;
first = (const struct BC_playlist_entry *)vfirst;
second = (const struct BC_playlist_entry *)vsecond;
int uuid_comparison = first->pe_mount_idx - second->pe_mount_idx;
if (uuid_comparison != 0)
return((uuid_comparison < 0) ? -1 : 1);
if (first->pe_offset == second->pe_offset)
return(0);
return((first->pe_offset < second->pe_offset) ? -1 : 1);
}
void
BC_sort_playlist(struct BC_playlist *pc)
{
if ((pc == NULL) || (pc->p_mounts == NULL) || (pc->p_entries == NULL) || (pc->p_nmounts == 0) || (pc->p_nentries == 0))
return;
if (pc->p_nentries > 0)
qsort((void *)pc->p_entries, pc->p_nentries, sizeof(*pc->p_entries), compare_playlist_entry);
}
int
BC_coalesce_playlist(struct BC_playlist *pc)
{
struct BC_playlist_entry *pe, *dpe;
int i, oentries, mount_idx;
pe = pc->p_entries;
oentries = 0;
dpe = pe;
for (mount_idx = 0; mount_idx < pc->p_nmounts; mount_idx++)
pc->p_mounts[mount_idx].pm_nentries = 0;
while (pe < pc->p_entries + pc->p_nentries) {
if (pe->pe_length > 0) {
for (i = 1; (pe + i) < (pc->p_entries + pc->p_nentries); i++) {
if (pe->pe_mount_idx != (pe + i)->pe_mount_idx)
continue;
if ((pe + i)->pe_offset > (pe->pe_offset + pe->pe_length))
break;
#define MAX_MERGE_SIZE (8*1024*1024)
if (((pe->pe_flags & BC_PE_LOWPRIORITY) && ((pe + i)->pe_flags & BC_PE_LOWPRIORITY)) ||
(!(pe->pe_flags & BC_PE_LOWPRIORITY) && !((pe + i)->pe_flags & BC_PE_LOWPRIORITY) && pe->pe_batch == (pe + i)->pe_batch) ||
(pe->pe_batch < (pe + i)->pe_batch && (int64_t)((pe + i)->pe_offset + (pe + i)->pe_length) - (pe->pe_offset + pe->pe_length) <= MAX_MERGE_SIZE) ||
(pe->pe_batch > (pe + i)->pe_batch && (int64_t)(pe + i)->pe_offset - pe->pe_offset <= MAX_MERGE_SIZE) ||
((pe->pe_offset + pe->pe_length) > ((pe + i)->pe_offset + (pe + i)->pe_length) && (pe->pe_batch > (pe + i)->pe_batch || pe->pe_flags & BC_PE_LOWPRIORITY))
) {
pe->pe_length = MAX((pe->pe_offset + pe->pe_length), ((pe + i)->pe_offset + (pe + i)->pe_length)) - pe->pe_offset;
pe->pe_batch = MIN(pe->pe_batch, (pe + i)->pe_batch);
if ( !((pe + i)->pe_flags & BC_PE_LOWPRIORITY)) {
pe->pe_flags &= (~BC_PE_LOWPRIORITY);
}
if ((pe + i)->pe_flags & BC_PE_SHARED) {
pe->pe_flags |= BC_PE_SHARED;
}
(pe + i)->pe_length = 0;
} else {
if (pe->pe_batch < (pe + i)->pe_batch ||
(pe + i)->pe_flags & BC_PE_LOWPRIORITY) {
u_int64_t endpoint = MAX((pe->pe_offset + pe->pe_length), ((pe + i)->pe_offset + (pe + i)->pe_length));
(pe + i)->pe_offset = pe->pe_offset + pe->pe_length;
(pe + i)->pe_length = endpoint - (pe + i)->pe_offset;
} else {
pe->pe_length = (pe + i)->pe_offset - pe->pe_offset;
}
}
}
*(dpe++) = *pe;
oentries++;
pc->p_mounts[pe->pe_mount_idx].pm_nentries++;
}
pe++;
}
pc->p_nentries = oentries;
pe = pc->p_entries;
pc->p_entries = realloc(pc->p_entries, sizeof(*pc->p_entries) * pc->p_nentries);
if (pc->p_entries == NULL)
pc->p_entries = pe;
return(0);
}
int
BC_verify_playlist(const struct BC_playlist *pc)
{
int i, error = 0;
int* mount_counts = NULL;
if (pc == NULL) {
warnx("playlist is null");
error = EINVAL;
goto out;
}
if ((pc->p_mounts == NULL) && (pc->p_nmounts > 0)) {
warnx("mounts is null");
error = EINVAL;
goto out;
}
if ((pc->p_entries == NULL) && (pc->p_nentries > 0)) {
warnx("entries is null");
error = EINVAL;
goto out;
}
if (pc->p_nentries > 0 && pc->p_nmounts == 0) {
warnx("entries, but no mounts");
error = EINVAL;
goto out;
}
if (pc->p_nmounts == 0) {
error = 0;
goto out;
}
mount_counts = calloc(pc->p_nmounts, sizeof(int));
for (i = 0; i < pc->p_nentries; i++) {
if (pc->p_entries[i].pe_mount_idx >= pc->p_nmounts) {
warnx("entry %d referenced unknown mount %d", i, pc->p_entries[i].pe_mount_idx);
error = EINVAL;
goto out;
}
mount_counts[pc->p_entries[i].pe_mount_idx]++;
if (pc->p_entries[i].pe_offset + pc->p_entries[i].pe_length <= pc->p_entries[i].pe_offset) {
warnx("entry %d has a bad range %lld,%lld", i, pc->p_entries[i].pe_offset, pc->p_entries[i].pe_length);
error = EINVAL;
goto out;
}
}
for (i = 0; i < pc->p_nmounts; i++) {
if (pc->p_mounts[i].pm_nentries == 0) {
warnx("mount %d has 0 entries", i);
error = EINVAL;
goto out;
}
if (mount_counts[i] != pc->p_mounts[i].pm_nentries) {
warnx("%d entries exist for mount %d, vs %d claimed", mount_counts[i], i, pc->p_mounts[i].pm_nentries);
error = EINVAL;
goto out;
}
}
out:
if (mount_counts)
free(mount_counts);
return (error);
}
int
BC_fetch_statistics(struct BC_statistics **pss)
{
struct BC_command bc;
static struct BC_statistics ss;
int error;
bc.bc_magic = BC_MAGIC;
bc.bc_opcode = BC_OP_STATS;
bc.bc_data1 = (uintptr_t) &ss;
bc.bc_data1_size = (unsigned int) sizeof(ss);
error = sysctlbyname(BC_SYSCTL, NULL, NULL, &bc, sizeof(bc));
if (error != 0) {
return(errno);
}
*pss = &ss;
return(0);
}
int
BC_convert_history(const struct BC_history *hc, struct BC_playlist **ppc)
{
struct BC_playlist *pc;
struct BC_playlist_mount *pm;
struct BC_playlist_entry *pe;
struct BC_history_mount *hm;
struct BC_history_entry *he;
int error, mount_idx, curbatch;
*ppc = NULL;
if (hc == NULL) {
error = EINVAL;
goto out;
}
if ((pc = calloc(1, sizeof(*pc))) == NULL) {
error = errno;
goto out;
}
if ((pc->p_mounts = malloc(sizeof(*pc->p_mounts) * hc->h_nmounts)) == NULL) {
error = errno;
goto out;
}
pc->p_nmounts = 0;
for (hm = hc->h_mounts; hm < (hc->h_mounts + hc->h_nmounts); hm++) {
if (! uuid_is_null(hm->hm_uuid)) {
pm = pc->p_mounts + pc->p_nmounts;
uuid_copy(pm->pm_uuid, hm->hm_uuid);
pm->pm_nentries = 0;
pc->p_nmounts++;
}
}
if ((pc->p_entries = malloc(sizeof(*pc->p_entries) * hc->h_nentries)) == NULL) {
error = errno;
goto out;
}
curbatch = 0;
pc->p_nentries = 0;
for (he = hc->h_entries; he < (hc->h_entries + hc->h_nentries); he++) {
if (he->he_flags & BC_HE_TAG) {
if (curbatch < BC_MAXBATCHES) {
curbatch++;
}
continue;
}
if (he->he_flags & BC_HE_WRITE)
continue;
if (he->he_length == 0)
continue;
if (he->he_mount_idx >= hc->h_nmounts) {
warnx("History entry %ld referenced mount index %d, but there are only %d mounts", (long)(he - hc->h_entries), he->he_mount_idx, hc->h_nmounts);
error = EINVAL;
goto out;
}
hm = hc->h_mounts + he->he_mount_idx;
pe = pc->p_entries + pc->p_nentries;
for (mount_idx = 0; mount_idx < pc->p_nmounts; mount_idx++) {
if (0 == uuid_compare(pc->p_mounts[mount_idx].pm_uuid, hm->hm_uuid)) {
break;
}
}
if (mount_idx >= pc->p_nmounts) {
continue;
}
pm = pc->p_mounts + mount_idx;
pe->pe_mount_idx = mount_idx;
pe->pe_offset = he->he_offset;
pe->pe_length = he->he_length;
pe->pe_batch = curbatch;
pe->pe_flags = 0;
if (pm->pm_nentries >= hm->hm_nentries) {
warnx("Bad playlist: more entries existed than mount %s claimed (%d)", uuid_string(pm->pm_uuid), hm->hm_nentries);
error = EINVAL;
goto out;
}
pm->pm_nentries++;
pc->p_nentries++;
}
for (mount_idx = 0; mount_idx < pc->p_nmounts; mount_idx++) {
pm = pc->p_mounts + mount_idx;
if (pm->pm_nentries == 0) {
pc->p_nmounts--;
if (mount_idx < pc->p_nmounts) {
memcpy(pm, pm + 1, sizeof(*pm) * (pc->p_nmounts - mount_idx));
for (pe = pc->p_entries; pe < (pc->p_entries + pc->p_nentries); pe++)
if (pe->pe_mount_idx > mount_idx)
pe->pe_mount_idx--;
mount_idx--;
}
}
}
if ((error = BC_verify_playlist(pc)) != 0) {
goto out;
}
*ppc = pc;
out:
if (error) {
PC_FREE_ZERO(pc);
}
return(error);
}
int
BC_start(struct BC_playlist *pc)
{
struct BC_command bc;
bc.bc_magic = BC_MAGIC;
bc.bc_opcode = BC_OP_START;
if (pc) {
bc.bc_data1 = (uintptr_t) pc->p_mounts;
bc.bc_data1_size = (unsigned int) (pc->p_nmounts * sizeof(*pc->p_mounts));
bc.bc_data2 = (uintptr_t) pc->p_entries;
bc.bc_data2_size = (unsigned int) (pc->p_nentries * sizeof(*pc->p_entries));
} else {
bc.bc_data1 = 0;
bc.bc_data1_size = 0;
bc.bc_data2 = 0;
bc.bc_data2_size = 0;
}
return(sysctlbyname(BC_SYSCTL, NULL, NULL, &bc, sizeof(bc)) ? errno : 0);
}
int
BC_stop(struct BC_history **phc)
{
struct BC_command bc;
struct BC_history *hc;
int error;
size_t nsize;
bc.bc_magic = BC_MAGIC;
bc.bc_opcode = BC_OP_STOP;
nsize = sizeof(bc);
error = sysctlbyname(BC_SYSCTL, &bc, &nsize, &bc, nsize);
if (error != 0) {
if (errno != ENXIO)
warnx("could not stop cache: %d %s", errno, strerror(errno));
return(errno);
}
if (nsize != sizeof(bc)) {
warnx("control structure wrong size, version mismatch?");
return(EINVAL);
}
bc.bc_opcode = BC_OP_HISTORY;
if ((hc = calloc(1, sizeof(*hc))) == NULL) {
warnx("could not allocate history struct memory");
return(ENOMEM);
}
if (bc.bc_data1_size == 0 || bc.bc_data2_size == 0) {
bc.bc_data1 = 0;
bc.bc_data2 = 0;
bc.bc_data1_size = 0;
bc.bc_data2_size = 0;
} else {
if ((hc->h_mounts = malloc(bc.bc_data1_size)) == NULL) {
warnx("could not allocate history mounts memory");
HC_FREE_ZERO(hc);
return(ENOMEM);
}
if ((hc->h_entries = malloc(bc.bc_data2_size)) == NULL) {
warnx("could not allocate history mounts memory");
HC_FREE_ZERO(hc);
return(ENOMEM);
}
hc->h_nmounts = bc.bc_data1_size / sizeof(struct BC_history_mount);
hc->h_nentries = bc.bc_data2_size / sizeof(struct BC_history_entry);
bc.bc_data1 = (uintptr_t) hc->h_mounts;
bc.bc_data2 = (uintptr_t) hc->h_entries;
}
error = sysctlbyname(BC_SYSCTL, NULL, NULL, &bc, sizeof(bc));
if (error != 0) {
warnx("could not fetch %u and %u bytes of history: %d %s", bc.bc_data1_size, bc.bc_data2_size, errno, strerror(errno));
HC_FREE_ZERO(hc);
return(errno);
}
if (hc->h_mounts || hc->h_entries) {
hc->h_nmounts = bc.bc_data1_size / sizeof(struct BC_history_mount);
hc->h_nentries = bc.bc_data2_size / sizeof(struct BC_history_entry);
}
*phc = hc;
return(0);
}
int
BC_playlist_for_file(int fd, struct BC_playlist** ppc) {
return BC_playlist_for_filename(fd, NULL, 0, ppc);
}
int
BC_playlist_for_filename(int fd, const char *fname, off_t maxsize, struct BC_playlist** ppc) {
struct BC_playlist* pc = NULL;
int error = 0;
int compressed_fd = -1;
off_t filesize = 0;
*ppc = NULL;
struct stat fd_stats;
if (0 != fstat(fd, &fd_stats)) {
warnx("Unable to stat %d: %d %s", fd, errno, strerror(errno));
error = errno;
goto out;
}
filesize = fd_stats.st_size;
if (fd_stats.st_flags & UF_COMPRESSED) {
if (fname) {
size_t sz = getxattr(fname, XATTR_RESOURCEFORK_NAME, NULL, 0, 0,
XATTR_SHOWCOMPRESSION);
if (sz > 0){
filesize = sz;
char rsrcpath[PATH_MAX];
sprintf(rsrcpath, "%s/..namedfork/rsrc", fname);
compressed_fd = open(rsrcpath, O_RDONLY);
if (compressed_fd < 0){
warnx("Unable to open compressed resource fork %s", rsrcpath);
error = errno;
goto out;
}
fd = compressed_fd;
} else {
error = EINVAL;
goto out;
}
} else {
error = EINVAL;
goto out;
}
}
struct {
uint32_t size;
uuid_t uuid;
} attrBuf = {0, {0}};
struct statfs statfs_buf;
if (0 != fstatfs(fd, &statfs_buf)) {
warnx("Unable to stafs %d: %d %s", fd, errno, strerror(errno));
uuid_clear(attrBuf.uuid);
filesize = (((filesize + (512 - 1)) / 512) * 512);
} else {
filesize = (((filesize + (statfs_buf.f_bsize - 1)) / statfs_buf.f_bsize) * statfs_buf.f_bsize);
struct attrlist list = {
.bitmapcount = ATTR_BIT_MAP_COUNT,
.volattr = ATTR_VOL_INFO | ATTR_VOL_UUID,
};
if (0 != getattrlist(statfs_buf.f_mntonname, &list, &attrBuf, sizeof(attrBuf), 0)) {
warnx("Unable to determine uuid for volume %s", statfs_buf.f_mntonname);
uuid_clear(attrBuf.uuid);
}
}
if (maxsize != 0 && maxsize < filesize) {
filesize = maxsize;
}
pc = calloc(1, sizeof(*pc));
if(!pc) {
warnx("Unable to allocate playlist structure");
error = errno;
goto out;
}
pc->p_nmounts = 1;
pc->p_mounts = malloc(sizeof(*pc->p_mounts));
if(!pc->p_mounts) {
warnx("could not allocate memory for playlist mounts");
PC_FREE_ZERO(pc);
error = errno;
goto out;
}
pc->p_mounts[0].pm_nentries = 0;
uuid_copy(pc->p_mounts[0].pm_uuid, attrBuf.uuid);
pc->p_nentries = 0;
pc->p_entries = NULL;
off_t ra_off;
for (ra_off = 0; (ra_off < filesize); ) {
off_t remaining = (filesize - ra_off);
struct log2phys block_range = {
.l2p_flags = 0,
.l2p_devoffset = ra_off, .l2p_contigbytes = remaining, };
#if __LP64__
int ret = syscall(SYS_fcntl, fd, F_LOG2PHYS_EXT, &block_range);
#else
int ret = fcntl(fd, F_LOG2PHYS_EXT, &block_range);
#endif
if (ret != 0) {
break;
}
if (remaining < block_range.l2p_contigbytes ) {
warnx("Invalid size returned for %d from disk (%lld bytes requested, %lld bytes returned)", fd, remaining, block_range.l2p_contigbytes);
break;
}
if (block_range.l2p_contigbytes == 0) {
break;
}
if (block_range.l2p_devoffset < 0) {
warnx("Invalid block range return for %d from disk (%lld:%lld returned %lld:%lld)\n", fd, ra_off, remaining, block_range.l2p_devoffset, block_range.l2p_contigbytes);
break;
}
ra_off += block_range.l2p_contigbytes;
pc->p_entries = reallocf(pc->p_entries, (pc->p_nentries + 1) * sizeof(*pc->p_entries));
if(! pc->p_entries) {
warnx("could not allocate memory for playlist data");
error = errno;
goto out;
}
pc->p_entries[pc->p_nentries].pe_offset = block_range.l2p_devoffset;
pc->p_entries[pc->p_nentries].pe_length = block_range.l2p_contigbytes;
pc->p_entries[pc->p_nentries].pe_batch = 0;
pc->p_entries[pc->p_nentries].pe_flags = 0;
pc->p_entries[pc->p_nentries].pe_mount_idx = 0;
pc->p_mounts[0].pm_nentries++;
pc->p_nentries++;
}
if (pc->p_nentries == 0) {
error = ENOENT;
goto out;
}
if ((error = BC_verify_playlist(pc)) != 0) {
goto out;
}
*ppc = pc;
error = 0;
out:
if (compressed_fd >= 0) {
close(compressed_fd);
}
if (error != 0) {
PC_FREE_ZERO(pc);
}
return (error);
}
int
BC_notify_mount(void)
{
struct BC_command bc;
int error;
bc.bc_magic = BC_MAGIC;
bc.bc_opcode = BC_OP_MOUNT;
error = sysctlbyname(BC_SYSCTL, NULL, NULL, &bc, sizeof(bc));
if (error != 0) {
if (errno != ENXIO)
warnx("could not notify cache of new mount: %d %s", errno, strerror(errno));
return(errno);
}
return(0);
}
int
BC_test(void)
{
struct BC_command bc;
bc.bc_magic = BC_MAGIC;
bc.bc_opcode = BC_OP_TEST;
return(sysctlbyname(BC_SYSCTL, NULL, NULL, &bc, sizeof(bc)) ? errno : 0);
}
int
BC_jettison(void)
{
struct BC_command bc;
int error;
bc.bc_magic = BC_MAGIC;
bc.bc_opcode = BC_OP_JETTISON;
error = sysctlbyname(BC_SYSCTL, NULL, NULL, &bc, sizeof(bc));
if (error != 0) {
if (errno != ENXIO)
warnx("could not jettison cache: %d %s", errno, strerror(errno));
return(errno);
}
return(0);
}
int
BC_print_statistics(char *fname, struct BC_statistics *ss)
{
FILE *fp;
uint disk_msecs, disk_bytes, d, b, m, bytes_remaining, other;
if (ss == NULL)
return(0);
errno = 0;
if (fname != NULL) {
fp = fopen(fname, "w");
} else {
fp = stdout;
}
if (fp == NULL)
return(errno);
fprintf(fp, "initiated reads %u\n", ss->ss_initiated_reads);
fprintf(fp, "blocks read %u\n", ss->ss_read_blocks);
fprintf(fp, "bytes read %u\n", ss->ss_read_bytes);
if (ss->ss_read_bytes > 0) {
bytes_remaining = ss->ss_read_bytes - ss->ss_hit_bytes - ss->ss_read_errors_bytes - ss->ss_write_discards - ss->ss_read_discards - ss->ss_stolen_bytes - ss->ss_spurious_bytes - ss->ss_hit_bytes_afterhistory - ss->ss_lost_bytes_afterhistory - ss->ss_bypass_nocache_discards;
fprintf(fp, " bytes used %-10u %3.0f%%\n", ss->ss_hit_bytes, ((float)ss->ss_hit_bytes / ss->ss_read_bytes) * 100);
fprintf(fp, " bytes remaining %-10u %3.0f%%\n", bytes_remaining, ((float)bytes_remaining / ss->ss_read_bytes) * 100);
fprintf(fp, " nonshared bytes read %-10u %3.0f%%\n", (ss->ss_read_bytes - ss->ss_shared_bytes), ((float)(ss->ss_read_bytes - ss->ss_shared_bytes) / ss->ss_read_bytes) * 100);
if ((ss->ss_read_bytes - ss->ss_shared_bytes) > 0) {
fprintf(fp, " nonshared bytes used %-10u %3.0f%%\n", (ss->ss_hit_bytes - ss->ss_hit_shared_bytes), ((float)(ss->ss_hit_bytes - ss->ss_hit_shared_bytes) / (ss->ss_read_bytes - ss->ss_shared_bytes)) * 100);
}
fprintf(fp, " shared cache bytes read %-10u %3.0f%%\n", ss->ss_shared_bytes, ((float)ss->ss_shared_bytes / ss->ss_read_bytes) * 100);
if (ss->ss_shared_bytes > 0) {
fprintf(fp, " shared cache bytes used %-10u %3.0f%%\n", ss->ss_hit_shared_bytes, ((float)ss->ss_hit_shared_bytes / ss->ss_shared_bytes) * 100);
}
fprintf(fp, " low priority bytes %-10u %3.0f%%\n", ss->ss_read_bytes_lowpri, ((float)ss->ss_read_bytes_lowpri / ss->ss_read_bytes) * 100);
fprintf(fp, " bytes failed to read %-10u %3.0f%%\n", ss->ss_read_errors_bytes, ((float)ss->ss_read_errors_bytes / ss->ss_read_bytes) * 100);
if (ss->ss_bypass_nocache_discards > 0) {
fprintf(fp, " bytes discarded by noncac %-10u %3.0f%%\n", ss->ss_bypass_nocache_discards, ((float)ss->ss_bypass_nocache_discards / ss->ss_read_bytes) * 100);
}
fprintf(fp, " bytes discarded by write %-10u %3.0f%%\n", ss->ss_write_discards, ((float)ss->ss_write_discards / ss->ss_read_bytes) * 100);
fprintf(fp, " bytes discarded by read %-10u %3.0f%%\n", ss->ss_read_discards, ((float)ss->ss_read_discards / ss->ss_read_bytes) * 100);
fprintf(fp, " bytes discarded by error %-10u %3.0f%%\n", ss->ss_error_discards, ((float)ss->ss_error_discards / ss->ss_read_bytes) * 100);
fprintf(fp, " bytes stolen from cache %-10u %3.0f%%\n", ss->ss_stolen_bytes, ((float)ss->ss_stolen_bytes / ss->ss_read_bytes) * 100);
fprintf(fp, " bytes used after boot %-10u %3.0f%%\n", ss->ss_hit_bytes_afterhistory, ((float)ss->ss_hit_bytes_afterhistory / ss->ss_read_bytes) * 100);
fprintf(fp, " bytes lost after boot %-10u %3.0f%%\n", ss->ss_lost_bytes_afterhistory, ((float)ss->ss_lost_bytes_afterhistory / ss->ss_read_bytes) * 100);
if (bytes_remaining == 0 || 0 != ss->ss_spurious_bytes) {
fprintf(fp, " bytes wasted %-10u %3.0f%%\n", ss->ss_spurious_bytes, ((float)ss->ss_spurious_bytes / ss->ss_read_bytes) * 100);
}
}
fprintf(fp, "bytes requested %u\n", ss->ss_requested_bytes);
if (ss->ss_requested_bytes > 0) {
fprintf(fp, " bytes hit %-10u %3.0f%%\n", ss->ss_hit_bytes, ((float)ss->ss_hit_bytes / ss->ss_requested_bytes) * 100);
for (m = 0; m < STAT_MOUNTMAX; m++) {
if (ss->ss_requested_bytes_m[m] > 0 && ss->ss_requested_bytes_m[m] != ss->ss_requested_bytes) {
fprintf(fp, " mount %d bytes hit %-10u %3.0f%% of %u\n", m, ss->ss_hit_bytes_m[m], ((float)ss->ss_hit_bytes_m[m] / ss->ss_requested_bytes_m[m]) * 100, ss->ss_requested_bytes_m[m]);
}
}
fprintf(fp, " noncached bytes requested %-10u %3.0f%%\n", ss->ss_bypass_nocache_bytes + ss->ss_hit_nocache_bytes, ((float)(ss->ss_bypass_nocache_bytes + ss->ss_hit_nocache_bytes) / ss->ss_requested_bytes) * 100);
if (ss->ss_bypass_nocache_bytes + ss->ss_hit_nocache_bytes > 0) {
fprintf(fp, " noncached bytes hit %-10u %3.0f%%\n", ss->ss_hit_nocache_bytes, ((float)ss->ss_hit_nocache_bytes / (ss->ss_bypass_nocache_bytes + ss->ss_hit_nocache_bytes)) * 100);
fprintf(fp, " noncached bytes missed %-10u %3.0f%%\n", ss->ss_bypass_nocache_bytes, ((float)ss->ss_bypass_nocache_bytes / (ss->ss_bypass_nocache_bytes + ss->ss_hit_nocache_bytes)) * 100);
}
}
if (ss->ss_initiated_reads > 0 && ss->ss_read_errors > 0) {
fprintf(fp, " read errors %-10u %3.0f%%\n", ss->ss_read_errors, ((float)ss->ss_read_errors / ss->ss_initiated_reads) * 100);
}
if (ss->ss_cache_time > 0 || ss->ss_read_bytes == 0) {
fprintf(fp, "cache active time %d.%03ds\n", ss->ss_cache_time / 1000, ss->ss_cache_time % 1000);
} else {
fprintf(fp, "cache active time (still active)\n");
}
fprintf(fp, "\n");
fprintf(fp, "total strategy calls %u\n", ss->ss_strategy_calls);
if (ss->ss_strategy_calls > 0) {
fprintf(fp, " filled calls %-10u %3.0f%%\n", ss->ss_strategy_calls - ss->ss_strategy_bypassed, ((float)(ss->ss_strategy_calls - ss->ss_strategy_bypassed) / ss->ss_strategy_calls) * 100);
fprintf(fp, " bypassed calls %-10u %3.0f%%\n", ss->ss_strategy_bypassed, ((float)ss->ss_strategy_bypassed / ss->ss_strategy_calls) * 100);
if (ss->ss_strategy_bypassed > 0) {
other = ss->ss_strategy_bypassed - ss->ss_strategy_nonread - ss->ss_strategy_noncached_mount - ss->ss_strategy_nonblocksize - (ss->ss_extent_lookups - ss->ss_extent_hits) - ss->ss_strategy_bypass_duringio_unfilled - ss->ss_strategy_unfilled_lowpri;
fprintf(fp, " nonread calls %-10u %3.0f%%\n", ss->ss_strategy_nonread, ((float)ss->ss_strategy_nonread / ss->ss_strategy_bypassed) * 100);
fprintf(fp, " noncached mount calls %-10u %3.0f%%\n", ss->ss_strategy_noncached_mount, ((float)ss->ss_strategy_noncached_mount / ss->ss_strategy_bypassed) * 100);
if (ss->ss_strategy_nonblocksize > 0) {
fprintf(fp, " nonblocksize calls %-10u %3.0f%%\n", ss->ss_strategy_nonblocksize, ((float)ss->ss_strategy_nonblocksize / ss->ss_strategy_bypassed) * 100);
}
fprintf(fp, " cache misses %-10u %3.0f%%\n", ss->ss_extent_lookups - ss->ss_extent_hits, ((float)(ss->ss_extent_lookups - ss->ss_extent_hits) / ss->ss_strategy_bypassed) * 100);
fprintf(fp, " cache hit failures %-10u %3.0f%%\n", other, ((float)other / ss->ss_strategy_bypassed) * 100);
if (ss->ss_strategy_bypass_duringio_unfilled > 0) {
fprintf(fp, " unfilled extent %-10u %3.0f%%\n", ss->ss_strategy_bypass_duringio_unfilled, ((float)ss->ss_strategy_bypass_duringio_unfilled / ss->ss_strategy_bypassed) * 100);
}
if (ss->ss_strategy_unfilled_lowpri > 0) {
fprintf(fp, " unfilled lowpri extent %-10u %3.0f%%\n", ss->ss_strategy_unfilled_lowpri, ((float)ss->ss_strategy_unfilled_lowpri / ss->ss_strategy_bypassed) * 100);
}
}
fprintf(fp, " noncached calls %-10u %3.0f%%\n", ss->ss_strategy_bypass_nocache + ss->ss_strategy_hit_nocache, ((float)(ss->ss_strategy_bypass_nocache + ss->ss_strategy_hit_nocache) / ss->ss_strategy_calls) * 100);
if (ss->ss_strategy_bypass_nocache + ss->ss_strategy_hit_nocache > 0) {
fprintf(fp, " noncached calls filled %-10u %3.0f%%\n", ss->ss_strategy_hit_nocache, ((float)ss->ss_strategy_hit_nocache / (ss->ss_strategy_bypass_nocache + ss->ss_strategy_hit_nocache)) * 100);
fprintf(fp, " noncached calls bypassed %-10u %3.0f%%\n", ss->ss_strategy_bypass_nocache, ((float)ss->ss_strategy_bypass_nocache / (ss->ss_strategy_bypass_nocache + ss->ss_strategy_hit_nocache)) * 100);
}
fprintf(fp, " throttled calls %-10u %3.0f%%\n", ss->ss_strategy_throttled, ((float)ss->ss_strategy_throttled / ss->ss_strategy_calls) * 100);
fprintf(fp, " calls during readahead %-10u %3.0f%%\n", ss->ss_strategy_duringio, ((float)ss->ss_strategy_duringio / ss->ss_strategy_calls) * 100);
if (ss->ss_strategy_duringio > 0) {
fprintf(fp, " filled during readahead %-10u %3.0f%%\n", ss->ss_strategy_duringio - ss->ss_strategy_bypass_duringio, ((float)(ss->ss_strategy_duringio - ss->ss_strategy_bypass_duringio) / ss->ss_strategy_duringio) * 100);
fprintf(fp, " bypassed during readahea %-10u %3.0f%%\n", ss->ss_strategy_bypass_duringio, ((float)ss->ss_strategy_bypass_duringio / ss->ss_strategy_duringio) * 100);
if (ss->ss_strategy_bypass_duringio > 0) {
other = ss->ss_strategy_bypass_duringio - ss->ss_strategy_bypass_duringio_rootdisk_nonread - ss->ss_strategy_bypass_duringio_rootdisk_read - ss->ss_strategy_bypass_duringio_rootdisk_failure - ss->ss_strategy_bypass_duringio_unfilled - ss->ss_strategy_bypass_duringio_nocache;
if (ss->ss_strategy_bypass_duringio_unfilled > 0) {
fprintf(fp, " unfilled extent %-10u %3.0f%%\n", ss->ss_strategy_bypass_duringio_unfilled, ((float)ss->ss_strategy_bypass_duringio_unfilled / ss->ss_strategy_bypass_duringio) * 100);
}
if (ss->ss_strategy_bypass_duringio_nocache > 0) {
fprintf(fp, " noncached IO %-10u %3.0f%%\n", ss->ss_strategy_bypass_duringio_nocache, ((float)ss->ss_strategy_bypass_duringio_nocache / ss->ss_strategy_bypass_duringio) * 100);
}
fprintf(fp, " root disk nonread %-10u %3.0f%%\n", ss->ss_strategy_bypass_duringio_rootdisk_nonread, ((float)ss->ss_strategy_bypass_duringio_rootdisk_nonread / ss->ss_strategy_bypass_duringio) * 100);
fprintf(fp, " root disk cache miss %-10u %3.0f%%\n", ss->ss_strategy_bypass_duringio_rootdisk_read, ((float)ss->ss_strategy_bypass_duringio_rootdisk_read / ss->ss_strategy_bypass_duringio) * 100);
fprintf(fp, " root disk hit failure %-10u %3.0f%%\n", ss->ss_strategy_bypass_duringio_rootdisk_failure, ((float)ss->ss_strategy_bypass_duringio_rootdisk_failure / ss->ss_strategy_bypass_duringio) * 100);
fprintf(fp, " non-root %-10u %3.0f%%\n", other, ((float)other / ss->ss_strategy_bypass_duringio) * 100);
fprintf(fp, " forced throttled %-10u %3.0f%%\n", ss->ss_strategy_forced_throttled, ((float)ss->ss_strategy_forced_throttled / ss->ss_strategy_bypass_duringio) * 100);
}
fprintf(fp, " extent hits during reada %-10u %3.0f%%\n", ss->ss_hit_duringio, ((float)ss->ss_hit_duringio / ss->ss_strategy_duringio) * 100);
if (ss->ss_hit_duringio > 0) {
fprintf(fp, " blocked during readahea %-10u %3.1f%%\n", ss->ss_strategy_blocked, ((float)ss->ss_strategy_blocked / ss->ss_hit_duringio) * 100);
fprintf(fp, " block calls timed out %-10u %3.1f%%\n", ss->ss_strategy_timedout, ((float)ss->ss_strategy_timedout / ss->ss_hit_duringio) * 100);
fprintf(fp, " longest blocked call %ums\n", ss->ss_strategy_time_longest_blocked);
fprintf(fp, " total blocked duration %ums\n", ss->ss_strategy_time_blocked);
}
}
}
if (ss->ss_strategy_unknown > 0) {
fprintf(fp, "unknown strategy calls %u\n", ss->ss_strategy_unknown);
fprintf(fp, "unknown strategy bytes %u\n", ss->ss_strategy_unknown_bytes);
}
if (ss->ss_read_bytes - ss->ss_read_bytes_lowpri > 0) {
fprintf(fp, "\n");
fprintf(fp, "total readahead threads %u\n", ss->ss_readahead_threads);
for (d = 0; d < STAT_DISKMAX; d++) {
disk_bytes = 0;
disk_msecs = 0;
for(b = 0; b < STAT_BATCHMAX; b++) {
disk_bytes += ss->ss_batch_bytes[d][b];
disk_msecs += ss->ss_batch_time[d][b];
}
if (0 == disk_bytes) continue;
if (disk_msecs > 0) {
fprintf(fp, "Disk %d reader rate: %ukB/s, %utps\n",
d,
(u_int)(((unsigned long long)disk_bytes * 1000) / (disk_msecs * 1024)),
(ss->ss_disk_initiated_reads[d] * 1000) / disk_msecs);
}
fprintf(fp, "Disk %d time %d.%03ds\n",
d,
disk_msecs / 1000,
disk_msecs % 1000);
fprintf(fp, "Disk %d bytes read: %u\n", d, disk_bytes);
for(b = 0; b < STAT_BATCHMAX; b++) {
if (ss->ss_batch_time[d][b] > 0) {
fprintf(fp, " batch %d time %d.%03ds\n",
b,
ss->ss_batch_time[d][b] / 1000,
ss->ss_batch_time[d][b] % 1000);
}
}
for(b = 0; b < STAT_BATCHMAX; b++) {
if (ss->ss_batch_bytes[d][b] > 0) {
fprintf(fp, " batch %d bytes read: %-10u", b, ss->ss_batch_bytes[d][b]);
if (ss->ss_batch_late_bytes[d][b] > 0) {
fprintf(fp, " (%u late)", ss->ss_batch_late_bytes[d][b]);
}
fprintf(fp, "\n");
}
}
}
}
if (ss->ss_read_bytes_lowpri > 0) {
for (d = 0; d < STAT_DISKMAX; d++) {
if (0 == ss->ss_batch_bytes_lowpri[d]) continue;
if (ss->ss_batch_time_lowpri[d] > 0) {
fprintf(fp, "Disk %d lowpri reader rate: %ukB/s, %u.%utps\n",
d,
(u_int)(((unsigned long long)ss->ss_batch_bytes_lowpri[d] * 1000) / (ss->ss_batch_time_lowpri[d] * 1024)),
(ss->ss_disk_initiated_reads_lowpri[d] * 1000) / ss->ss_batch_time_lowpri[d], (10 * (ss->ss_disk_initiated_reads_lowpri[d] * 1000) / ss->ss_batch_time_lowpri[d]) % 10);
}
fprintf(fp, "Disk %d lowpri bytes read: %u\n", d, ss->ss_batch_bytes_lowpri[d]);
fprintf(fp, "Disk %d lowpri time: %d.%03ds\n",
d,
ss->ss_batch_time_lowpri[d] / 1000,
ss->ss_batch_time_lowpri[d] % 1000);
}
}
fprintf(fp, "\n");
fprintf(fp, "mounts in cache %u\n", ss->ss_total_mounts);
fprintf(fp, "extents in cache %u\n", ss->ss_total_extents);
if (ss->ss_extents_clipped > 0 && ss->ss_total_extents > 0) {
fprintf(fp, " extents clipped %-10u %3.0f%%\n", ss->ss_extents_clipped, ((float)ss->ss_extents_clipped / ss->ss_total_extents) * 100);
}
fprintf(fp, "extent lookups %u\n", ss->ss_extent_lookups);
if (ss->ss_extent_lookups > 0) {
fprintf(fp, "extent hits %-10u %3.0f%%\n", ss->ss_extent_hits, ((float)ss->ss_extent_hits / ss->ss_extent_lookups) * 100);
if (ss->ss_extent_hits > 0) {
fprintf(fp, " multiple extent hits %-10u %3.0f%%\n", ss->ss_hit_multiple, ((float)ss->ss_hit_multiple / ss->ss_extent_hits) * 100);
fprintf(fp, " hits aborted %-10u %3.0f%%\n", ss->ss_hit_aborted, ((float)ss->ss_hit_aborted / ss->ss_extent_hits) * 100);
fprintf(fp, " hits missing blocks %-10u %3.0f%%\n", ss->ss_hit_blkmissing, ((float)ss->ss_hit_blkmissing / ss->ss_extent_hits) * 100);
fprintf(fp, " hits stolen %-10u %3.0f%%\n", ss->ss_hit_stolen, ((float)ss->ss_hit_stolen / ss->ss_extent_hits) * 100);
fprintf(fp, " hits failed %-10u %3.0f%%\n", ss->ss_hit_failure, ((float)ss->ss_hit_failure / ss->ss_extent_hits) * 100);
}
}
fprintf(fp, "\n");
fprintf(fp, "preload time %d.%03ds\n", ss->ss_preload_time / 1000, ss->ss_preload_time % 1000);
if (ss->ss_history_time > 0 || ss->ss_history_entries == 0) {
fprintf(fp, "history active time %d.%03ds\n", ss->ss_history_time / 1000, ss->ss_history_time % 1000);
if (ss->ss_history_time > 0) {
fprintf(fp, "read/write IO rate %u/%utps\n", (ss->ss_history_reads * 1000) / ss->ss_history_time, (ss->ss_history_writes * 1000) / ss->ss_history_time);
}
} else {
fprintf(fp, "history active time (still active)\n");
}
fprintf(fp, "history entries %u\n", ss->ss_history_entries);
fprintf(fp, "history bytes %u\n", ss->ss_history_bytes);
fprintf(fp, "history mounts %u\n", ss->ss_history_mounts);
fprintf(fp, "unidentifiable mounts %u\n", ss->ss_history_mount_no_uuid);
if (ss->ss_history_unknown > 0) {
fprintf(fp, "history unknown calls %u\n", ss->ss_history_unknown);
fprintf(fp, "history unknown bytes %u\n", ss->ss_history_unknown_bytes);
}
fprintf(fp, "number history recordings %u\n", ss->ss_history_num_recordings);
fprintf(fp, "\n");
fprintf(fp, "current flags 0x%x\n", ss->ss_cache_flags);
if (fp == stdout) {
fflush(fp);
} else {
fclose(fp);
}
return(0);
}
int
BC_print_history(char *fname, struct BC_history *hc)
{
FILE *fp;
int i;
errno = 0;
if (fname != NULL) {
fp = fopen(fname, "w");
} else {
fp = stdout;
}
if (fp == NULL)
return(errno);
for (i = 0; i < hc->h_nentries; i++) {
fprintf(fp, "%s %-12llu %-8llu %5u%s%s\n",
uuid_string(hc->h_mounts[hc->h_entries[i].he_mount_idx].hm_uuid),
hc->h_entries[i].he_offset, hc->h_entries[i].he_length,
hc->h_entries[i].he_pid,
hc->h_entries[i].he_flags & BC_HE_HIT ? " hit" :
hc->h_entries[i].he_flags & BC_HE_WRITE ? " write" :
hc->h_entries[i].he_flags & BC_HE_TAG ? " tag" : " miss",
hc->h_entries[i].he_flags & BC_HE_SHARED ? " shared" : "");
}
if (fp == stdout) {
fflush(fp);
} else {
fclose(fp);
}
return(0);
}
int
BC_tag_history(void)
{
struct BC_command bc;
int error;
bc.bc_magic = BC_MAGIC;
bc.bc_opcode = BC_OP_TAG;
error = sysctlbyname(BC_SYSCTL, NULL, NULL, &bc, sizeof(bc));
if (error != 0) {
warnx("could not insert prefetch tag: %d %s", errno, strerror(errno));
return(ENOENT);
}
return(0);
}
int
BC_unload(void)
{
#if 1
return(0);
#else
pid_t child;
char *argv[4];
int result;
child = fork();
switch (child) {
case -1:
return(errno);
case 0:
argv[0] = BC_KEXTUNLOAD;
argv[1] = "-b";
argv[2] = BC_BUNDLE_ID;
argv[3] = NULL;
result = execve(BC_KEXTUNLOAD, argv, NULL);
exit((result != 0) ? 1 : 0);
default:
waitpid(child, &result, 0);
break;
}
return((result != 0) ? EBUSY : 0);
#endif
}