1 /* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
4 * This file is part of Evergreen.
6 * Evergreen is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published
8 * by the Free Software Foundation, either version 2 of the License,
9 * or (at your option) any later version.
11 * Evergreen is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with Evergreen. If not, see <http://www.gnu.org/licenses/>.
19 * This Source Code Form is derived from code that was originally
20 * subject to the terms of the Mozilla Public License, v. 2.0 and
21 * included in Evergreen. You may, therefore, use this Source Code
22 * Form under the terms of the Mozilla Public License 2.0. This
23 * licensing option does not affect the larger Evergreen project, only
24 * the Source Code Forms bearing this exception are affected. If a
25 * copy of the MPL was not distributed with this file, You can obtain
26 * one at http://mozilla.org/MPL/2.0/.
29 #include <sys/types.h>
34 #include "mar_private.h"
35 #include "mar_cmdline.h"
41 #include <netinet/in.h>
48 uint32_t size_allocated;
53 * Push a new item onto the stack of items. The stack is a single block
56 static int mar_push(struct MarItemStack *stack, uint32_t length, uint32_t flags,
59 uint32_t n_offset, n_length, n_flags;
63 namelen = strlen(name);
64 size = MAR_ITEM_SIZE(namelen);
66 if (stack->size_allocated - stack->size_used < size) {
67 /* increase size of stack */
68 size_t size_needed = ROUND_UP(stack->size_used + size, BLOCKSIZE);
69 stack->head = realloc(stack->head, size_needed);
72 stack->size_allocated = size_needed;
75 data = (((char *) stack->head) + stack->size_used);
77 n_offset = htonl(stack->last_offset);
78 n_length = htonl(length);
79 n_flags = htonl(flags);
81 memcpy(data, &n_offset, sizeof(n_offset));
82 data += sizeof(n_offset);
84 memcpy(data, &n_length, sizeof(n_length));
85 data += sizeof(n_length);
87 memcpy(data, &n_flags, sizeof(n_flags));
88 data += sizeof(n_flags);
90 memcpy(data, name, namelen + 1);
92 stack->size_used += size;
93 stack->last_offset += length;
97 static int mar_concat_file(FILE *fp, const char *path) {
103 in = fopen(path, "rb");
107 while ((len = fread(buf, 1, BLOCKSIZE, in)) > 0) {
108 if (fwrite(buf, len, 1, fp) != 1) {
119 * Writes out the product information block to the specified file.
121 * @param fp The opened MAR file being created.
122 * @param stack A pointer to the MAR item stack being used to create
124 * @param infoBlock The product info block to store in the file.
125 * @return 0 on success.
128 mar_concat_product_info_block(FILE *fp,
129 struct MarItemStack *stack,
130 struct ProductInformationBlock *infoBlock)
132 char buf[PIB_MAX_MAR_CHANNEL_ID_SIZE + PIB_MAX_PRODUCT_VERSION_SIZE];
133 uint32_t additionalBlockID = 1, infoBlockSize, unused;
134 if (!fp || !infoBlock ||
135 !infoBlock->MARChannelID ||
136 !infoBlock->productVersion) {
140 /* The MAR channel name must be < 64 bytes per the spec */
141 if (strlen(infoBlock->MARChannelID) > PIB_MAX_MAR_CHANNEL_ID_SIZE) {
145 /* The product version must be < 32 bytes per the spec */
146 if (strlen(infoBlock->productVersion) > PIB_MAX_PRODUCT_VERSION_SIZE) {
150 /* Although we don't need the product information block size to include the
151 maximum MAR channel name and product version, we allocate the maximum
152 amount to make it easier to modify the MAR file for repurposing MAR files
153 to different MAR channels. + 2 is for the NULL terminators. */
154 infoBlockSize = sizeof(infoBlockSize) +
155 sizeof(additionalBlockID) +
156 PIB_MAX_MAR_CHANNEL_ID_SIZE +
157 PIB_MAX_PRODUCT_VERSION_SIZE + 2;
159 stack->last_offset += infoBlockSize;
162 /* Write out the product info block size */
163 infoBlockSize = htonl(infoBlockSize);
164 if (fwrite(&infoBlockSize,
165 sizeof(infoBlockSize), 1, fp) != 1) {
168 infoBlockSize = ntohl(infoBlockSize);
170 /* Write out the product info block ID */
171 additionalBlockID = htonl(additionalBlockID);
172 if (fwrite(&additionalBlockID,
173 sizeof(additionalBlockID), 1, fp) != 1) {
176 additionalBlockID = ntohl(additionalBlockID);
178 /* Write out the channel name and NULL terminator */
179 if (fwrite(infoBlock->MARChannelID,
180 strlen(infoBlock->MARChannelID) + 1, 1, fp) != 1) {
184 /* Write out the product version string and NULL terminator */
185 if (fwrite(infoBlock->productVersion,
186 strlen(infoBlock->productVersion) + 1, 1, fp) != 1) {
190 /* Write out the rest of the block that is unused */
191 unused = infoBlockSize - (sizeof(infoBlockSize) +
192 sizeof(additionalBlockID) +
193 strlen(infoBlock->MARChannelID) +
194 strlen(infoBlock->productVersion) + 2);
195 memset(buf, 0, sizeof(buf));
196 if (fwrite(buf, unused, 1, fp) != 1) {
203 * Refreshes the product information block with the new information.
204 * The input MAR must not be signed or the function call will fail.
206 * @param path The path to the MAR file whose product info block
207 * should be refreshed.
208 * @param infoBlock Out parameter for where to store the result to
209 * @return 0 on success, -1 on failure
212 refresh_product_info_block(const char *path,
213 struct ProductInformationBlock *infoBlock)
217 uint32_t numSignatures, additionalBlockSize, additionalBlockID,
218 offsetAdditionalBlocks, numAdditionalBlocks, i;
219 int additionalBlocks, hasSignatureBlock;
222 rv = get_mar_file_info(path,
226 &offsetAdditionalBlocks,
227 &numAdditionalBlocks);
229 fprintf(stderr, "ERROR: Could not obtain MAR information.\n");
233 if (hasSignatureBlock && numSignatures) {
234 fprintf(stderr, "ERROR: Cannot refresh a signed MAR\n");
238 fp = fopen(path, "r+b");
240 fprintf(stderr, "ERROR: could not open target file: %s\n", path);
244 if (fseeko(fp, offsetAdditionalBlocks, SEEK_SET)) {
245 fprintf(stderr, "ERROR: could not seek to additional blocks\n");
250 for (i = 0; i < numAdditionalBlocks; ++i) {
251 /* Get the position of the start of this block */
254 /* Read the additional block size */
255 if (fread(&additionalBlockSize,
256 sizeof(additionalBlockSize),
260 additionalBlockSize = ntohl(additionalBlockSize);
262 /* Read the additional block ID */
263 if (fread(&additionalBlockID,
264 sizeof(additionalBlockID),
268 additionalBlockID = ntohl(additionalBlockID);
270 if (PRODUCT_INFO_BLOCK_ID == additionalBlockID) {
271 if (fseeko(fp, oldPos, SEEK_SET)) {
272 fprintf(stderr, "Could not seek back to Product Information Block\n");
277 if (mar_concat_product_info_block(fp, NULL, infoBlock)) {
278 fprintf(stderr, "Could not concat Product Information Block\n");
286 /* This is not the additional block you're looking for. Move along. */
287 if (fseek(fp, additionalBlockSize, SEEK_CUR)) {
288 fprintf(stderr, "ERROR: Could not seek past current block.\n");
295 /* If we had a product info block we would have already returned */
297 fprintf(stderr, "ERROR: Could not refresh because block does not exist\n");
302 * Create a MAR file from a set of files.
303 * @param dest The path to the file to create. This path must be
304 * compatible with fopen.
305 * @param numfiles The number of files to store in the archive.
306 * @param files The list of null-terminated file paths. Each file
307 * path must be compatible with fopen.
308 * @param infoBlock The information to store in the product information block.
309 * @return A non-zero value if an error occurs.
311 int mar_create(const char *dest, int
312 num_files, char **files,
313 struct ProductInformationBlock *infoBlock) {
314 struct MarItemStack stack;
315 uint32_t offset_to_index = 0, size_of_index,
316 numSignatures, numAdditionalSections;
317 uint64_t sizeOfEntireMAR = 0;
322 memset(&stack, 0, sizeof(stack));
324 fp = fopen(dest, "wb");
326 fprintf(stderr, "ERROR: could not create target file: %s\n", dest);
330 if (fwrite(MAR_ID, MAR_ID_SIZE, 1, fp) != 1)
332 if (fwrite(&offset_to_index, sizeof(uint32_t), 1, fp) != 1)
335 stack.last_offset = MAR_ID_SIZE +
336 sizeof(offset_to_index) +
337 sizeof(numSignatures) +
338 sizeof(numAdditionalSections) +
339 sizeof(sizeOfEntireMAR);
341 /* We will circle back on this at the end of the MAR creation to fill it */
342 if (fwrite(&sizeOfEntireMAR, sizeof(sizeOfEntireMAR), 1, fp) != 1) {
346 /* Write out the number of signatures, for now only at most 1 is supported */
348 if (fwrite(&numSignatures, sizeof(numSignatures), 1, fp) != 1) {
352 /* Write out the number of additional sections, for now just 1
353 for the product info block */
354 numAdditionalSections = htonl(1);
355 if (fwrite(&numAdditionalSections,
356 sizeof(numAdditionalSections), 1, fp) != 1) {
359 numAdditionalSections = ntohl(numAdditionalSections);
361 if (mar_concat_product_info_block(fp, &stack, infoBlock)) {
365 for (i = 0; i < num_files; ++i) {
366 if (stat(files[i], &st)) {
367 fprintf(stderr, "ERROR: file not found: %s\n", files[i]);
371 if (mar_push(&stack, st.st_size, st.st_mode & 0777, files[i]))
374 /* concatenate input file to archive */
375 if (mar_concat_file(fp, files[i]))
379 /* write out the index (prefixed with length of index) */
380 size_of_index = htonl(stack.size_used);
381 if (fwrite(&size_of_index, sizeof(size_of_index), 1, fp) != 1)
383 if (fwrite(stack.head, stack.size_used, 1, fp) != 1)
386 /* To protect against invalid MAR files, we assumes that the MAR file
387 size is less than or equal to MAX_SIZE_OF_MAR_FILE. */
388 if (ftell(fp) > MAX_SIZE_OF_MAR_FILE) {
392 /* write out offset to index file in network byte order */
393 offset_to_index = htonl(stack.last_offset);
394 if (fseek(fp, MAR_ID_SIZE, SEEK_SET))
396 if (fwrite(&offset_to_index, sizeof(offset_to_index), 1, fp) != 1)
398 offset_to_index = ntohl(stack.last_offset);
400 sizeOfEntireMAR = ((uint64_t)stack.last_offset) +
402 sizeof(size_of_index);
403 sizeOfEntireMAR = HOST_TO_NETWORK64(sizeOfEntireMAR);
404 if (fwrite(&sizeOfEntireMAR, sizeof(sizeOfEntireMAR), 1, fp) != 1)
406 sizeOfEntireMAR = NETWORK_TO_HOST64(sizeOfEntireMAR);