/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include "bblk_einfo.h" #include "boot_utils.h" bblk_hash_t bblk_no_hash = {BBLK_NO_HASH, 0, "(no hash)", NULL}; bblk_hash_t bblk_md5_hash = {BBLK_HASH_MD5, 0x10, "MD5", md5_calc}; bblk_hash_t *bblk_hash_list[BBLK_HASH_TOT] = { &bblk_no_hash, &bblk_md5_hash }; /* * einfo_compare_dotted_version() * Compares two strings with an arbitrary long number of dot-separated numbers. * Returns: 0 - if the version numbers are equal * 1 - if str1 version number is more recent than str2 * 2 - if str2 version number is more recent than str1 * -1 - if an error occurred * * Comparison is done field by field, by retrieving an unsigned integer value, * (missing fields are assumed as 0, but explict zeroes take precedence) so: * 4.1.2.11 > 4.1.2.2 > 4.1.2.0 > 4.1.2 * * where ">" means "more recent than". */ static int einfo_compare_dotted_version(const char *str1, const char *str2) { int retval = 0; char *verstr1, *verstr2, *freeptr1, *freeptr2; char *parsep1, *parsep2; unsigned int val_str1, val_str2; freeptr1 = verstr1 = strdup(str1); freeptr2 = verstr2 = strdup(str2); if (verstr1 == NULL || verstr2 == NULL) { retval = -1; goto out; } while (verstr1 != NULL && verstr2 != NULL) { parsep1 = strsep(&verstr1, "."); parsep2 = strsep(&verstr2, "."); val_str1 = atoi(parsep1); val_str2 = atoi(parsep2); if (val_str1 > val_str2) { retval = 1; goto out; } if (val_str2 > val_str1) { retval = 2; goto out; } } /* Common portion of the version string is equal. */ if (verstr1 == NULL && verstr2 != NULL) retval = 2; if (verstr2 == NULL && verstr1 != NULL) retval = 1; out: free(freeptr1); free(freeptr2); return (retval); } /* * einfo_compare_timestamps() * Currently, timestamp is in %Y%m%dT%H%M%SZ format in UTC, which means that * we can simply do a lexicographic comparison to know which one is the most * recent. * * Returns: 0 - if timestamps coincide * 1 - if the timestamp in str1 is more recent * 2 - if the timestamp in str2 is more recent */ static int einfo_compare_timestamps(const char *str1, const char *str2) { int retval; retval = strcmp(str1, str2); if (retval > 0) retval = 1; if (retval < 0) retval = 2; return (retval); } /* * einfo_compare_version() * Given two extended versions, compare the two and returns which one is more * "recent". Comparison is based on dotted version number fields and a * timestamp. * * Returns: -1 - on error * 0 - if the two versions coincide * 1 - if the version in str1 is more recent * 2 - if the version in str2 is more recent * * The version string generally uses following form: * self_release,build_release:timestamp * The release numbers are compared as dotted versions. * * While comparing, if the self releases are identical but the build * release is missing, this version string is considered older. * * If the release strings are identical, and one of the timestamps is missing, * we return an error. Otherwise, return the result from comparing the * timestamps. */ static int einfo_compare_version(const char *str1, const char *str2) { int retval = 0; char *verstr1, *verstr2, *freeptr1, *freeptr2; char *parsep1, *parsep2; char *timep1, *timep2; freeptr1 = verstr1 = strdup(str1); freeptr2 = verstr2 = strdup(str2); if (verstr1 == NULL || verstr2 == NULL) { retval = -1; goto out; } /* Extract the time part from the version string. */ timep1 = verstr1; timep2 = verstr2; parsep1 = strsep(&timep1, ":"); parsep2 = strsep(&timep2, ":"); while (parsep1 != NULL && parsep2 != NULL) { parsep1 = strsep(&verstr1, ",-"); parsep2 = strsep(&verstr2, ",-"); /* If both are NULL, compare timestamps */ if (parsep1 == NULL && parsep2 == NULL) break; if (parsep1 == NULL) { retval = 2; goto out; } if (parsep2 == NULL) { retval = 1; goto out; } retval = einfo_compare_dotted_version(parsep1, parsep2); if (retval == 0) continue; else goto out; } /* The dotted versions are identical, check timestamps. */ if (timep1 == NULL || timep2 == NULL) { retval = -1; goto out; } retval = einfo_compare_timestamps(timep1, timep2); out: free(freeptr1); free(freeptr2); return (retval); } /* * print_einfo() * * Print the extended information contained into the pointed structure. * 'bufsize' specifies the real size of the structure, since str_off and * hash_off need to point somewhere past the header. */ void print_einfo(uint8_t flags, bblk_einfo_t *einfo, unsigned long bufsize) { int i = 0; char *version; boolean_t has_hash = B_FALSE; unsigned char *hash = NULL; if (einfo->str_off + einfo->str_size > bufsize) { (void) fprintf(stdout, gettext("String offset %d is beyond the " "buffer size\n"), einfo->str_off); return; } version = (char *)einfo + einfo->str_off; if (einfo->hash_type != BBLK_NO_HASH && einfo->hash_type < BBLK_HASH_TOT) { if (einfo->hash_off + einfo->hash_size > bufsize) { (void) fprintf(stdout, gettext("Warning: hashing " "present but hash offset %d is beyond the buffer " "size\n"), einfo->hash_off); has_hash = B_FALSE; } else { hash = (unsigned char *)einfo + einfo->hash_off; has_hash = B_TRUE; } } if (flags & EINFO_PRINT_HEADER) { (void) fprintf(stdout, "Boot Block Extended Info Header:\n"); (void) fprintf(stdout, "\tmagic: "); for (i = 0; i < EINFO_MAGIC_SIZE; i++) (void) fprintf(stdout, "%c", einfo->magic[i]); (void) fprintf(stdout, "\n"); (void) fprintf(stdout, "\tversion: %d\n", einfo->version); (void) fprintf(stdout, "\tflags: %x\n", einfo->flags); (void) fprintf(stdout, "\textended version string offset: %d\n", einfo->str_off); (void) fprintf(stdout, "\textended version string size: %d\n", einfo->str_size); (void) fprintf(stdout, "\thashing type: %d (%s)\n", einfo->hash_type, has_hash ? bblk_hash_list[einfo->hash_type]->name : "nil"); (void) fprintf(stdout, "\thash offset: %d\n", einfo->hash_off); (void) fprintf(stdout, "\thash size: %d\n", einfo->hash_size); } if (flags & EINFO_EASY_PARSE) { (void) fprintf(stdout, "%s\n", version); } else { (void) fprintf(stdout, "Extended version string: %s\n", version); if (has_hash) { (void) fprintf(stdout, "%s hash: ", bblk_hash_list[einfo->hash_type]->name); } else { (void) fprintf(stdout, "No hashing available\n"); } } if (has_hash) { for (i = 0; i < einfo->hash_size; i++) { (void) fprintf(stdout, "%02x", hash[i]); } (void) fprintf(stdout, "\n"); } } static int compute_hash(bblk_hs_t *hs, unsigned char *dest, bblk_hash_t *hash) { if (hs == NULL || dest == NULL || hash == NULL) return (-1); hash->compute_hash(dest, hs->src_buf, hs->src_size); return (0); } int prepare_and_write_einfo(unsigned char *dest, char *infostr, bblk_hs_t *hs, uint32_t maxsize, uint32_t *used_space) { uint16_t hash_size; uint32_t hash_off; unsigned char *data; bblk_einfo_t *einfo = (bblk_einfo_t *)dest; bblk_hash_t *hashinfo = bblk_hash_list[BBLK_DEFAULT_HASH]; /* * 'dest' might be both containing the buffer we want to hash and * containing our einfo structure: delay any update of it after the * hashing has been calculated. */ hash_size = hashinfo->size; hash_off = sizeof (bblk_einfo_t); if (hash_off + hash_size > maxsize) { (void) fprintf(stderr, gettext("Unable to add extended info, " "not enough space\n")); return (-1); } data = dest + hash_off; if (compute_hash(hs, data, hashinfo) < 0) { (void) fprintf(stderr, gettext("%s hash operation failed\n"), hashinfo->name); einfo->hash_type = bblk_no_hash.type; einfo->hash_size = bblk_no_hash.size; } else { einfo->hash_type = hashinfo->type; einfo->hash_size = hashinfo->size; } (void) memcpy(einfo->magic, EINFO_MAGIC, EINFO_MAGIC_SIZE); einfo->version = BBLK_EINFO_VERSION; einfo->flags = 0; einfo->hash_off = hash_off; einfo->hash_size = hash_size; einfo->str_off = einfo->hash_off + einfo->hash_size + 1; if (infostr == NULL) { (void) fprintf(stderr, gettext("Unable to add extended info, " "string is empty\n")); return (-1); } einfo->str_size = strlen(infostr); if (einfo->str_off + einfo->str_size > maxsize) { (void) fprintf(stderr, gettext("Unable to add extended info, " "not enough space\n")); return (-1); } data = dest + einfo->str_off; (void) memcpy(data, infostr, einfo->str_size); *used_space = einfo->str_off + einfo->str_size; return (0); } /* * einfo_should_update() * Given information on the boot block currently on disk (disk_einfo) and * information on the supplied boot block (hs for hashing, verstr as the * associated version string) decide if an update of the on-disk boot block * is necessary or not. */ boolean_t einfo_should_update(bblk_einfo_t *disk_einfo, bblk_hs_t *hs, char *verstr) { bblk_hash_t *hashing; unsigned char *disk_hash; unsigned char *local_hash; char *disk_version; int retval; if (disk_einfo == NULL) return (B_TRUE); if (memcmp(disk_einfo->magic, EINFO_MAGIC, EINFO_MAGIC_SIZE) != 0) return (B_TRUE); if (disk_einfo->version < BBLK_EINFO_VERSION) return (B_TRUE); disk_version = einfo_get_string(disk_einfo); retval = einfo_compare_version(verstr, disk_version); /* * If something goes wrong or if the on-disk version is more recent * do not update the bootblock. */ if (retval == -1 || retval == 2) return (B_FALSE); /* * If we got here it means that the two version strings are either * equal or the new bootblk binary is more recent. In order to save * some needless writes let's use the hash to determine if an update * is really necessary. */ if (disk_einfo->hash_type == bblk_no_hash.type) return (B_TRUE); if (disk_einfo->hash_type >= BBLK_HASH_TOT) return (B_TRUE); hashing = bblk_hash_list[disk_einfo->hash_type]; local_hash = malloc(hashing->size); if (local_hash == NULL) return (B_TRUE); /* * Failure in computing the hash may mean something wrong * with the boot block file. Better be conservative here. */ if (compute_hash(hs, local_hash, hashing) < 0) { free(local_hash); return (B_FALSE); } disk_hash = (unsigned char *)einfo_get_hash(disk_einfo); if (memcmp(local_hash, disk_hash, disk_einfo->hash_size) == 0) { free(local_hash); return (B_FALSE); } free(local_hash); return (B_TRUE); } char * einfo_get_string(bblk_einfo_t *einfo) { if (einfo == NULL) return (NULL); return ((char *)einfo + einfo->str_off); } char * einfo_get_hash(bblk_einfo_t *einfo) { if (einfo == NULL) return (NULL); return ((char *)einfo + einfo->hash_off); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. */ #ifndef _BBLKEINFO_H #define _BBLKEINFO_H #ifdef __cplusplus extern "C" { #endif #include #include #include #include #define BBLK_EINFO_VERSION (1) #define EINFO_MAGIC "EXTINFO" #define EINFO_MAGIC_SIZE (7) #pragma pack(1) typedef struct _extended_info { char magic[EINFO_MAGIC_SIZE]; uint8_t version; uint8_t flags; uint32_t str_off; uint16_t str_size; uint8_t hash_type; uint32_t hash_off; uint16_t hash_size; char rsvd[32]; } bblk_einfo_t; #pragma pack() enum bblk_hash_types_t { BBLK_NO_HASH = 0, BBLK_HASH_MD5, BBLK_HASH_TOT }; #define EINFO_PRINT_HEADER 0x01 #define EINFO_EASY_PARSE 0x02 typedef struct _hashing_function { unsigned int type; unsigned int size; char name[16]; void (*compute_hash)(void *, const void *, unsigned int); } bblk_hash_t; typedef struct _hashing_source { unsigned char *src_buf; unsigned int src_size; } bblk_hs_t; #define BBLK_DEFAULT_HASH BBLK_HASH_MD5 extern bblk_hash_t bblk_no_hash; extern bblk_hash_t bblk_md5_hash; extern bblk_hash_t *bblk_hash_list[BBLK_HASH_TOT]; void print_einfo(uint8_t, bblk_einfo_t *, unsigned long); int prepare_and_write_einfo(unsigned char *, char *, bblk_hs_t *, uint32_t, uint32_t *); boolean_t einfo_should_update(bblk_einfo_t *, bblk_hs_t *, char *); char *einfo_get_string(bblk_einfo_t *); char *einfo_get_hash(bblk_einfo_t *); #ifdef __cplusplus } #endif #endif /* _BBLKEINFO_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. */ #include #include #include #include #include #include "bblk_einfo.h" #include "boot_utils.h" boolean_t boot_debug = B_FALSE; boolean_t nowrite = B_FALSE; void boot_gdebug(const char *funcname, char *format, ...) { va_list ap; if (boot_debug == B_FALSE) return; (void) fprintf(stdout, "%s(): ", funcname); va_start(ap, format); /* LINTED: E_SEC_PRINTF_VAR_FMT */ (void) vfprintf(stdout, format, ap); va_end(ap); } /* * Common functions to write out and read in block-sized data to a file * descriptor. */ int write_out(int fd, void *buffer, size_t size, off_t off) { int ret; char *buf = buffer; if (size % SECTOR_SIZE != 0) BOOT_DEBUG("Expected block-sized data, got: %d\n", size); /* Dry run. */ if (nowrite) return (BC_SUCCESS); for (;;) { again: ret = pwrite(fd, buf, size, off); if (ret == -1) { if (errno == EAGAIN) goto again; else return (BC_ERROR); } if (ret < size) { size -= ret; off += ret; buf += ret; } else { break; } } return (BC_SUCCESS); } int read_in(int fd, void *buffer, size_t size, off_t off) { int ret; char *buf = buffer; if (size % SECTOR_SIZE != 0) BOOT_DEBUG("Expected block-sized data, got: %d\n", size); for (;;) { again: ret = pread(fd, buf, size, off); if (ret == -1) { if (errno == EAGAIN) goto again; else return (BC_ERROR); } if (ret < size) { size -= ret; off += ret; buf += ret; } else { break; } } return (BC_SUCCESS); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. */ #ifndef _BOOT_UTILS_H #define _BOOT_UTILS_H #ifdef __cplusplus extern "C" { #endif #include #include #include "bblk_einfo.h" /* Common return values for various operations. */ #define BC_SUCCESS (0) #define BC_ERROR (1) #define BC_NOUPDT (4) #define BC_NOEXTRA (5) #define BC_NOEINFO (6) #define SECTOR_SIZE (512) extern boolean_t boot_debug; extern boolean_t nowrite; #define BOOT_DEBUG(...) boot_gdebug(__func__, __VA_ARGS__) void boot_gdebug(const char *, char *, ...); int write_out(int, void *, size_t, off_t); int read_in(int, void *, size_t, off_t); #ifdef __cplusplus } #endif #endif /* _BOOT_UTILS_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2012 Nexenta Systems, Inc. All rights reserved. */ #include #include #include #include #include #include #include #include "bblk_einfo.h" #include "boot_utils.h" #include "mboot_extra.h" /* * Common functions to deal with the fake-multiboot encapsulation of the * bootblock and the location of the extra information area. */ /* mboot checksum routine. */ uint32_t compute_checksum(char *data, uint32_t size) { uint32_t *ck_ptr; uint32_t cksum = 0; int i; ck_ptr = (uint32_t *)data; for (i = 0; i < size; i += sizeof (uint32_t)) cksum += *ck_ptr++; return (-cksum); } /* Given a buffer, look for a multiboot header within it. */ int find_multiboot(char *buffer, uint32_t buf_size, uint32_t *mboot_off) { multiboot_header_t *mboot; uint32_t *iter; uint32_t cksum; uint32_t boundary; int i = 0; iter = (uint32_t *)buffer; *mboot_off = 0; /* multiboot header has to be within the first 32K. */ boundary = MBOOT_SCAN_SIZE; if (boundary > buf_size) boundary = buf_size; boundary = boundary - sizeof (multiboot_header_t); for (i = 0; i < boundary; i += 4, iter++) { mboot = (multiboot_header_t *)iter; if (mboot->magic != MB_HEADER_MAGIC) continue; /* Found magic signature -- check checksum. */ cksum = -(mboot->flags + mboot->magic); if (mboot->checksum != cksum) { BOOT_DEBUG("multiboot magic found at %p, but checksum " "mismatches (is %x, should be %x)\n", mboot, mboot->checksum, cksum); continue; } else { if (!(mboot->flags & BB_MBOOT_AOUT_FLAG)) { BOOT_DEBUG("multiboot structure found, but no " "AOUT kludge specified, skipping.\n"); continue; } else { /* proper multiboot structure found. */ *mboot_off = i; return (BC_SUCCESS); } } } return (BC_ERROR); } /* * Given a pointer to the extra information area (a sequence of bb_header_ext_t * + payload chunks), find the extended information structure. */ bblk_einfo_t * find_einfo(char *extra, uint32_t size) { bb_header_ext_t *ext_header; bblk_einfo_t *einfo; uint32_t cksum; assert(extra != NULL); ext_header = (bb_header_ext_t *)extra; if (ext_header->size > size) { BOOT_DEBUG("Unable to find extended versioning information, " "data size too big\n"); return (NULL); } cksum = compute_checksum(extra + sizeof (bb_header_ext_t), ext_header->size); BOOT_DEBUG("Extended information header checksum is %x\n", cksum); if (cksum != ext_header->checksum) { BOOT_DEBUG("Unable to find extended versioning information, " "data looks corrupted\n"); return (NULL); } /* * Currently we only have one extra header so it must be encapsulating * the extended information structure. */ einfo = (bblk_einfo_t *)(extra + sizeof (bb_header_ext_t)); if (memcmp(einfo->magic, EINFO_MAGIC, EINFO_MAGIC_SIZE) != 0) { BOOT_DEBUG("Unable to read stage2 extended versioning " "information, wrong magic identifier\n"); BOOT_DEBUG("Found %s, expected %s\n", einfo->magic, EINFO_MAGIC); return (NULL); } return (einfo); } /* * Given a pointer to the extra area, add the extended information structure * encapsulated by a bb_header_ext_t structure. */ void add_einfo(char *extra, char *updt_str, bblk_hs_t *hs, uint32_t avail_space) { bb_header_ext_t *ext_hdr; uint32_t used_space; unsigned char *dest; int ret; assert(extra != NULL); if (updt_str == NULL) { BOOT_DEBUG("WARNING: no update string passed to " "add_stage2_einfo()\n"); return; } /* Reserve space for the extra header. */ ext_hdr = (bb_header_ext_t *)extra; dest = (unsigned char *)extra + sizeof (*ext_hdr); /* Place the extended information structure. */ ret = prepare_and_write_einfo(dest, updt_str, hs, avail_space, &used_space); if (ret != 0) { (void) fprintf(stderr, gettext("Unable to write the extended " "versioning information\n")); return; } /* Fill the extended information associated header. */ ext_hdr->size = P2ROUNDUP(used_space, 8); ext_hdr->checksum = compute_checksum((char *)dest, ext_hdr->size); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2012 Nexenta Systems, Inc. All rights reserved. */ #ifndef _MBOOT_EXTRA_H #define _MBOOT_EXTRA_H #ifdef __cplusplus extern "C" { #endif #include #include #include "bblk_einfo.h" /* multiboot header needs to be located in the first 64KB. */ #define MBOOT_SCAN_SIZE (64 * 1024) /* multiboot header AOUT_KLUDGE flag. */ #define BB_MBOOT_AOUT_FLAG (0x00010000) /* Extra header preceeding the payloads at the end of the bootblock. */ typedef struct _bb_extra_header { uint32_t size; uint32_t checksum; } bb_header_ext_t; uint32_t compute_checksum(char *, uint32_t); bblk_einfo_t *find_einfo(char *, uint32_t); int find_multiboot(char *, uint32_t, uint32_t *); void add_einfo(char *, char *, bblk_hs_t *, uint32_t); int compare_bootblocks(char *, char *, char **); #ifdef __cplusplus } #endif #endif /* _MBOOT_EXTRA_H */