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: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #include <sys/types.h>
8 #include <sys/stat.h>
9 #include <fcntl.h>
10 #include <stdlib.h>
11 #include <string.h>
12 #include "mar_private.h"
13 #include "mar_cmdline.h"
14 #include "mar.h"
15
16 #ifdef XP_WIN
17 #include <winsock2.h>
18 #else
19 #include <netinet/in.h>
20 #include <unistd.h>
21 #endif
22
23 struct MarItemStack {
24 void *head;
25 uint32_t size_used;
26 uint32_t size_allocated;
27 uint32_t last_offset;
28 };
29
30 /**
31 * Push a new item onto the stack of items. The stack is a single block
32 * of memory.
33 */
mar_push(struct MarItemStack * stack,uint32_t length,uint32_t flags,const char * name)34 static int mar_push(struct MarItemStack *stack, uint32_t length, uint32_t flags,
35 const char *name) {
36 int namelen;
37 uint32_t n_offset, n_length, n_flags;
38 uint32_t size;
39 char *data;
40
41 namelen = strlen(name);
42 size = MAR_ITEM_SIZE(namelen);
43
44 if (stack->size_allocated - stack->size_used < size) {
45 /* increase size of stack */
46 size_t size_needed = ROUND_UP(stack->size_used + size, BLOCKSIZE);
47 stack->head = realloc(stack->head, size_needed);
48 if (!stack->head)
49 return -1;
50 stack->size_allocated = size_needed;
51 }
52
53 data = (((char *) stack->head) + stack->size_used);
54
55 n_offset = htonl(stack->last_offset);
56 n_length = htonl(length);
57 n_flags = htonl(flags);
58
59 memcpy(data, &n_offset, sizeof(n_offset));
60 data += sizeof(n_offset);
61
62 memcpy(data, &n_length, sizeof(n_length));
63 data += sizeof(n_length);
64
65 memcpy(data, &n_flags, sizeof(n_flags));
66 data += sizeof(n_flags);
67
68 memcpy(data, name, namelen + 1);
69
70 stack->size_used += size;
71 stack->last_offset += length;
72 return 0;
73 }
74
mar_concat_file(FILE * fp,const char * path)75 static int mar_concat_file(FILE *fp, const char *path) {
76 FILE *in;
77 char buf[BLOCKSIZE];
78 size_t len;
79 int rv = 0;
80
81 in = fopen(path, "rb");
82 if (!in) {
83 fprintf(stderr, "ERROR: could not open file in mar_concat_file()\n");
84 perror(path);
85 return -1;
86 }
87
88 while ((len = fread(buf, 1, BLOCKSIZE, in)) > 0) {
89 if (fwrite(buf, len, 1, fp) != 1) {
90 rv = -1;
91 break;
92 }
93 }
94
95 fclose(in);
96 return rv;
97 }
98
99 /**
100 * Writes out the product information block to the specified file.
101 *
102 * @param fp The opened MAR file being created.
103 * @param stack A pointer to the MAR item stack being used to create
104 * the MAR
105 * @param infoBlock The product info block to store in the file.
106 * @return 0 on success.
107 */
108 static int
mar_concat_product_info_block(FILE * fp,struct MarItemStack * stack,struct ProductInformationBlock * infoBlock)109 mar_concat_product_info_block(FILE *fp,
110 struct MarItemStack *stack,
111 struct ProductInformationBlock *infoBlock)
112 {
113 char buf[PIB_MAX_MAR_CHANNEL_ID_SIZE + PIB_MAX_PRODUCT_VERSION_SIZE];
114 uint32_t additionalBlockID = 1, infoBlockSize, unused;
115 if (!fp || !infoBlock ||
116 !infoBlock->MARChannelID ||
117 !infoBlock->productVersion) {
118 return -1;
119 }
120
121 /* The MAR channel name must be < 64 bytes per the spec */
122 if (strlen(infoBlock->MARChannelID) > PIB_MAX_MAR_CHANNEL_ID_SIZE) {
123 return -1;
124 }
125
126 /* The product version must be < 32 bytes per the spec */
127 if (strlen(infoBlock->productVersion) > PIB_MAX_PRODUCT_VERSION_SIZE) {
128 return -1;
129 }
130
131 /* Although we don't need the product information block size to include the
132 maximum MAR channel name and product version, we allocate the maximum
133 amount to make it easier to modify the MAR file for repurposing MAR files
134 to different MAR channels. + 2 is for the NULL terminators. */
135 infoBlockSize = sizeof(infoBlockSize) +
136 sizeof(additionalBlockID) +
137 PIB_MAX_MAR_CHANNEL_ID_SIZE +
138 PIB_MAX_PRODUCT_VERSION_SIZE + 2;
139 if (stack) {
140 stack->last_offset += infoBlockSize;
141 }
142
143 /* Write out the product info block size */
144 infoBlockSize = htonl(infoBlockSize);
145 if (fwrite(&infoBlockSize,
146 sizeof(infoBlockSize), 1, fp) != 1) {
147 return -1;
148 }
149 infoBlockSize = ntohl(infoBlockSize);
150
151 /* Write out the product info block ID */
152 additionalBlockID = htonl(additionalBlockID);
153 if (fwrite(&additionalBlockID,
154 sizeof(additionalBlockID), 1, fp) != 1) {
155 return -1;
156 }
157 additionalBlockID = ntohl(additionalBlockID);
158
159 /* Write out the channel name and NULL terminator */
160 if (fwrite(infoBlock->MARChannelID,
161 strlen(infoBlock->MARChannelID) + 1, 1, fp) != 1) {
162 return -1;
163 }
164
165 /* Write out the product version string and NULL terminator */
166 if (fwrite(infoBlock->productVersion,
167 strlen(infoBlock->productVersion) + 1, 1, fp) != 1) {
168 return -1;
169 }
170
171 /* Write out the rest of the block that is unused */
172 unused = infoBlockSize - (sizeof(infoBlockSize) +
173 sizeof(additionalBlockID) +
174 strlen(infoBlock->MARChannelID) +
175 strlen(infoBlock->productVersion) + 2);
176 memset(buf, 0, sizeof(buf));
177 if (fwrite(buf, unused, 1, fp) != 1) {
178 return -1;
179 }
180 return 0;
181 }
182
183 /**
184 * Refreshes the product information block with the new information.
185 * The input MAR must not be signed or the function call will fail.
186 *
187 * @param path The path to the MAR file whose product info block
188 * should be refreshed.
189 * @param infoBlock Out parameter for where to store the result to
190 * @return 0 on success, -1 on failure
191 */
192 int
refresh_product_info_block(const char * path,struct ProductInformationBlock * infoBlock)193 refresh_product_info_block(const char *path,
194 struct ProductInformationBlock *infoBlock)
195 {
196 FILE *fp ;
197 int rv;
198 uint32_t numSignatures, additionalBlockSize, additionalBlockID,
199 offsetAdditionalBlocks, numAdditionalBlocks, i;
200 int additionalBlocks, hasSignatureBlock;
201 int64_t oldPos;
202
203 rv = get_mar_file_info(path,
204 &hasSignatureBlock,
205 &numSignatures,
206 &additionalBlocks,
207 &offsetAdditionalBlocks,
208 &numAdditionalBlocks);
209 if (rv) {
210 fprintf(stderr, "ERROR: Could not obtain MAR information.\n");
211 return -1;
212 }
213
214 if (hasSignatureBlock && numSignatures) {
215 fprintf(stderr, "ERROR: Cannot refresh a signed MAR\n");
216 return -1;
217 }
218
219 fp = fopen(path, "r+b");
220 if (!fp) {
221 fprintf(stderr, "ERROR: could not open target file: %s\n", path);
222 return -1;
223 }
224
225 if (fseeko(fp, offsetAdditionalBlocks, SEEK_SET)) {
226 fprintf(stderr, "ERROR: could not seek to additional blocks\n");
227 fclose(fp);
228 return -1;
229 }
230
231 for (i = 0; i < numAdditionalBlocks; ++i) {
232 /* Get the position of the start of this block */
233 oldPos = ftello(fp);
234
235 /* Read the additional block size */
236 if (fread(&additionalBlockSize,
237 sizeof(additionalBlockSize),
238 1, fp) != 1) {
239 fclose(fp);
240 return -1;
241 }
242 additionalBlockSize = ntohl(additionalBlockSize);
243
244 /* Read the additional block ID */
245 if (fread(&additionalBlockID,
246 sizeof(additionalBlockID),
247 1, fp) != 1) {
248 fclose(fp);
249 return -1;
250 }
251 additionalBlockID = ntohl(additionalBlockID);
252
253 if (PRODUCT_INFO_BLOCK_ID == additionalBlockID) {
254 if (fseeko(fp, oldPos, SEEK_SET)) {
255 fprintf(stderr, "Could not seek back to Product Information Block\n");
256 fclose(fp);
257 return -1;
258 }
259
260 if (mar_concat_product_info_block(fp, NULL, infoBlock)) {
261 fprintf(stderr, "Could not concat Product Information Block\n");
262 fclose(fp);
263 return -1;
264 }
265
266 fclose(fp);
267 return 0;
268 } else {
269 /* This is not the additional block you're looking for. Move along. */
270 if (fseek(fp, additionalBlockSize, SEEK_CUR)) {
271 fprintf(stderr, "ERROR: Could not seek past current block.\n");
272 fclose(fp);
273 return -1;
274 }
275 }
276 }
277
278 /* If we had a product info block we would have already returned */
279 fclose(fp);
280 fprintf(stderr, "ERROR: Could not refresh because block does not exist\n");
281 return -1;
282 }
283
284 /**
285 * Create a MAR file from a set of files.
286 * @param dest The path to the file to create. This path must be
287 * compatible with fopen.
288 * @param numfiles The number of files to store in the archive.
289 * @param files The list of null-terminated file paths. Each file
290 * path must be compatible with fopen.
291 * @param infoBlock The information to store in the product information block.
292 * @return A non-zero value if an error occurs.
293 */
mar_create(const char * dest,int num_files,char ** files,struct ProductInformationBlock * infoBlock)294 int mar_create(const char *dest, int
295 num_files, char **files,
296 struct ProductInformationBlock *infoBlock) {
297 struct MarItemStack stack;
298 uint32_t offset_to_index = 0, size_of_index,
299 numSignatures, numAdditionalSections;
300 uint64_t sizeOfEntireMAR = 0;
301 struct stat st;
302 FILE *fp;
303 int i, rv = -1;
304
305 memset(&stack, 0, sizeof(stack));
306
307 fp = fopen(dest, "wb");
308 if (!fp) {
309 fprintf(stderr, "ERROR: could not create target file: %s\n", dest);
310 return -1;
311 }
312
313 if (fwrite(MAR_ID, MAR_ID_SIZE, 1, fp) != 1)
314 goto failure;
315 if (fwrite(&offset_to_index, sizeof(uint32_t), 1, fp) != 1)
316 goto failure;
317
318 stack.last_offset = MAR_ID_SIZE +
319 sizeof(offset_to_index) +
320 sizeof(numSignatures) +
321 sizeof(numAdditionalSections) +
322 sizeof(sizeOfEntireMAR);
323
324 /* We will circle back on this at the end of the MAR creation to fill it */
325 if (fwrite(&sizeOfEntireMAR, sizeof(sizeOfEntireMAR), 1, fp) != 1) {
326 goto failure;
327 }
328
329 /* Write out the number of signatures, for now only at most 1 is supported */
330 numSignatures = 0;
331 if (fwrite(&numSignatures, sizeof(numSignatures), 1, fp) != 1) {
332 goto failure;
333 }
334
335 /* Write out the number of additional sections, for now just 1
336 for the product info block */
337 numAdditionalSections = htonl(1);
338 if (fwrite(&numAdditionalSections,
339 sizeof(numAdditionalSections), 1, fp) != 1) {
340 goto failure;
341 }
342 numAdditionalSections = ntohl(numAdditionalSections);
343
344 if (mar_concat_product_info_block(fp, &stack, infoBlock)) {
345 goto failure;
346 }
347
348 for (i = 0; i < num_files; ++i) {
349 if (stat(files[i], &st)) {
350 fprintf(stderr, "ERROR: file not found: %s\n", files[i]);
351 goto failure;
352 }
353
354 if (mar_push(&stack, st.st_size, st.st_mode & 0777, files[i]))
355 goto failure;
356
357 /* concatenate input file to archive */
358 if (mar_concat_file(fp, files[i]))
359 goto failure;
360 }
361
362 /* write out the index (prefixed with length of index) */
363 size_of_index = htonl(stack.size_used);
364 if (fwrite(&size_of_index, sizeof(size_of_index), 1, fp) != 1)
365 goto failure;
366 if (fwrite(stack.head, stack.size_used, 1, fp) != 1)
367 goto failure;
368
369 /* To protect against invalid MAR files, we assumes that the MAR file
370 size is less than or equal to MAX_SIZE_OF_MAR_FILE. */
371 if (ftell(fp) > MAX_SIZE_OF_MAR_FILE) {
372 goto failure;
373 }
374
375 /* write out offset to index file in network byte order */
376 offset_to_index = htonl(stack.last_offset);
377 if (fseek(fp, MAR_ID_SIZE, SEEK_SET))
378 goto failure;
379 if (fwrite(&offset_to_index, sizeof(offset_to_index), 1, fp) != 1)
380 goto failure;
381 offset_to_index = ntohl(stack.last_offset);
382
383 sizeOfEntireMAR = ((uint64_t)stack.last_offset) +
384 stack.size_used +
385 sizeof(size_of_index);
386 sizeOfEntireMAR = HOST_TO_NETWORK64(sizeOfEntireMAR);
387 if (fwrite(&sizeOfEntireMAR, sizeof(sizeOfEntireMAR), 1, fp) != 1)
388 goto failure;
389 sizeOfEntireMAR = NETWORK_TO_HOST64(sizeOfEntireMAR);
390
391 rv = 0;
392 failure:
393 if (stack.head)
394 free(stack.head);
395 fclose(fp);
396 if (rv)
397 remove(dest);
398 return rv;
399 }
400