michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim:set ts=2 sw=2 sts=2 et cindent: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include "mar_private.h" michael@0: #include "mar_cmdline.h" michael@0: #include "mar.h" michael@0: michael@0: #ifdef XP_WIN michael@0: #include michael@0: #else michael@0: #include michael@0: #include michael@0: #endif michael@0: michael@0: struct MarItemStack { michael@0: void *head; michael@0: uint32_t size_used; michael@0: uint32_t size_allocated; michael@0: uint32_t last_offset; michael@0: }; michael@0: michael@0: /** michael@0: * Push a new item onto the stack of items. The stack is a single block michael@0: * of memory. michael@0: */ michael@0: static int mar_push(struct MarItemStack *stack, uint32_t length, uint32_t flags, michael@0: const char *name) { michael@0: int namelen; michael@0: uint32_t n_offset, n_length, n_flags; michael@0: uint32_t size; michael@0: char *data; michael@0: michael@0: namelen = strlen(name); michael@0: size = MAR_ITEM_SIZE(namelen); michael@0: michael@0: if (stack->size_allocated - stack->size_used < size) { michael@0: /* increase size of stack */ michael@0: size_t size_needed = ROUND_UP(stack->size_used + size, BLOCKSIZE); michael@0: stack->head = realloc(stack->head, size_needed); michael@0: if (!stack->head) michael@0: return -1; michael@0: stack->size_allocated = size_needed; michael@0: } michael@0: michael@0: data = (((char *) stack->head) + stack->size_used); michael@0: michael@0: n_offset = htonl(stack->last_offset); michael@0: n_length = htonl(length); michael@0: n_flags = htonl(flags); michael@0: michael@0: memcpy(data, &n_offset, sizeof(n_offset)); michael@0: data += sizeof(n_offset); michael@0: michael@0: memcpy(data, &n_length, sizeof(n_length)); michael@0: data += sizeof(n_length); michael@0: michael@0: memcpy(data, &n_flags, sizeof(n_flags)); michael@0: data += sizeof(n_flags); michael@0: michael@0: memcpy(data, name, namelen + 1); michael@0: michael@0: stack->size_used += size; michael@0: stack->last_offset += length; michael@0: return 0; michael@0: } michael@0: michael@0: static int mar_concat_file(FILE *fp, const char *path) { michael@0: FILE *in; michael@0: char buf[BLOCKSIZE]; michael@0: size_t len; michael@0: int rv = 0; michael@0: michael@0: in = fopen(path, "rb"); michael@0: if (!in) { michael@0: fprintf(stderr, "ERROR: could not open file in mar_concat_file()\n"); michael@0: perror(path); michael@0: return -1; michael@0: } michael@0: michael@0: while ((len = fread(buf, 1, BLOCKSIZE, in)) > 0) { michael@0: if (fwrite(buf, len, 1, fp) != 1) { michael@0: rv = -1; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: fclose(in); michael@0: return rv; michael@0: } michael@0: michael@0: /** michael@0: * Writes out the product information block to the specified file. michael@0: * michael@0: * @param fp The opened MAR file being created. michael@0: * @param stack A pointer to the MAR item stack being used to create michael@0: * the MAR michael@0: * @param infoBlock The product info block to store in the file. michael@0: * @return 0 on success. michael@0: */ michael@0: static int michael@0: mar_concat_product_info_block(FILE *fp, michael@0: struct MarItemStack *stack, michael@0: struct ProductInformationBlock *infoBlock) michael@0: { michael@0: char buf[PIB_MAX_MAR_CHANNEL_ID_SIZE + PIB_MAX_PRODUCT_VERSION_SIZE]; michael@0: uint32_t additionalBlockID = 1, infoBlockSize, unused; michael@0: if (!fp || !infoBlock || michael@0: !infoBlock->MARChannelID || michael@0: !infoBlock->productVersion) { michael@0: return -1; michael@0: } michael@0: michael@0: /* The MAR channel name must be < 64 bytes per the spec */ michael@0: if (strlen(infoBlock->MARChannelID) > PIB_MAX_MAR_CHANNEL_ID_SIZE) { michael@0: return -1; michael@0: } michael@0: michael@0: /* The product version must be < 32 bytes per the spec */ michael@0: if (strlen(infoBlock->productVersion) > PIB_MAX_PRODUCT_VERSION_SIZE) { michael@0: return -1; michael@0: } michael@0: michael@0: /* Although we don't need the product information block size to include the michael@0: maximum MAR channel name and product version, we allocate the maximum michael@0: amount to make it easier to modify the MAR file for repurposing MAR files michael@0: to different MAR channels. + 2 is for the NULL terminators. */ michael@0: infoBlockSize = sizeof(infoBlockSize) + michael@0: sizeof(additionalBlockID) + michael@0: PIB_MAX_MAR_CHANNEL_ID_SIZE + michael@0: PIB_MAX_PRODUCT_VERSION_SIZE + 2; michael@0: if (stack) { michael@0: stack->last_offset += infoBlockSize; michael@0: } michael@0: michael@0: /* Write out the product info block size */ michael@0: infoBlockSize = htonl(infoBlockSize); michael@0: if (fwrite(&infoBlockSize, michael@0: sizeof(infoBlockSize), 1, fp) != 1) { michael@0: return -1; michael@0: } michael@0: infoBlockSize = ntohl(infoBlockSize); michael@0: michael@0: /* Write out the product info block ID */ michael@0: additionalBlockID = htonl(additionalBlockID); michael@0: if (fwrite(&additionalBlockID, michael@0: sizeof(additionalBlockID), 1, fp) != 1) { michael@0: return -1; michael@0: } michael@0: additionalBlockID = ntohl(additionalBlockID); michael@0: michael@0: /* Write out the channel name and NULL terminator */ michael@0: if (fwrite(infoBlock->MARChannelID, michael@0: strlen(infoBlock->MARChannelID) + 1, 1, fp) != 1) { michael@0: return -1; michael@0: } michael@0: michael@0: /* Write out the product version string and NULL terminator */ michael@0: if (fwrite(infoBlock->productVersion, michael@0: strlen(infoBlock->productVersion) + 1, 1, fp) != 1) { michael@0: return -1; michael@0: } michael@0: michael@0: /* Write out the rest of the block that is unused */ michael@0: unused = infoBlockSize - (sizeof(infoBlockSize) + michael@0: sizeof(additionalBlockID) + michael@0: strlen(infoBlock->MARChannelID) + michael@0: strlen(infoBlock->productVersion) + 2); michael@0: memset(buf, 0, sizeof(buf)); michael@0: if (fwrite(buf, unused, 1, fp) != 1) { michael@0: return -1; michael@0: } michael@0: return 0; michael@0: } michael@0: michael@0: /** michael@0: * Refreshes the product information block with the new information. michael@0: * The input MAR must not be signed or the function call will fail. michael@0: * michael@0: * @param path The path to the MAR file whose product info block michael@0: * should be refreshed. michael@0: * @param infoBlock Out parameter for where to store the result to michael@0: * @return 0 on success, -1 on failure michael@0: */ michael@0: int michael@0: refresh_product_info_block(const char *path, michael@0: struct ProductInformationBlock *infoBlock) michael@0: { michael@0: FILE *fp ; michael@0: int rv; michael@0: uint32_t numSignatures, additionalBlockSize, additionalBlockID, michael@0: offsetAdditionalBlocks, numAdditionalBlocks, i; michael@0: int additionalBlocks, hasSignatureBlock; michael@0: int64_t oldPos; michael@0: michael@0: rv = get_mar_file_info(path, michael@0: &hasSignatureBlock, michael@0: &numSignatures, michael@0: &additionalBlocks, michael@0: &offsetAdditionalBlocks, michael@0: &numAdditionalBlocks); michael@0: if (rv) { michael@0: fprintf(stderr, "ERROR: Could not obtain MAR information.\n"); michael@0: return -1; michael@0: } michael@0: michael@0: if (hasSignatureBlock && numSignatures) { michael@0: fprintf(stderr, "ERROR: Cannot refresh a signed MAR\n"); michael@0: return -1; michael@0: } michael@0: michael@0: fp = fopen(path, "r+b"); michael@0: if (!fp) { michael@0: fprintf(stderr, "ERROR: could not open target file: %s\n", path); michael@0: return -1; michael@0: } michael@0: michael@0: if (fseeko(fp, offsetAdditionalBlocks, SEEK_SET)) { michael@0: fprintf(stderr, "ERROR: could not seek to additional blocks\n"); michael@0: fclose(fp); michael@0: return -1; michael@0: } michael@0: michael@0: for (i = 0; i < numAdditionalBlocks; ++i) { michael@0: /* Get the position of the start of this block */ michael@0: oldPos = ftello(fp); michael@0: michael@0: /* Read the additional block size */ michael@0: if (fread(&additionalBlockSize, michael@0: sizeof(additionalBlockSize), michael@0: 1, fp) != 1) { michael@0: return -1; michael@0: } michael@0: additionalBlockSize = ntohl(additionalBlockSize); michael@0: michael@0: /* Read the additional block ID */ michael@0: if (fread(&additionalBlockID, michael@0: sizeof(additionalBlockID), michael@0: 1, fp) != 1) { michael@0: return -1; michael@0: } michael@0: additionalBlockID = ntohl(additionalBlockID); michael@0: michael@0: if (PRODUCT_INFO_BLOCK_ID == additionalBlockID) { michael@0: if (fseeko(fp, oldPos, SEEK_SET)) { michael@0: fprintf(stderr, "Could not seek back to Product Information Block\n"); michael@0: fclose(fp); michael@0: return -1; michael@0: } michael@0: michael@0: if (mar_concat_product_info_block(fp, NULL, infoBlock)) { michael@0: fprintf(stderr, "Could not concat Product Information Block\n"); michael@0: fclose(fp); michael@0: return -1; michael@0: } michael@0: michael@0: fclose(fp); michael@0: return 0; michael@0: } else { michael@0: /* This is not the additional block you're looking for. Move along. */ michael@0: if (fseek(fp, additionalBlockSize, SEEK_CUR)) { michael@0: fprintf(stderr, "ERROR: Could not seek past current block.\n"); michael@0: fclose(fp); michael@0: return -1; michael@0: } michael@0: } michael@0: } michael@0: michael@0: /* If we had a product info block we would have already returned */ michael@0: fclose(fp); michael@0: fprintf(stderr, "ERROR: Could not refresh because block does not exist\n"); michael@0: return -1; michael@0: } michael@0: michael@0: /** michael@0: * Create a MAR file from a set of files. michael@0: * @param dest The path to the file to create. This path must be michael@0: * compatible with fopen. michael@0: * @param numfiles The number of files to store in the archive. michael@0: * @param files The list of null-terminated file paths. Each file michael@0: * path must be compatible with fopen. michael@0: * @param infoBlock The information to store in the product information block. michael@0: * @return A non-zero value if an error occurs. michael@0: */ michael@0: int mar_create(const char *dest, int michael@0: num_files, char **files, michael@0: struct ProductInformationBlock *infoBlock) { michael@0: struct MarItemStack stack; michael@0: uint32_t offset_to_index = 0, size_of_index, michael@0: numSignatures, numAdditionalSections; michael@0: uint64_t sizeOfEntireMAR = 0; michael@0: struct stat st; michael@0: FILE *fp; michael@0: int i, rv = -1; michael@0: michael@0: memset(&stack, 0, sizeof(stack)); michael@0: michael@0: fp = fopen(dest, "wb"); michael@0: if (!fp) { michael@0: fprintf(stderr, "ERROR: could not create target file: %s\n", dest); michael@0: return -1; michael@0: } michael@0: michael@0: if (fwrite(MAR_ID, MAR_ID_SIZE, 1, fp) != 1) michael@0: goto failure; michael@0: if (fwrite(&offset_to_index, sizeof(uint32_t), 1, fp) != 1) michael@0: goto failure; michael@0: michael@0: stack.last_offset = MAR_ID_SIZE + michael@0: sizeof(offset_to_index) + michael@0: sizeof(numSignatures) + michael@0: sizeof(numAdditionalSections) + michael@0: sizeof(sizeOfEntireMAR); michael@0: michael@0: /* We will circle back on this at the end of the MAR creation to fill it */ michael@0: if (fwrite(&sizeOfEntireMAR, sizeof(sizeOfEntireMAR), 1, fp) != 1) { michael@0: goto failure; michael@0: } michael@0: michael@0: /* Write out the number of signatures, for now only at most 1 is supported */ michael@0: numSignatures = 0; michael@0: if (fwrite(&numSignatures, sizeof(numSignatures), 1, fp) != 1) { michael@0: goto failure; michael@0: } michael@0: michael@0: /* Write out the number of additional sections, for now just 1 michael@0: for the product info block */ michael@0: numAdditionalSections = htonl(1); michael@0: if (fwrite(&numAdditionalSections, michael@0: sizeof(numAdditionalSections), 1, fp) != 1) { michael@0: goto failure; michael@0: } michael@0: numAdditionalSections = ntohl(numAdditionalSections); michael@0: michael@0: if (mar_concat_product_info_block(fp, &stack, infoBlock)) { michael@0: goto failure; michael@0: } michael@0: michael@0: for (i = 0; i < num_files; ++i) { michael@0: if (stat(files[i], &st)) { michael@0: fprintf(stderr, "ERROR: file not found: %s\n", files[i]); michael@0: goto failure; michael@0: } michael@0: michael@0: if (mar_push(&stack, st.st_size, st.st_mode & 0777, files[i])) michael@0: goto failure; michael@0: michael@0: /* concatenate input file to archive */ michael@0: if (mar_concat_file(fp, files[i])) michael@0: goto failure; michael@0: } michael@0: michael@0: /* write out the index (prefixed with length of index) */ michael@0: size_of_index = htonl(stack.size_used); michael@0: if (fwrite(&size_of_index, sizeof(size_of_index), 1, fp) != 1) michael@0: goto failure; michael@0: if (fwrite(stack.head, stack.size_used, 1, fp) != 1) michael@0: goto failure; michael@0: michael@0: /* To protect against invalid MAR files, we assumes that the MAR file michael@0: size is less than or equal to MAX_SIZE_OF_MAR_FILE. */ michael@0: if (ftell(fp) > MAX_SIZE_OF_MAR_FILE) { michael@0: goto failure; michael@0: } michael@0: michael@0: /* write out offset to index file in network byte order */ michael@0: offset_to_index = htonl(stack.last_offset); michael@0: if (fseek(fp, MAR_ID_SIZE, SEEK_SET)) michael@0: goto failure; michael@0: if (fwrite(&offset_to_index, sizeof(offset_to_index), 1, fp) != 1) michael@0: goto failure; michael@0: offset_to_index = ntohl(stack.last_offset); michael@0: michael@0: sizeOfEntireMAR = ((uint64_t)stack.last_offset) + michael@0: stack.size_used + michael@0: sizeof(size_of_index); michael@0: sizeOfEntireMAR = HOST_TO_NETWORK64(sizeOfEntireMAR); michael@0: if (fwrite(&sizeOfEntireMAR, sizeof(sizeOfEntireMAR), 1, fp) != 1) michael@0: goto failure; michael@0: sizeOfEntireMAR = NETWORK_TO_HOST64(sizeOfEntireMAR); michael@0: michael@0: rv = 0; michael@0: failure: michael@0: if (stack.head) michael@0: free(stack.head); michael@0: fclose(fp); michael@0: if (rv) michael@0: remove(dest); michael@0: return rv; michael@0: }