]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/xul/staff_client/external/libmar/src/mar_create.c
Add libmar to build update tools to the repo.
[working/Evergreen.git] / Open-ILS / xul / staff_client / external / libmar / src / mar_create.c
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 /*
4  * This file is part of Evergreen.
5  *
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.
10  *
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.
15  *
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/>.
18  *
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/.
27  */
28
29 #include <sys/types.h>
30 #include <sys/stat.h>
31 #include <fcntl.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include "mar_private.h"
35 #include "mar_cmdline.h"
36 #include "mar.h"
37
38 #ifdef XP_WIN
39 #include <winsock2.h>
40 #else
41 #include <netinet/in.h>
42 #include <unistd.h>
43 #endif
44
45 struct MarItemStack {
46   void *head;
47   uint32_t size_used;
48   uint32_t size_allocated;
49   uint32_t last_offset;
50 };
51
52 /**
53  * Push a new item onto the stack of items.  The stack is a single block
54  * of memory.
55  */
56 static int mar_push(struct MarItemStack *stack, uint32_t length, uint32_t flags,
57                     const char *name) {
58   int namelen;
59   uint32_t n_offset, n_length, n_flags;
60   uint32_t size;
61   char *data;
62   
63   namelen = strlen(name);
64   size = MAR_ITEM_SIZE(namelen);
65
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);
70     if (!stack->head)
71       return -1;
72     stack->size_allocated = size_needed;
73   }
74
75   data = (((char *) stack->head) + stack->size_used);
76
77   n_offset = htonl(stack->last_offset);
78   n_length = htonl(length);
79   n_flags = htonl(flags);
80
81   memcpy(data, &n_offset, sizeof(n_offset));
82   data += sizeof(n_offset);
83
84   memcpy(data, &n_length, sizeof(n_length));
85   data += sizeof(n_length);
86
87   memcpy(data, &n_flags, sizeof(n_flags));
88   data += sizeof(n_flags);
89
90   memcpy(data, name, namelen + 1);
91   
92   stack->size_used += size;
93   stack->last_offset += length;
94   return 0;
95 }
96
97 static int mar_concat_file(FILE *fp, const char *path) {
98   FILE *in;
99   char buf[BLOCKSIZE];
100   size_t len;
101   int rv = 0;
102
103   in = fopen(path, "rb");
104   if (!in)
105     return -1;
106
107   while ((len = fread(buf, 1, BLOCKSIZE, in)) > 0) {
108     if (fwrite(buf, len, 1, fp) != 1) {
109       rv = -1;
110       break;
111     }
112   }
113
114   fclose(in);
115   return rv;
116 }
117
118 /**
119  * Writes out the product information block to the specified file.
120  *
121  * @param fp           The opened MAR file being created.
122  * @param stack        A pointer to the MAR item stack being used to create 
123  *                     the MAR
124  * @param infoBlock    The product info block to store in the file.
125  * @return 0 on success.
126 */
127 static int
128 mar_concat_product_info_block(FILE *fp, 
129                               struct MarItemStack *stack,
130                               struct ProductInformationBlock *infoBlock)
131 {
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) {
137     return -1;
138   }
139  
140   /* The MAR channel name must be < 64 bytes per the spec */
141   if (strlen(infoBlock->MARChannelID) > PIB_MAX_MAR_CHANNEL_ID_SIZE) {
142     return -1;
143   }
144
145   /* The product version must be < 32 bytes per the spec */
146   if (strlen(infoBlock->productVersion) > PIB_MAX_PRODUCT_VERSION_SIZE) {
147     return -1;
148   }
149
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;
158   if (stack) {
159     stack->last_offset += infoBlockSize;
160   }
161
162   /* Write out the product info block size */
163   infoBlockSize = htonl(infoBlockSize);
164   if (fwrite(&infoBlockSize, 
165       sizeof(infoBlockSize), 1, fp) != 1) {
166     return -1;
167   }
168   infoBlockSize = ntohl(infoBlockSize);
169
170   /* Write out the product info block ID */
171   additionalBlockID = htonl(additionalBlockID);
172   if (fwrite(&additionalBlockID, 
173       sizeof(additionalBlockID), 1, fp) != 1) {
174     return -1;
175   }
176   additionalBlockID = ntohl(additionalBlockID);
177
178   /* Write out the channel name and NULL terminator */
179   if (fwrite(infoBlock->MARChannelID, 
180       strlen(infoBlock->MARChannelID) + 1, 1, fp) != 1) {
181     return -1;
182   }
183
184   /* Write out the product version string and NULL terminator */
185   if (fwrite(infoBlock->productVersion, 
186       strlen(infoBlock->productVersion) + 1, 1, fp) != 1) {
187     return -1;
188   }
189
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) {
197     return -1;
198   }
199   return 0;
200 }
201
202 /** 
203  * Refreshes the product information block with the new information.
204  * The input MAR must not be signed or the function call will fail.
205  * 
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
210 */
211 int
212 refresh_product_info_block(const char *path,
213                            struct ProductInformationBlock *infoBlock)
214 {
215   FILE *fp ;
216   int rv;
217   uint32_t numSignatures, additionalBlockSize, additionalBlockID,
218     offsetAdditionalBlocks, numAdditionalBlocks, i;
219   int additionalBlocks, hasSignatureBlock;
220   int64_t oldPos;
221
222   rv = get_mar_file_info(path, 
223                          &hasSignatureBlock,
224                          &numSignatures,
225                          &additionalBlocks,
226                          &offsetAdditionalBlocks,
227                          &numAdditionalBlocks);
228   if (rv) {
229     fprintf(stderr, "ERROR: Could not obtain MAR information.\n");
230     return -1;
231   }
232
233   if (hasSignatureBlock && numSignatures) {
234     fprintf(stderr, "ERROR: Cannot refresh a signed MAR\n");
235     return -1;
236   }
237
238   fp = fopen(path, "r+b");
239   if (!fp) {
240     fprintf(stderr, "ERROR: could not open target file: %s\n", path);
241     return -1;
242   }
243
244   if (fseeko(fp, offsetAdditionalBlocks, SEEK_SET)) {
245     fprintf(stderr, "ERROR: could not seek to additional blocks\n");
246     fclose(fp);
247     return -1;
248   }
249
250   for (i = 0; i < numAdditionalBlocks; ++i) {
251     /* Get the position of the start of this block */
252     oldPos = ftello(fp);
253
254     /* Read the additional block size */
255     if (fread(&additionalBlockSize, 
256               sizeof(additionalBlockSize), 
257               1, fp) != 1) {
258       return -1;
259     }
260     additionalBlockSize = ntohl(additionalBlockSize);
261
262     /* Read the additional block ID */
263     if (fread(&additionalBlockID, 
264               sizeof(additionalBlockID), 
265               1, fp) != 1) {
266       return -1;
267     }
268     additionalBlockID = ntohl(additionalBlockID);
269
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");
273         fclose(fp);
274         return -1;
275       }
276
277       if (mar_concat_product_info_block(fp, NULL, infoBlock)) {
278         fprintf(stderr, "Could not concat Product Information Block\n");
279         fclose(fp);
280         return -1;
281       }
282
283       fclose(fp);
284       return 0;
285     } else {
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");
289         fclose(fp);
290         return -1;
291       }
292     }
293   }
294
295   /* If we had a product info block we would have already returned */
296   fclose(fp);
297   fprintf(stderr, "ERROR: Could not refresh because block does not exist\n");
298   return -1;
299 }
300
301 /**
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.
310  */
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;
318   struct stat st;
319   FILE *fp;
320   int i, rv = -1;
321
322   memset(&stack, 0, sizeof(stack));
323
324   fp = fopen(dest, "wb");
325   if (!fp) {
326     fprintf(stderr, "ERROR: could not create target file: %s\n", dest);
327     return -1;
328   }
329
330   if (fwrite(MAR_ID, MAR_ID_SIZE, 1, fp) != 1)
331     goto failure;
332   if (fwrite(&offset_to_index, sizeof(uint32_t), 1, fp) != 1)
333     goto failure;
334
335   stack.last_offset = MAR_ID_SIZE + 
336                       sizeof(offset_to_index) +
337                       sizeof(numSignatures) + 
338                       sizeof(numAdditionalSections) +
339                       sizeof(sizeOfEntireMAR);
340
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) {
343     goto failure;
344   }
345
346   /* Write out the number of signatures, for now only at most 1 is supported */
347   numSignatures = 0;
348   if (fwrite(&numSignatures, sizeof(numSignatures), 1, fp) != 1) {
349     goto failure;
350   }
351
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) {
357     goto failure;
358   }
359   numAdditionalSections = ntohl(numAdditionalSections);
360
361   if (mar_concat_product_info_block(fp, &stack, infoBlock)) {
362     goto failure;
363   }
364
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]);
368       goto failure;
369     }
370
371     if (mar_push(&stack, st.st_size, st.st_mode & 0777, files[i]))
372       goto failure;
373
374     /* concatenate input file to archive */
375     if (mar_concat_file(fp, files[i]))
376       goto failure;
377   }
378
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)
382     goto failure;
383   if (fwrite(stack.head, stack.size_used, 1, fp) != 1)
384     goto failure;
385
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) {
389     goto failure;
390   }
391
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))
395     goto failure;
396   if (fwrite(&offset_to_index, sizeof(offset_to_index), 1, fp) != 1)
397     goto failure;
398   offset_to_index = ntohl(stack.last_offset);
399   
400   sizeOfEntireMAR = ((uint64_t)stack.last_offset) +
401                     stack.size_used +
402                     sizeof(size_of_index);
403   sizeOfEntireMAR = HOST_TO_NETWORK64(sizeOfEntireMAR);
404   if (fwrite(&sizeOfEntireMAR, sizeof(sizeOfEntireMAR), 1, fp) != 1)
405     goto failure;
406   sizeOfEntireMAR = NETWORK_TO_HOST64(sizeOfEntireMAR);
407
408   rv = 0;
409 failure: 
410   if (stack.head)
411     free(stack.head);
412   fclose(fp);
413   if (rv)
414     remove(dest);
415   return rv;
416 }