diff --git a/CMakeLists.txt b/CMakeLists.txt index 0c1b91c..5746228 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,12 +27,13 @@ string(REPLACE "-" "." gitver "${gitver}") add_definitions(-DVERSION="${gitver}") add_executable(frugen frugen.c) +add_library(fru_reader STATIC fru_reader.c) add_library(fru-static STATIC fru.c) add_library(fru-shared SHARED fru.c) find_library(JSON_LIB json-c) SET_TARGET_PROPERTIES(fru-static PROPERTIES OUTPUT_NAME fru CLEAN_DIRECT_OUTPUT 1) SET_TARGET_PROPERTIES(fru-shared PROPERTIES OUTPUT_NAME fru CLEAN_DIRECT_OUTPUT 1) -target_link_libraries(frugen fru-static) +target_link_libraries(frugen fru-static fru_reader) if (JSON_LIB) message (STATUS "Using JSON Library found at " ${JSON_LIB}) add_definitions(-D__HAS_JSON__) @@ -42,6 +43,7 @@ else (JSON_LIB) endif (JSON_LIB) # To make frugen 32-bit, uncomment the following lines or use an external toolchain file +# set_target_properties(fru_reader-static PROPERTIES OUTPUT_NAME fru CLEAN_DIRECT_OUTPUT 1) #set_target_properties(frugen PROPERTIES COMPILE_FLAGS "-m32" LINK_FLAGS "-m32") #set_target_properties(fru-static PROPERTIES COMPILE_FLAGS "-m32" LINK_FLAGS "-m32") #set_target_properties(fru-shared PROPERTIES COMPILE_FLAGS "-m32" LINK_FLAGS "-m32") diff --git a/fatal.h b/fatal.h new file mode 100644 index 0000000..423f7dd --- /dev/null +++ b/fatal.h @@ -0,0 +1,6 @@ +#pragma once +#define fatal(fmt, args...) do { \ + fprintf(stderr, fmt, ##args); \ + fprintf(stderr, "\n"); \ + exit(1); \ +} while(0) diff --git a/fru.c b/fru.c index bb32c08..6e2fd04 100644 --- a/fru.c +++ b/fru.c @@ -29,6 +29,17 @@ #define DEBUG(f, args...) #endif +static time_t epoch_seconds_1996() { + struct tm tm_1996 = { + .tm_year = 96, + .tm_mon = 0, + .tm_mday = 1 + }; + // The argument to mktime is zoneless + return mktime(&tm_1996); +} + + /** * Strip trailing spaces */ @@ -170,23 +181,26 @@ static fru_field_t *fru_encode_6bit(const unsigned char *s /**< [in] Input strin } /** - * Allocate a buffer and decode a 6-bit ASCII string from it + * Decode a 6-bit ASCII string + * + * Return false if there were errors during decoding and true otherwise. */ -static unsigned char *fru_decode_6bit(const fru_field_t *field) +static bool fru_decode_6bit(const fru_field_t *field, + uint8_t *out, //< [out] buffer to decode into + size_t out_len) //< [in] length of output buffer { - unsigned char *out = NULL; const unsigned char *s6; int len, len6bit; int i, i6; - if (!field) return out; + if (!field) return false; len6bit = FRU_FIELDDATALEN(field->typelen); s6 = field->data; len = FRU_6BIT_FULLLENGTH(len6bit); - if (!(out = malloc(len + 1))) { - return out; + if (out_len < (len + 1)) { + return false; } DEBUG("Allocated a destination buffer at %p\n", out); memset(out, 0, len + 1); @@ -224,7 +238,7 @@ static unsigned char *fru_decode_6bit(const fru_field_t *field) // string that was a byte shorter than a multiple of 4. cut_tail(out); - return out; + return true; } /** @@ -280,24 +294,25 @@ fru_field_t * fru_encode_data(int len, const uint8_t *data) } /** - * Allocate a buffer and decode the data from it. + * Decode data from a buffer into another buffer. * * For binary data use FRU_FIELDDATALEN(field->typelen) to find - * out the size of the returned buffer. + * out the size of valid bytes in the returned buffer. + * + * Return false if there were errors during decoding and true otherwise. */ -static -unsigned char * fru_decode_data(const fru_field_t *field) +bool fru_decode_data(fru_field_t *field, + uint8_t *out, //< [out] buffer to decode into + size_t out_len) // <[in] length of output buffer { - unsigned char * out; - - if (!field) return NULL; + if (!field) return false; if (FRU_ISTYPE(field->typelen, ASCII_6BIT)) { - out = fru_decode_6bit(field); + return fru_decode_6bit(field, out, out_len); } else { - out = malloc(FRU_FIELDDATALEN(field->typelen) + 1); - if (!out) return NULL; + if (out_len < (FRU_FIELDDATALEN(field->typelen) + 1)) + return false; if (FRU_ISTYPE(field->typelen, BCDPLUS)) { int i; @@ -330,7 +345,7 @@ unsigned char * fru_decode_data(const fru_field_t *field) } } - return out; + return true; } #if 0 @@ -430,13 +445,7 @@ fru_info_area_t *fru_create_info_area(fru_area_type_t atype, ///< [in] Area t if (FRU_AREA_HAS_DATE(atype)) { uint32_t fru_time; - struct tm tm_1996 = { - .tm_year = 96, - .tm_mon = 0, - .tm_mday = 1 - }; const struct timeval tv_unspecified = { 0 }; - struct timeval tv_1996 = { 0 }; if (!tv) { errno = EFAULT; @@ -451,10 +460,8 @@ fru_info_area_t *fru_create_info_area(fru_area_type_t atype, ///< [in] Area t printf("Using FRU_DATE_UNSPECIFIED\n"); fru_time = FRU_DATE_UNSPECIFIED; } else { - // The argument to mktime is zoneless - tv_1996.tv_sec = mktime(&tm_1996); // FRU time is in minutes and we don't care about microseconds - fru_time = (tv->tv_sec - tv_1996.tv_sec) / 60; + fru_time = (tv->tv_sec - epoch_seconds_1996()) / 60; } header.mfgdate[0] = fru_time & 0xFF; header.mfgdate[1] = (fru_time >> 8) & 0xFF; @@ -492,7 +499,7 @@ fru_info_area_t *fru_create_info_area(fru_area_type_t atype, ///< [in] Area t // Now fill the output buffer. First copy the header. memcpy(outp, &header, headerlen); outp += headerlen; - + DEBUG("area size is %d (%d) bytes\n", totalsize, FRU_BYTES(header.blocks)); DEBUG("area size in header is (%d) bytes\n", FRU_BYTES(((fru_info_area_t *)out)->blocks)); @@ -520,6 +527,33 @@ fru_info_area_t *fru_create_info_area(fru_area_type_t atype, ///< [in] Area t return out; } +static bool fru_decode_custom_fields(const uint8_t *data, fru_reclist_t **reclist) { + fru_field_t *field = NULL; + + while (true) { + field = (fru_field_t*)data; + if (field->typelen == 0xc1) + break; + + fru_reclist_t *custom_field = add_reclist(reclist); + if (custom_field == NULL) + return false; + + // Create a NUL terminated version of the data for encoding + // TODO pass the length into fru_encode_data instead + size_t length = FRU_FIELDDATALEN(field->typelen); + uint8_t *buff = malloc(length + 1); + if (buff == NULL) + return false; + memcpy(buff, field->data, length); + buff[length] = 0; + custom_field->rec = fru_encode_data(LEN_AUTO, buff); + data += FRU_FIELDSIZE(field->typelen); + } + + return true; +} + /** * Allocate and build a Chassis Information Area block. * @@ -538,7 +572,7 @@ fru_info_area_t *fru_create_info_area(fru_area_type_t atype, ///< [in] Area t * @returns fru_info_area_t *area A newly allocated buffer containing the created area * */ -fru_chassis_area_t * fru_chassis_info(const fru_exploded_chassis_t *chassis) ///< [in] Exploded chassis info area +fru_chassis_area_t * fru_encode_chassis_info(const fru_exploded_chassis_t *chassis) ///< [in] Exploded chassis info area { int i; @@ -567,6 +601,31 @@ fru_chassis_area_t * fru_chassis_info(const fru_exploded_chassis_t *chassis) /// return out; } +bool fru_decode_chassis_info( + const fru_chassis_area_t *area, //< [in] encoded chassis + fru_exploded_chassis_t *chassis_out //< [out] +) +{ + chassis_out->type = area->langtype; + const uint8_t *data = area->data; + fru_field_t *field = (fru_field_t*)data; + + if(!fru_decode_data(field, chassis_out->pn, + sizeof(chassis_out->pn))) + return false; + + data += FRU_FIELDSIZE(field->typelen); + field = (fru_field_t*)data; + if (!fru_decode_data(field, chassis_out->serial, + sizeof(chassis_out->serial))) + return false; + data += FRU_FIELDSIZE(field->typelen); + + fru_decode_custom_fields(data, &chassis_out->cust); + + return true; +} + /** * Allocate and build a Board Information Area block. * @@ -585,7 +644,7 @@ fru_chassis_area_t * fru_chassis_info(const fru_exploded_chassis_t *chassis) /// * @returns fru_info_area_t *area A newly allocated buffer containing the created area * */ -fru_board_area_t * fru_board_info(const fru_exploded_board_t *board) ///< [in] Exploded board information area +fru_board_area_t * fru_encode_board_info(const fru_exploded_board_t *board) ///< [in] Exploded board information area { int i; @@ -612,6 +671,60 @@ fru_board_area_t * fru_board_info(const fru_exploded_board_t *board) ///< [in] E return out; } +bool fru_decode_board_info( + const fru_board_area_t *area, //< [in] encoded board + fru_exploded_board_t *board_out //< [out] +) +{ + fru_field_t *field; + const uint8_t *data = area->data; + + board_out->lang = area->langtype; + + uint32_t *min_since_1996 = (uint32_t*)&(area->mfgdate); + struct tm tm_1996 = { + .tm_year = 96, + .tm_mon = 0, + .tm_mday = 1 + }; + // The argument to mktime is zoneless + board_out->tv.tv_sec = mktime(&tm_1996) + 60 * (*min_since_1996); + + field = (fru_field_t*)data; + if (!fru_decode_data(field, board_out->mfg, + sizeof(board_out->mfg))) + return false; + data += FRU_FIELDSIZE(field->typelen); + + field = (fru_field_t*)data; + if (!fru_decode_data(field, board_out->pname, + sizeof(board_out->pname))) + return false; + data += FRU_FIELDSIZE(field->typelen); + + field = (fru_field_t*)data; + if (!fru_decode_data(field, board_out->serial, + sizeof(board_out->serial))) + return false; + data += FRU_FIELDSIZE(field->typelen); + + field = (fru_field_t*)data; + if (!fru_decode_data(field, board_out->pn, + sizeof(board_out->pn))) + return false; + data += FRU_FIELDSIZE(field->typelen); + + field = (fru_field_t*)data; + if (!fru_decode_data(field, board_out->file, + sizeof(board_out->file))) + return false; + data += FRU_FIELDSIZE(field->typelen); + + fru_decode_custom_fields(data, &board_out->cust); + + return true; +} + /** * Allocate and build a Product Information Area block. * @@ -630,7 +743,7 @@ fru_board_area_t * fru_board_info(const fru_exploded_board_t *board) ///< [in] E * @returns fru_info_area_t *area A newly allocated buffer containing the created area * */ -fru_product_area_t * fru_product_info(const fru_exploded_product_t *product) ///< [in] Exploded product information area +fru_product_area_t * fru_encode_product_info(const fru_exploded_product_t *product) ///< [in] Exploded product information area { int i; @@ -661,6 +774,64 @@ fru_product_area_t * fru_product_info(const fru_exploded_product_t *product) /// return out; } + +bool fru_decode_product_info( + const fru_product_area_t *area, //< [in] encoded product + fru_exploded_product_t *product_out //< [out] +) +{ + fru_field_t *field; + const uint8_t *data = area->data; + + product_out->lang = area->langtype; + + field = (fru_field_t*)data; + if (!fru_decode_data(field, product_out->mfg, + sizeof(product_out->mfg))) + return false; + data += FRU_FIELDSIZE(field->typelen); + + field = (fru_field_t*)data; + if (!fru_decode_data(field, product_out->pname, + sizeof(product_out->pname))) + return false; + data += FRU_FIELDSIZE(field->typelen); + + field = (fru_field_t*)data; + if (!fru_decode_data(field, product_out->pn, + sizeof(product_out->pn))) + return false; + data += FRU_FIELDSIZE(field->typelen); + + field = (fru_field_t*)data; + if (!fru_decode_data(field, product_out->ver, + sizeof(product_out->ver))) + return false; + data += FRU_FIELDSIZE(field->typelen); + + field = (fru_field_t*)data; + if (!fru_decode_data(field, product_out->serial, + sizeof(product_out->serial))) + return false; + data += FRU_FIELDSIZE(field->typelen); + + field = (fru_field_t*)data; + if (!fru_decode_data(field, product_out->atag, + sizeof(product_out->atag))) + return false; + data += FRU_FIELDSIZE(field->typelen); + + field = (fru_field_t*)data; + if (!fru_decode_data(field, product_out->file, + sizeof(product_out->file))) + return false; + data += FRU_FIELDSIZE(field->typelen); + + fru_decode_custom_fields(data, &product_out->cust); + + return true; +} + /** * Create a FRU information file. * @@ -789,7 +960,7 @@ void test_encodings(void) for(i = 0; i < ARRAY_SZ(test_strings); i++) { fru_field_t *field; - const unsigned char *out; + const unsigned char out[FRU_FIELDMAXARRAY]; printf("Data set %d.\n", i); printf("Original data "); @@ -825,8 +996,8 @@ void test_encodings(void) dump(FRU_FIELDSIZE(field->typelen), (uint8_t *)field); printf("Decoding... "); - out = fru_decode_data(field); - if (!out) { + if (!fru_decode_data(field->typelen, &field->data, out, + FRU_FIELDMAXARRAY)) { printf("FAIL!"); goto next; } @@ -849,7 +1020,6 @@ void test_encodings(void) printf("FAIL!"); } - free((void *)out); next: free((void *)field); printf("\n\n"); diff --git a/fru.h b/fru.h index c7441ba..ef353be 100644 --- a/fru.h +++ b/fru.h @@ -5,6 +5,7 @@ #ifndef __FRULIB_FRU_H__ #define __FRULIB_FRU_H__ +#include #include #include #include @@ -152,7 +153,7 @@ typedef fru_info_area_t fru_chassis_area_t; typedef struct fru_board_area_s { FRU_INFO_AREA_HEADER; - uint8_t mfgdate[3]; ///< Manufacturing date/time in seconds since 1996/1/1 0:00 + uint8_t mfgdate[3]; ///< Manufacturing date/time in minutes since 1996/1/1 0:00 uint8_t data[]; ///< Variable size (multiple of 8 bytes) data with tail padding and checksum } fru_board_area_t; @@ -226,10 +227,14 @@ typedef struct { #define fru_loadfield(eafield, value) strncpy(eafield, value, FRU_FIELDMAXLEN) -fru_chassis_area_t * fru_chassis_info(const fru_exploded_chassis_t *chassis); -fru_board_area_t * fru_board_info(const fru_exploded_board_t *board); -fru_product_area_t * fru_product_info(const fru_exploded_product_t *product); +fru_chassis_area_t * fru_encode_chassis_info(const fru_exploded_chassis_t *chassis); +bool fru_decode_chassis_info(const fru_chassis_area_t *area, fru_exploded_chassis_t *chassis_out); +fru_board_area_t * fru_encode_board_info(const fru_exploded_board_t *board); +bool fru_decode_board_info(const fru_board_area_t *area, fru_exploded_board_t *board_out); +fru_product_area_t * fru_encode_product_info(const fru_exploded_product_t *product); +bool fru_decode_product_info(const fru_product_area_t *area, fru_exploded_product_t *product_out); fru_field_t * fru_encode_data(int len, const uint8_t *data); +bool fru_decode_data(fru_field_t *field, uint8_t *out, size_t out_len); fru_t * fru_create(fru_area_t area[FRU_MAX_AREAS], size_t *size); #endif // __FRULIB_FRU_H__ diff --git a/fru_reader.c b/fru_reader.c new file mode 100644 index 0000000..9acb559 --- /dev/null +++ b/fru_reader.c @@ -0,0 +1,84 @@ +#include +#include +#include "fru.h" +#include "fru_reader.h" +#include "fatal.h" + +static void safe_read(int fd, uint8_t *buffer, size_t length) { + if (!buffer) + fatal("Cannot read into NULL buffer"); + + size_t total_bytes_read = 0; + while (total_bytes_read != length) { + ssize_t bytes_read = read( + fd, buffer + total_bytes_read, length - total_bytes_read); + if (bytes_read == -1) + fatal("Error reading file"); + if (bytes_read == 0) + fatal("Reached end of file"); + + total_bytes_read += bytes_read; + } +} + +fru_t *read_fru_header(int fd) { + fru_t *fru = malloc(sizeof(fru_t)); + if (!fru) + return NULL; + safe_read(fd, (uint8_t*)fru, sizeof(fru_t)); + return fru; +} + +/** + * Allocate and read a fru_chassis_area_t from a file descriptor + */ +fru_chassis_area_t *read_fru_chassis_area(int fd) { + size_t base_len = sizeof(fru_chassis_area_t); + fru_chassis_area_t *area = malloc(base_len); + if (!area) + return NULL; + safe_read(fd, (uint8_t*)area, base_len); + size_t data_len = 8 * area->blocks; + area = realloc(area, data_len); + if (!area) + return NULL; + safe_read(fd, (uint8_t*)&area->data, data_len - base_len); + + return area; +} + +/** + * Allocate and read a fru_board_area_t from a file descriptor + */ +fru_board_area_t *read_fru_board_area(int fd) { + size_t base_len = sizeof(fru_board_area_t); + fru_board_area_t *area = malloc(base_len); + if (!area) + return NULL; + safe_read(fd, (uint8_t*)area, base_len); + size_t data_len = 8 * area->blocks; + area = realloc(area, data_len); + if (!area) + return NULL; + safe_read(fd, (uint8_t*)&area->data, data_len - base_len); + + return area; +} + +/** + * Allocate and read a fru_product_area_t from a file descriptor + */ +fru_product_area_t *read_fru_product_area(int fd) { + size_t base_len = sizeof(fru_product_area_t); + fru_product_area_t *area = malloc(base_len); + if (!area) + return NULL; + safe_read(fd, (uint8_t*)area, base_len); + size_t data_len = 8 * area->blocks; + area = realloc(area, data_len); + if (!area) + return NULL; + safe_read(fd, (uint8_t*)&area->data, data_len - base_len); + + return area; +} diff --git a/fru_reader.h b/fru_reader.h new file mode 100644 index 0000000..26adf1d --- /dev/null +++ b/fru_reader.h @@ -0,0 +1,5 @@ +#include "fru.h" +fru_t *read_fru_header(int fd); +fru_chassis_area_t *read_fru_chassis_area(int fd); +fru_board_area_t *read_fru_board_area(int fd); +fru_product_area_t *read_fru_product_area(int fd); diff --git a/frugen.c b/frugen.c index a6b8fe0..e220992 100644 --- a/frugen.c +++ b/frugen.c @@ -15,18 +15,14 @@ #include #include #include "fru.h" +#include "fru_reader.h" #include "smbios.h" +#include "fatal.h" #ifdef __HAS_JSON__ #include #endif -#define fatal(fmt, args...) do { \ - fprintf(stderr, fmt, ##args); \ - fprintf(stderr, "\n"); \ - exit(1); \ -} while(0) - volatile int debug_level = 0; #define debug(level, fmt, args...) do { \ int e = errno; \ @@ -39,7 +35,6 @@ volatile int debug_level = 0; } \ } while(0) - /** * Convert 2 bytes of hex string into a binary byte */ @@ -264,6 +259,9 @@ int main(int argc, char *argv[]) /* Set input file format to JSON */ { .name = "json", .val = 'j', .has_arg = false }, + /* Set input file format to raw binary */ + { .name = "raw", .val = 'r', .has_arg = false }, + /* Set file to load the data from */ { .name = "from", .val = 'z', .has_arg = true }, @@ -302,6 +300,7 @@ int main(int argc, char *argv[]) "\n\t\t" "There must be an even number of characters in a 'binary' argument", ['j'] = "Set input text file format to JSON (default). Specify before '--from'", + ['r'] = "Set input file format to raw binary. Specify before '--from'", ['z'] = "Load FRU information from a text file", /* Chassis info area related options */ ['t'] = "Set chassis type (hex). Defaults to 0x02 ('Unknown')", @@ -336,7 +335,8 @@ int main(int argc, char *argv[]) has_internal = false, has_multirec = false; - bool use_json = true; /* TODO: Add more input formats, consider libconfig */ + bool use_json = false; /* TODO: Add more input formats, consider libconfig */ + bool use_binary = false; do { fru_reclist_t **custom = NULL; @@ -376,6 +376,16 @@ int main(int argc, char *argv[]) case 'j': // json use_json = true; + if (use_binary) { + fatal("Can't specify --json and --raw together"); + } + break; + + case 'r': // binary + use_binary = true; + if (use_json) { + fatal("Can't specify --json and --raw together"); + } break; case 'z': // from @@ -460,6 +470,61 @@ int main(int argc, char *argv[]) fatal("JSON support was disabled at compile time"); #endif } + else if (use_binary) { + int fd = open(optarg, O_RDONLY); + if (fd < 0) { + fatal("Failed to open file: %s", strerror(errno)); + } + + fru_t *raw_fru = read_fru_header(fd); + if (!raw_fru) + fatal("Failed to read fru header"); + + if (raw_fru->chassis != 0) { + if (lseek(fd, 8 * raw_fru->chassis, SEEK_SET) < 0) + fatal("Failed to seek"); + + fru_chassis_area_t *chassis_raw = + read_fru_chassis_area(fd); + bool success = fru_decode_chassis_info( + chassis_raw, &chassis); + if (!success) + fatal("Failed to decode chassis"); + + free(chassis_raw); + has_chassis = true; + } + if (raw_fru->board != 0) { + if(lseek(fd, 8 * raw_fru->board, SEEK_SET) < 0) + fatal("Failed to seek"); + + fru_board_area_t *board_raw = read_fru_board_area(fd); + bool success = fru_decode_board_info(board_raw, &board); + if (!success) + fatal("Failed to decode board"); + + free(board_raw); + has_board = true; + has_bdate = true; + } + if (raw_fru->product != 0) { + if (lseek(fd, 8 * raw_fru->product, SEEK_SET) < 0) + fatal("Failed to seek"); + + fru_product_area_t *product_raw = + read_fru_product_area(fd); + bool success = + fru_decode_product_info(product_raw, &product); + if (!success) + fatal("Failed to decode product"); + + free(product_raw); + has_product = true; + } + + free(raw_fru); + close(fd); + } else { fatal("The requested input file format is not supported"); } @@ -593,7 +658,7 @@ int main(int argc, char *argv[]) fru_chassis_area_t *ci = NULL; debug(1, "FRU file will have a chassis information area"); debug(3, "Chassis information area's custom field list is %p", chassis.cust); - ci = fru_chassis_info(&chassis); + ci = fru_encode_chassis_info(&chassis); e = errno; free_reclist(chassis.cust); @@ -617,7 +682,7 @@ int main(int argc, char *argv[]) board.tv = (struct timeval){0}; } - bi = fru_board_info(&board); + bi = fru_encode_board_info(&board); e = errno; free_reclist(board.cust); @@ -634,7 +699,7 @@ int main(int argc, char *argv[]) fru_product_area_t *pi = NULL; debug(1, "FRU file will have a product information area"); debug(3, "Product information area's custom field list is %p", product.cust); - pi = fru_product_info(&product); + pi = fru_encode_product_info(&product); e = errno; free_reclist(product.cust);