1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 /* 22 * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. 23 */ 24 25 #include <stdio.h> 26 #include <stdint.h> 27 #include <stdlib.h> 28 #include <string.h> 29 #include <libintl.h> 30 #include <unistd.h> 31 #include <fcntl.h> 32 #include <sys/types.h> 33 #include <sys/stat.h> 34 35 #include "bblk_einfo.h" 36 #include "boot_utils.h" 37 38 bblk_hash_t bblk_no_hash = {BBLK_NO_HASH, 0, "(no hash)", NULL}; 39 bblk_hash_t bblk_md5_hash = {BBLK_HASH_MD5, 0x10, "MD5", md5_calc}; 40 41 bblk_hash_t *bblk_hash_list[BBLK_HASH_TOT] = { 42 &bblk_no_hash, 43 &bblk_md5_hash 44 }; 45 46 /* 47 * einfo_compare_dotted_version() 48 * Compares two strings with an arbitrary long number of dot-separated numbers. 49 * Returns: 0 - if the version numbers are equal 50 * 1 - if str1 version number is more recent than str2 51 * 2 - if str2 version number is more recent than str1 52 * -1 - if an error occurred 53 * 54 * Comparison is done field by field, by retrieving an unsigned integer value, 55 * (missing fields are assumed as 0, but explict zeroes take precedence) so: 56 * 4.1.2.11 > 4.1.2.2 > 4.1.2.0 > 4.1.2 57 * 58 * where ">" means "more recent than". 59 */ 60 static int 61 einfo_compare_dotted_version(const char *str1, const char *str2) 62 { 63 int retval = 0; 64 char *verstr1, *verstr2, *freeptr1, *freeptr2; 65 char *parsep1, *parsep2; 66 unsigned int val_str1, val_str2; 67 68 freeptr1 = verstr1 = strdup(str1); 69 freeptr2 = verstr2 = strdup(str2); 70 if (verstr1 == NULL || verstr2 == NULL) { 71 retval = -1; 72 goto out; 73 } 74 75 while (verstr1 != NULL && verstr2 != NULL) { 76 parsep1 = strsep(&verstr1, "."); 77 parsep2 = strsep(&verstr2, "."); 78 79 val_str1 = atoi(parsep1); 80 val_str2 = atoi(parsep2); 81 82 if (val_str1 > val_str2) { 83 retval = 1; 84 goto out; 85 } 86 87 if (val_str2 > val_str1) { 88 retval = 2; 89 goto out; 90 } 91 } 92 93 /* Common portion of the version string is equal. */ 94 if (verstr1 == NULL && verstr2 != NULL) 95 retval = 2; 96 if (verstr2 == NULL && verstr1 != NULL) 97 retval = 1; 98 99 out: 100 free(freeptr1); 101 free(freeptr2); 102 return (retval); 103 } 104 105 /* 106 * einfo_compare_timestamps() 107 * Currently, timestamp is in %Y%m%dT%H%M%SZ format in UTC, which means that 108 * we can simply do a lexicographic comparison to know which one is the most 109 * recent. 110 * 111 * Returns: 0 - if timestamps coincide 112 * 1 - if the timestamp in str1 is more recent 113 * 2 - if the timestamp in str2 is more recent 114 */ 115 static int 116 einfo_compare_timestamps(const char *str1, const char *str2) 117 { 118 int retval; 119 120 retval = strcmp(str1, str2); 121 if (retval > 0) 122 retval = 1; 123 if (retval < 0) 124 retval = 2; 125 126 return (retval); 127 } 128 129 /* 130 * einfo_compare_version() 131 * Given two extended versions, compare the two and returns which one is more 132 * "recent". Comparison is based on dotted version number fields and a 133 * timestamp. 134 * 135 * Returns: -1 - on error 136 * 0 - if the two versions coincide 137 * 1 - if the version in str1 is more recent 138 * 2 - if the version in str2 is more recent 139 */ 140 static int 141 einfo_compare_version(const char *str1, const char *str2) 142 { 143 int retval = 0; 144 char *verstr1, *verstr2, *freeptr1, *freeptr2; 145 char *parsep1, *parsep2; 146 147 freeptr1 = verstr1 = strdup(str1); 148 freeptr2 = verstr2 = strdup(str2); 149 if (verstr1 == NULL || verstr2 == NULL) { 150 retval = -1; 151 goto out; 152 } 153 154 parsep1 = verstr1; 155 parsep2 = verstr2; 156 157 while (parsep1 != NULL && parsep2 != NULL) { 158 parsep1 = strsep(&verstr1, ",:-"); 159 parsep2 = strsep(&verstr2, ",:-"); 160 161 /* verstr1 or verstr2 will be NULL before parsep1 or parsep2. */ 162 if (verstr1 == NULL || verstr2 == NULL) { 163 retval = einfo_compare_timestamps(parsep1, parsep2); 164 goto out; 165 } 166 167 retval = einfo_compare_dotted_version(parsep1, parsep2); 168 if (retval == 0) 169 continue; 170 else 171 goto out; 172 } 173 out: 174 free(freeptr1); 175 free(freeptr2); 176 return (retval); 177 } 178 179 /* 180 * print_einfo() 181 * 182 * Print the extended information contained into the pointed structure. 183 * 'bufsize' specifies the real size of the structure, since str_off and 184 * hash_off need to point somewhere past the header. 185 */ 186 void 187 print_einfo(uint8_t flags, bblk_einfo_t *einfo, unsigned long bufsize) 188 { 189 int i = 0; 190 char *version; 191 boolean_t has_hash = B_FALSE; 192 unsigned char *hash; 193 194 if (einfo->str_off + einfo->str_size > bufsize) { 195 (void) fprintf(stdout, gettext("String offset %d is beyond the " 196 "buffer size\n"), einfo->str_off); 197 return; 198 } 199 200 version = (char *)einfo + einfo->str_off; 201 if (einfo->hash_type != BBLK_NO_HASH && 202 einfo->hash_type < BBLK_HASH_TOT) { 203 if (einfo->hash_off + einfo->hash_size > bufsize) { 204 (void) fprintf(stdout, gettext("Warning: hashing " 205 "present but hash offset %d is beyond the buffer " 206 "size\n"), einfo->hash_off); 207 has_hash = B_FALSE; 208 } else { 209 hash = (unsigned char *)einfo + einfo->hash_off; 210 has_hash = B_TRUE; 211 } 212 } 213 214 if (flags & EINFO_PRINT_HEADER) { 215 (void) fprintf(stdout, "Boot Block Extended Info Header:\n"); 216 (void) fprintf(stdout, "\tmagic: "); 217 for (i = 0; i < EINFO_MAGIC_SIZE; i++) 218 (void) fprintf(stdout, "%c", einfo->magic[i]); 219 (void) fprintf(stdout, "\n"); 220 (void) fprintf(stdout, "\tversion: %d\n", einfo->version); 221 (void) fprintf(stdout, "\tflags: %x\n", einfo->flags); 222 (void) fprintf(stdout, "\textended version string offset: %d\n", 223 einfo->str_off); 224 (void) fprintf(stdout, "\textended version string size: %d\n", 225 einfo->str_size); 226 (void) fprintf(stdout, "\thashing type: %d (%s)\n", 227 einfo->hash_type, has_hash ? 228 bblk_hash_list[einfo->hash_type]->name : "nil"); 229 (void) fprintf(stdout, "\thash offset: %d\n", einfo->hash_off); 230 (void) fprintf(stdout, "\thash size: %d\n", einfo->hash_size); 231 } 232 233 if (flags & EINFO_EASY_PARSE) { 234 (void) fprintf(stdout, "%s\n", version); 235 } else { 236 (void) fprintf(stdout, "Extended version string: %s\n", 237 version); 238 if (has_hash) { 239 (void) fprintf(stdout, "%s hash: ", 240 bblk_hash_list[einfo->hash_type]->name); 241 } else { 242 (void) fprintf(stdout, "No hashing available\n"); 243 } 244 } 245 246 if (has_hash) { 247 for (i = 0; i < einfo->hash_size; i++) { 248 (void) fprintf(stdout, "%02x", hash[i]); 249 } 250 (void) fprintf(stdout, "\n"); 251 } 252 } 253 254 static int 255 compute_hash(bblk_hs_t *hs, unsigned char *dest, bblk_hash_t *hash) 256 { 257 if (hs == NULL || dest == NULL || hash == NULL) 258 return (-1); 259 260 hash->compute_hash(dest, hs->src_buf, hs->src_size); 261 return (0); 262 } 263 264 int 265 prepare_and_write_einfo(unsigned char *dest, char *infostr, bblk_hs_t *hs, 266 uint32_t maxsize, uint32_t *used_space) 267 { 268 uint16_t hash_size; 269 uint32_t hash_off; 270 unsigned char *data; 271 bblk_einfo_t *einfo = (bblk_einfo_t *)dest; 272 bblk_hash_t *hashinfo = bblk_hash_list[BBLK_DEFAULT_HASH]; 273 274 /* 275 * 'dest' might be both containing the buffer we want to hash and 276 * containing our einfo structure: delay any update of it after the 277 * hashing has been calculated. 278 */ 279 hash_size = hashinfo->size; 280 hash_off = sizeof (bblk_einfo_t); 281 282 if (hash_off + hash_size > maxsize) { 283 (void) fprintf(stderr, gettext("Unable to add extended info, " 284 "not enough space\n")); 285 return (-1); 286 } 287 288 data = dest + hash_off; 289 if (compute_hash(hs, data, hashinfo) < 0) { 290 (void) fprintf(stderr, gettext("%s hash operation failed\n"), 291 hashinfo->name); 292 einfo->hash_type = bblk_no_hash.type; 293 einfo->hash_size = bblk_no_hash.size; 294 } else { 295 einfo->hash_type = hashinfo->type; 296 einfo->hash_size = hashinfo->size; 297 } 298 299 (void) memcpy(einfo->magic, EINFO_MAGIC, EINFO_MAGIC_SIZE); 300 einfo->version = BBLK_EINFO_VERSION; 301 einfo->flags = 0; 302 einfo->hash_off = hash_off; 303 einfo->hash_size = hash_size; 304 einfo->str_off = einfo->hash_off + einfo->hash_size + 1; 305 306 if (infostr == NULL) { 307 (void) fprintf(stderr, gettext("Unable to add extended info, " 308 "string is empty\n")); 309 return (-1); 310 } 311 einfo->str_size = strlen(infostr); 312 313 if (einfo->str_off + einfo->str_size > maxsize) { 314 (void) fprintf(stderr, gettext("Unable to add extended info, " 315 "not enough space\n")); 316 return (-1); 317 } 318 319 data = dest + einfo->str_off; 320 (void) memcpy(data, infostr, einfo->str_size); 321 *used_space = einfo->str_off + einfo->str_size; 322 323 return (0); 324 } 325 326 /* 327 * einfo_should_update() 328 * Given information on the boot block currently on disk (disk_einfo) and 329 * information on the supplied boot block (hs for hashing, verstr as the 330 * associated version string) decide if an update of the on-disk boot block 331 * is necessary or not. 332 */ 333 boolean_t 334 einfo_should_update(bblk_einfo_t *disk_einfo, bblk_hs_t *hs, char *verstr) 335 { 336 bblk_hash_t *hashing; 337 unsigned char *disk_hash; 338 unsigned char *local_hash; 339 char *disk_version; 340 int retval; 341 342 if (disk_einfo == NULL) 343 return (B_TRUE); 344 345 if (memcmp(disk_einfo->magic, EINFO_MAGIC, EINFO_MAGIC_SIZE) != 0) 346 return (B_TRUE); 347 348 if (disk_einfo->version < BBLK_EINFO_VERSION) 349 return (B_TRUE); 350 351 disk_version = einfo_get_string(disk_einfo); 352 retval = einfo_compare_version(verstr, disk_version); 353 /* 354 * If something goes wrong or if the on-disk version is more recent 355 * do not update the bootblock. 356 */ 357 if (retval == -1 || retval == 2) 358 return (B_FALSE); 359 360 /* 361 * If we got here it means that the two version strings are either 362 * equal or the new bootblk binary is more recent. In order to save 363 * some needless writes let's use the hash to determine if an update 364 * is really necessary. 365 */ 366 if (disk_einfo->hash_type == bblk_no_hash.type) 367 return (B_TRUE); 368 369 if (disk_einfo->hash_type >= BBLK_HASH_TOT) 370 return (B_TRUE); 371 372 hashing = bblk_hash_list[disk_einfo->hash_type]; 373 374 local_hash = malloc(hashing->size); 375 if (local_hash == NULL) 376 return (B_TRUE); 377 378 /* 379 * Failure in computing the hash may mean something wrong 380 * with the boot block file. Better be conservative here. 381 */ 382 if (compute_hash(hs, local_hash, hashing) < 0) { 383 free(local_hash); 384 return (B_FALSE); 385 } 386 387 disk_hash = (unsigned char *)einfo_get_hash(disk_einfo); 388 389 if (memcmp(local_hash, disk_hash, disk_einfo->hash_size) == 0) { 390 free(local_hash); 391 return (B_FALSE); 392 } 393 394 free(local_hash); 395 return (B_TRUE); 396 } 397 398 char * 399 einfo_get_string(bblk_einfo_t *einfo) 400 { 401 if (einfo == NULL) 402 return (NULL); 403 404 return ((char *)einfo + einfo->str_off); 405 } 406 407 char * 408 einfo_get_hash(bblk_einfo_t *einfo) 409 { 410 if (einfo == NULL) 411 return (NULL); 412 413 return ((char *)einfo + einfo->hash_off); 414 } 415