• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 //
5 // Utility for manipulating firmware screen block (BMPBLOCK) in GBB.
6 //
7 
8 #include <assert.h>
9 #include <errno.h>
10 #include <getopt.h>
11 #include <lzma.h>
12 #include <stdarg.h>
13 #include <stdint.h>
14 #include <stdio.h>
15 #include <stdlib.h>
16 #include <string.h>
17 #include <yaml.h>
18 
19 #include "bmpblk_utility.h"
20 #include "image_types.h"
21 #include "vboot_api.h"
22 
23 extern "C" {
24 #include "eficompress.h"
25 }
26 
27 
error(const char * format,...)28 static void error(const char *format, ...) {
29   va_list ap;
30   va_start(ap, format);
31   fprintf(stderr, "ERROR: ");
32   vfprintf(stderr, format, ap);
33   va_end(ap);
34   exit(1);
35 }
36 
37 ///////////////////////////////////////////////////////////////////////
38 // BmpBlock Utility implementation
39 
40 namespace vboot_reference {
41 
BmpBlockUtil(bool debug)42   BmpBlockUtil::BmpBlockUtil(bool debug) {
43     major_version_ = BMPBLOCK_MAJOR_VERSION;
44     minor_version_ = BMPBLOCK_MINOR_VERSION;
45     config_.config_filename.clear();
46     memset(&config_.header, '\0', BMPBLOCK_SIGNATURE_SIZE);
47     config_.images_map.clear();
48     config_.screens_map.clear();
49     config_.localizations.clear();
50     bmpblock_.clear();
51     set_compression_ = false;
52     compression_ = COMPRESS_NONE;
53     debug_ = debug;
54     render_hwid_ = true;
55     support_font_ = true;
56     got_font_ = false;
57     got_rtol_font_ = false;
58   }
59 
~BmpBlockUtil()60   BmpBlockUtil::~BmpBlockUtil() {
61   }
62 
force_compression(uint32_t compression)63   void BmpBlockUtil::force_compression(uint32_t compression) {
64     compression_ = compression;
65     set_compression_ = true;
66   }
67 
load_from_config(const char * filename)68   void BmpBlockUtil::load_from_config(const char *filename) {
69     load_yaml_config(filename);
70     fill_bmpblock_header();
71     load_all_image_files();
72   }
73 
load_yaml_config(const char * filename)74   void BmpBlockUtil::load_yaml_config(const char *filename) {
75     yaml_parser_t parser;
76 
77     config_.config_filename = filename;
78     config_.images_map.clear();
79     config_.screens_map.clear();
80     config_.localizations.clear();
81     config_.locale_names.clear();
82 
83     FILE *fp = fopen(filename, "rb");
84     if (!fp) {
85       perror(filename);
86       exit(errno);
87     }
88 
89     yaml_parser_initialize(&parser);
90     yaml_parser_set_input_file(&parser, fp);
91     parse_config(&parser);
92     yaml_parser_delete(&parser);
93     fclose(fp);
94 
95 
96     // TODO: Check the yaml file for self-consistency. Warn on any problems.
97     // All images should be used somewhere in the screens.
98     // All images referenced in the screens should be defined.
99     // All screens should be used somewhere in the localizations.
100     // All screens referenced in the localizations should be defined.
101     // The number of localizations should match the number of locale_index
102 
103     if (debug_) {
104       printf("%ld image_names\n", config_.image_names.size());
105       for (unsigned int i = 0; i < config_.image_names.size(); ++i) {
106         printf(" %d: \"%s\"\n", i, config_.image_names[i].c_str());
107       }
108       printf("%ld images_map\n", config_.images_map.size());
109       for (StrImageConfigMap::iterator it = config_.images_map.begin();
110            it != config_.images_map.end();
111            ++it) {
112         printf("  \"%s\": filename=\"%s\" offset=0x%x tag=%d fmt=%d\n",
113                it->first.c_str(),
114                it->second.filename.c_str(),
115                it->second.offset,
116                it->second.data.tag,
117                it->second.data.format);
118       }
119       printf("%ld screens_map\n", config_.screens_map.size());
120       for (StrScreenConfigMap::iterator it = config_.screens_map.begin();
121            it != config_.screens_map.end();
122            ++it) {
123         printf("  \"%s\":\n", it->first.c_str());
124         for (int k=0; k<MAX_IMAGE_IN_LAYOUT; k++) {
125           printf("    %d: \"%s\" (%d,%d) ofs=0x%x\n",
126                  k,
127                  it->second.image_names[k].c_str(),
128                  it->second.data.images[k].x,
129                  it->second.data.images[k].y,
130                  it->second.data.images[k].image_info_offset);
131         }
132       }
133     }
134   }
135 
expect_event(yaml_parser_t * parser,const yaml_event_type_e type)136   void BmpBlockUtil::expect_event(yaml_parser_t *parser,
137                                   const yaml_event_type_e type) {
138     yaml_event_t event;
139     yaml_parser_parse(parser, &event);
140     if (event.type != type) {
141       error("Syntax error.\n");
142     }
143     yaml_event_delete(&event);
144   }
145 
parse_config(yaml_parser_t * parser)146   void BmpBlockUtil::parse_config(yaml_parser_t *parser) {
147     expect_event(parser, YAML_STREAM_START_EVENT);
148     expect_event(parser, YAML_DOCUMENT_START_EVENT);
149     parse_first_layer(parser);
150     expect_event(parser, YAML_DOCUMENT_END_EVENT);
151     expect_event(parser, YAML_STREAM_END_EVENT);
152   }
153 
parse_first_layer(yaml_parser_t * parser)154   void BmpBlockUtil::parse_first_layer(yaml_parser_t *parser) {
155     yaml_event_t event;
156     string keyword;
157     expect_event(parser, YAML_MAPPING_START_EVENT);
158     for (;;) {
159       yaml_parser_parse(parser, &event);
160       switch (event.type) {
161       case YAML_SCALAR_EVENT:
162         keyword = (char*)event.data.scalar.value;
163         if (keyword == "bmpblock") {
164           parse_bmpblock(parser);
165         } else if (keyword == "compression") {
166           parse_compression(parser);
167         } else if (keyword == "images") {
168           parse_images(parser);
169         } else if (keyword == "screens") {
170           parse_screens(parser);
171         } else if (keyword == "localizations") {
172           parse_localizations(parser);
173         } else if (keyword == "locale_index") {
174           parse_locale_index(parser);
175         }
176         break;
177       case YAML_MAPPING_END_EVENT:
178         yaml_event_delete(&event);
179         return;
180       default:
181         error("Syntax error in parsing config file.\n");
182       }
183       yaml_event_delete(&event);
184     }
185   }
186 
parse_bmpblock(yaml_parser_t * parser)187   void BmpBlockUtil::parse_bmpblock(yaml_parser_t *parser) {
188     yaml_event_t event;
189     yaml_parser_parse(parser, &event);
190     if (event.type != YAML_SCALAR_EVENT) {
191       error("Syntax error in parsing bmpblock.\n");
192     }
193     string gotversion = (char*)event.data.scalar.value;
194     if (gotversion != "2.0") {
195       error("Unsupported version specified in config file (%s)\n",
196             gotversion.c_str());
197     }
198     yaml_event_delete(&event);
199   }
200 
parse_compression(yaml_parser_t * parser)201   void BmpBlockUtil::parse_compression(yaml_parser_t *parser) {
202     yaml_event_t event;
203     yaml_parser_parse(parser, &event);
204     if (event.type != YAML_SCALAR_EVENT) {
205       error("Syntax error in parsing bmpblock.\n");
206     }
207     char *comp_str = (char *)event.data.scalar.value;
208     char *e = 0;
209     uint32_t comp = (uint32_t)strtoul(comp_str, &e, 0);
210     if (!*comp_str || (e && *e) || comp >= MAX_COMPRESS) {
211       error("Invalid compression specified in config file (%d)\n", comp);
212     }
213     if (!set_compression_) {
214       compression_ = comp;
215     }
216     yaml_event_delete(&event);
217   }
218 
parse_images(yaml_parser_t * parser)219   void BmpBlockUtil::parse_images(yaml_parser_t *parser) {
220     yaml_event_t event;
221     string image_name, image_filename;
222     expect_event(parser, YAML_MAPPING_START_EVENT);
223     for (;;) {
224       yaml_parser_parse(parser, &event);
225       switch (event.type) {
226       case YAML_SCALAR_EVENT:
227         image_name = (char*)event.data.scalar.value;
228         yaml_event_delete(&event);
229         yaml_parser_parse(parser, &event);
230         if (event.type != YAML_SCALAR_EVENT) {
231           error("Syntax error in parsing images.\n");
232         }
233         image_filename = (char*)event.data.scalar.value;
234         config_.image_names.push_back(image_name);
235         config_.images_map[image_name] = ImageConfig();
236         config_.images_map[image_name].filename = image_filename;
237         if (image_name == RENDER_HWID) {
238           got_font_ = true;
239         }
240         if (image_name == RENDER_HWID_RTOL) {
241           got_rtol_font_ = true;
242         }
243         break;
244       case YAML_MAPPING_END_EVENT:
245         yaml_event_delete(&event);
246         return;
247       default:
248         error("Syntax error in parsing images.\n");
249       }
250       yaml_event_delete(&event);
251     }
252   }
253 
parse_layout(yaml_parser_t * parser,ScreenConfig & screen)254   void BmpBlockUtil::parse_layout(yaml_parser_t *parser, ScreenConfig &screen) {
255     yaml_event_t event;
256     int depth = 0, index1 = 0, index2 = 0;
257     expect_event(parser, YAML_SEQUENCE_START_EVENT);
258     for (;;) {
259       yaml_parser_parse(parser, &event);
260       switch (event.type) {
261       case YAML_SEQUENCE_START_EVENT:
262         depth++;
263         break;
264       case YAML_SCALAR_EVENT:
265         switch (index2) {
266         case 0:
267           screen.data.images[index1].x = atoi((char*)event.data.scalar.value);
268           break;
269         case 1:
270           screen.data.images[index1].y = atoi((char*)event.data.scalar.value);
271           break;
272         case 2:
273           screen.image_names[index1] = (char*)event.data.scalar.value;
274           // Detect the special case where we're rendering the HWID string
275           // instead of displaying a bitmap.  The image name may not
276           // exist in the list of images (v1.1), but we will still need an
277           // ImageInfo struct to remember where to draw the text.
278           // Note that v1.2 requires that the image name DOES exist, because
279           // the corresponding file is used to hold the font glpyhs.
280           if (render_hwid_) {
281             if (screen.image_names[index1] == RENDER_HWID) {
282               config_.images_map[RENDER_HWID].data.tag = TAG_HWID;
283               if (support_font_ && !got_font_)
284                 error("Font required in 'image:' section for %s\n",
285                       RENDER_HWID);
286             } else if (screen.image_names[index1] == RENDER_HWID_RTOL) {
287               config_.images_map[RENDER_HWID_RTOL].data.tag = TAG_HWID_RTOL;
288               if (support_font_ && !got_rtol_font_)
289                 error("Font required in 'image:' section for %s\n",
290                       RENDER_HWID_RTOL);
291             }
292           }
293           break;
294         default:
295           error("Syntax error in parsing layout\n");
296         }
297         index2++;
298         break;
299       case YAML_SEQUENCE_END_EVENT:
300         if (depth == 1) {
301           index1++;
302           index2 = 0;
303         } else if (depth == 0) {
304           yaml_event_delete(&event);
305           return;
306         }
307         depth--;
308         break;
309       default:
310         error("Syntax error in paring layout.\n");
311       }
312       yaml_event_delete(&event);
313     }
314   }
315 
parse_screens(yaml_parser_t * parser)316   void BmpBlockUtil::parse_screens(yaml_parser_t *parser) {
317     yaml_event_t event;
318     string screen_name;
319     expect_event(parser, YAML_MAPPING_START_EVENT);
320     for (;;) {
321       yaml_parser_parse(parser, &event);
322       switch (event.type) {
323       case YAML_SCALAR_EVENT:
324         screen_name = (char*)event.data.scalar.value;
325         config_.screens_map[screen_name] = ScreenConfig();
326         parse_layout(parser, config_.screens_map[screen_name]);
327         break;
328       case YAML_MAPPING_END_EVENT:
329         yaml_event_delete(&event);
330         return;
331       default:
332         error("Syntax error in parsing screens.\n");
333       }
334       yaml_event_delete(&event);
335     }
336   }
337 
parse_localizations(yaml_parser_t * parser)338   void BmpBlockUtil::parse_localizations(yaml_parser_t *parser) {
339     yaml_event_t event;
340     int depth = 0, index = 0;
341     expect_event(parser, YAML_SEQUENCE_START_EVENT);
342     for (;;) {
343       yaml_parser_parse(parser, &event);
344       switch (event.type) {
345       case YAML_SEQUENCE_START_EVENT:
346         config_.localizations.push_back(vector<string>());
347         depth++;
348         break;
349       case YAML_SCALAR_EVENT:
350         config_.localizations[index].push_back((char*)event.data.scalar.value);
351         break;
352       case YAML_SEQUENCE_END_EVENT:
353         if (depth == 1) {
354           index++;
355         } else if (depth == 0) {
356           yaml_event_delete(&event);
357           return;
358         }
359         depth--;
360         break;
361       default:
362         error("Syntax error in parsing localizations.\n");
363       }
364       yaml_event_delete(&event);
365     }
366   }
367 
parse_locale_index(yaml_parser_t * parser)368   void BmpBlockUtil::parse_locale_index(yaml_parser_t *parser) {
369     yaml_event_t event;
370     expect_event(parser, YAML_SEQUENCE_START_EVENT);
371     for (;;) {
372       yaml_parser_parse(parser, &event);
373       switch (event.type) {
374       case YAML_SCALAR_EVENT:
375         config_.locale_names.append((char*)event.data.scalar.value);
376         config_.locale_names.append(1, (char)'\0'); // '\0' to delimit
377         break;
378       case YAML_SEQUENCE_END_EVENT:
379         yaml_event_delete(&event);
380         config_.locale_names.append(1, (char)'\0'); // double '\0' to terminate
381         return;
382       default:
383         error("Syntax error in parsing localizations.\n");
384       }
385     }
386   }
387 
load_all_image_files()388   void BmpBlockUtil::load_all_image_files() {
389     for (unsigned int i = 0; i < config_.image_names.size(); i++) {
390       StrImageConfigMap::iterator it =
391         config_.images_map.find(config_.image_names[i]);
392       if (debug_) {
393         printf("loading image \"%s\" from \"%s\"\n",
394                config_.image_names[i].c_str(),
395                it->second.filename.c_str());
396       }
397       const string &content = read_image_file(it->second.filename.c_str());
398       it->second.raw_content = content;
399       it->second.data.original_size = content.size();
400       it->second.data.format =
401         identify_image_type(content.c_str(),
402                             (uint32_t)content.size(), &it->second.data);
403       if (FORMAT_INVALID == it->second.data.format) {
404         error("Unsupported image format in %s\n", it->second.filename.c_str());
405       }
406       switch(compression_) {
407       case COMPRESS_NONE:
408         it->second.data.compression = compression_;
409         it->second.compressed_content = content;
410         it->second.data.compressed_size = content.size();
411         break;
412       case COMPRESS_EFIv1:
413       {
414         // The content will always compress smaller (so sez the docs).
415         uint32_t tmpsize = content.size();
416         uint8_t *tmpbuf = (uint8_t *)malloc(tmpsize);
417         // The size of the compressed content is also returned.
418         if (EFI_SUCCESS != EfiCompress((uint8_t *)content.c_str(), tmpsize,
419                                        tmpbuf, &tmpsize)) {
420           error("Unable to compress!\n");
421         }
422         it->second.data.compression = compression_;
423         it->second.compressed_content.assign((const char *)tmpbuf, tmpsize);
424         it->second.data.compressed_size = tmpsize;
425         free(tmpbuf);
426       }
427       break;
428       case COMPRESS_LZMA1:
429       {
430         // Calculate the worst case of buffer size.
431         uint32_t tmpsize = lzma_stream_buffer_bound(content.size());
432         uint8_t *tmpbuf = (uint8_t *)malloc(tmpsize);
433         lzma_stream stream = LZMA_STREAM_INIT;
434         lzma_options_lzma options;
435         lzma_ret result;
436 
437         lzma_lzma_preset(&options, 9);
438         result = lzma_alone_encoder(&stream, &options);
439         if (result != LZMA_OK) {
440           error("Unable to initialize easy encoder (error: %d)!\n", result);
441         }
442 
443         stream.next_in = (uint8_t *)content.data();
444         stream.avail_in = content.size();
445         stream.next_out = tmpbuf;
446         stream.avail_out = tmpsize;
447         result = lzma_code(&stream, LZMA_FINISH);
448         if (result != LZMA_STREAM_END) {
449           error("Unable to encode data (error: %d)!\n", result);
450         }
451 
452         it->second.data.compression = compression_;
453         it->second.compressed_content.assign((const char *)tmpbuf,
454                                              tmpsize - stream.avail_out);
455         it->second.data.compressed_size = tmpsize - stream.avail_out;
456         lzma_end(&stream);
457         free(tmpbuf);
458       }
459       break;
460       default:
461         error("Unsupported compression method attempted.\n");
462       }
463     }
464   }
465 
read_image_file(const char * filename)466   const string BmpBlockUtil::read_image_file(const char *filename) {
467     string content;
468     vector<char> buffer;
469 
470     FILE *fp = fopen(filename, "rb");
471     if (!fp) {
472       perror(filename);
473       exit(errno);
474     }
475 
476     if (fseek(fp, 0, SEEK_END) == 0) {
477       buffer.resize(ftell(fp));
478       rewind(fp);
479     }
480 
481     if (!buffer.empty()) {
482       if(fread(&buffer[0], buffer.size(), 1, fp) != 1) {
483         perror(filename);
484         buffer.clear();
485       } else {
486         content.assign(buffer.begin(), buffer.end());
487       }
488     }
489 
490     fclose(fp);
491     return content;
492   }
493 
fill_bmpblock_header()494   void BmpBlockUtil::fill_bmpblock_header() {
495     memset(&config_.header, '\0', sizeof(config_.header));
496     memcpy(&config_.header.signature, BMPBLOCK_SIGNATURE,
497            BMPBLOCK_SIGNATURE_SIZE);
498     config_.header.major_version = major_version_;
499     config_.header.minor_version = minor_version_;
500     config_.header.number_of_localizations = config_.localizations.size();
501     config_.header.number_of_screenlayouts = config_.localizations[0].size();
502     // NOTE: this is part of the yaml consistency check
503     for (unsigned int i = 1; i < config_.localizations.size(); ++i) {
504       assert(config_.header.number_of_screenlayouts ==
505              config_.localizations[i].size());
506     }
507     config_.header.number_of_imageinfos = config_.images_map.size();
508     config_.header.locale_string_offset = 0; // Filled by pack_bmpblock()
509   }
510 
pack_bmpblock()511   void BmpBlockUtil::pack_bmpblock() {
512     bmpblock_.clear();
513 
514     /* Compute the ImageInfo offsets from start of BMPBLOCK. */
515     uint32_t current_offset = sizeof(BmpBlockHeader) +
516       sizeof(ScreenLayout) * (config_.header.number_of_localizations *
517                               config_.header.number_of_screenlayouts);
518     for (StrImageConfigMap::iterator it = config_.images_map.begin();
519          it != config_.images_map.end();
520          ++it) {
521       it->second.offset = current_offset;
522       if (debug_)
523         printf("  \"%s\": filename=\"%s\" offset=0x%x tag=%d fmt=%d\n",
524                it->first.c_str(),
525                it->second.filename.c_str(),
526                it->second.offset,
527                it->second.data.tag,
528                it->second.data.format);
529       current_offset += sizeof(ImageInfo) +
530         it->second.data.compressed_size;
531       /* Make it 4-byte aligned. */
532       if ((current_offset & 3) > 0) {
533         current_offset = (current_offset & ~3) + 4;
534       }
535     }
536     /* And leave room for the locale_index string */
537     if (config_.locale_names.size()) {
538       config_.header.locale_string_offset = current_offset;
539       current_offset += config_.locale_names.size();
540     }
541 
542     bmpblock_.resize(current_offset);
543 
544     /* Fill BmpBlockHeader struct. */
545     string::iterator current_filled = bmpblock_.begin();
546     std::copy(reinterpret_cast<char*>(&config_.header),
547               reinterpret_cast<char*>(&config_.header + 1),
548               current_filled);
549     current_filled += sizeof(config_.header);
550     current_offset = sizeof(config_.header);
551 
552     /* Fill all ScreenLayout structs. */
553     for (unsigned int i = 0; i < config_.localizations.size(); ++i) {
554       for (unsigned int j = 0; j < config_.localizations[i].size(); ++j) {
555         ScreenConfig &screen = config_.screens_map[config_.localizations[i][j]];
556         for (unsigned int k = 0;
557              k < MAX_IMAGE_IN_LAYOUT && !screen.image_names[k].empty();
558              ++k) {
559           if (config_.images_map.find(screen.image_names[k]) ==
560               config_.images_map.end()) {
561             error("Invalid image name \"%s\"\n", screen.image_names[k].c_str());
562           }
563           if (debug_)
564             printf("i=%d j=%d k=%d=\"%s\" (%d,%d) ofs=%x\n", i,j,k,
565                    screen.image_names[k].c_str(),
566                    screen.data.images[k].x, screen.data.images[k].y,
567                    config_.images_map[screen.image_names[k]].offset
568               );
569           screen.data.images[k].image_info_offset =
570             config_.images_map[screen.image_names[k]].offset;
571         }
572         std::copy(reinterpret_cast<char*>(&screen.data),
573                   reinterpret_cast<char*>(&screen.data + 1),
574                   current_filled);
575         current_filled += sizeof(screen.data);
576         if (debug_)
577           printf("S: current offset is 0x%08x\n", current_offset);
578         current_offset += sizeof(screen.data);
579       }
580     }
581 
582     /* Fill all ImageInfo structs and image contents. */
583     for (StrImageConfigMap::iterator it = config_.images_map.begin();
584          it != config_.images_map.end();
585          ++it) {
586       current_filled = bmpblock_.begin() + it->second.offset;
587       current_offset = it->second.offset;
588       if (debug_)
589         printf("I0: current offset is 0x%08x\n", current_offset);
590       std::copy(reinterpret_cast<char*>(&it->second.data),
591                 reinterpret_cast<char*>(&it->second.data + 1),
592                 current_filled);
593       current_filled += sizeof(it->second.data);
594       current_offset += sizeof(it->second.data);
595       if (debug_)
596         printf("I1: current offset is 0x%08x (len %ld)\n",
597                current_offset, it->second.compressed_content.length());
598       std::copy(it->second.compressed_content.begin(),
599                 it->second.compressed_content.end(),
600                 current_filled);
601     }
602 
603     /* Fill in locale_names. */
604     if (config_.header.locale_string_offset) {
605       current_offset = config_.header.locale_string_offset;
606       current_filled = bmpblock_.begin() + current_offset;
607       if (debug_)
608         printf("locale_names: offset 0x%08x (len %ld)\n",
609                current_offset, config_.locale_names.size());
610       std::copy(config_.locale_names.begin(),
611                 config_.locale_names.end(),
612                 current_filled);
613     }
614   }
615 
write_to_bmpblock(const char * filename)616   void BmpBlockUtil::write_to_bmpblock(const char *filename) {
617     assert(!bmpblock_.empty());
618 
619     FILE *fp = fopen(filename, "wb");
620     if (!fp) {
621       perror(filename);
622       exit(errno);
623     }
624 
625     int r = fwrite(bmpblock_.c_str(), bmpblock_.size(), 1, fp);
626     fclose(fp);
627     if (r != 1) {
628       perror(filename);
629       exit(errno);
630     }
631   }
632 
633 }  // namespace vboot_reference
634 
635 #ifndef FOR_LIBRARY
636 
637   //////////////////////////////////////////////////////////////////////////////
638   // Command line utilities.
639 
640   extern "C" {
641 #include "bmpblk_util.h"
642   }
643 
644   using vboot_reference::BmpBlockUtil;
645 
646   // utility function: provide usage of this utility and exit.
usagehelp_exit(const char * prog_name)647   static void usagehelp_exit(const char *prog_name) {
648     printf(
649       "\n"
650       "To create a new BMPBLOCK file using config from YAML file:\n"
651       "\n"
652       "  %s [-z NUM] -c YAML BMPBLOCK\n"
653       "\n"
654       "    -z NUM  = compression algorithm to use\n"
655       "              0 = none\n"
656       "              1 = EFIv1\n"
657       "              2 = LZMA1\n"
658       "\n", prog_name);
659     printf(
660       "To display the contents of a BMPBLOCK:\n"
661       "\n"
662       "  %s [-y] BMPBLOCK\n"
663       "\n"
664       "    -y  = display as yaml\n"
665       "\n", prog_name);
666     printf(
667       "To unpack a BMPBLOCK file:\n"
668       "\n"
669       "  %s -x [-d DIR] [-f] BMPBLOCK\n"
670       "\n"
671       "    -d DIR  = directory to use (default '.')\n"
672       "    -f      = force overwriting existing files\n"
673       "\n", prog_name);
674     exit(1);
675   }
676 
677   ///////////////////////////////////////////////////////////////////////
678   // main
679 
main(int argc,char * argv[])680   int main(int argc, char *argv[]) {
681 
682     const char *prog_name = strrchr(argv[0], '/');
683     if (prog_name)
684       prog_name++;
685     else
686       prog_name = argv[0];
687 
688     int overwrite = 0, extract_mode = 0;
689     int compression = 0;
690     int set_compression = 0;
691     const char *config_fn = 0, *bmpblock_fn = 0, *extract_dir = ".";
692     int show_as_yaml = 0;
693     bool debug = false;
694 
695     int opt;
696     opterr = 0;                           // quiet
697     int errorcnt = 0;
698     char *e = 0;
699     while ((opt = getopt(argc, argv, ":c:xz:fd:yD")) != -1) {
700       switch (opt) {
701       case 'c':
702         config_fn = optarg;
703         break;
704       case 'x':
705         extract_mode = 1;
706         break;
707       case 'y':
708         show_as_yaml = 1;
709         break;
710       case 'z':
711         compression = (int)strtoul(optarg, &e, 0);
712         if (!*optarg || (e && *e)) {
713           fprintf(stderr, "%s: invalid argument to -%c: \"%s\"\n",
714                   prog_name, opt, optarg);
715           errorcnt++;
716         }
717         if (compression >= MAX_COMPRESS) {
718           fprintf(stderr, "%s: compression type must be less than %d\n",
719                   prog_name, MAX_COMPRESS);
720           errorcnt++;
721         }
722         set_compression = 1;
723         break;
724       case 'f':
725         overwrite = 1;
726         break;
727       case 'd':
728         extract_dir= optarg;
729         break;
730       case 'D':
731         debug = true;
732         break;
733       case ':':
734         fprintf(stderr, "%s: missing argument to -%c\n",
735                 prog_name, optopt);
736         errorcnt++;
737         break;
738       default:
739         fprintf(stderr, "%s: unrecognized switch: -%c\n",
740                 prog_name, optopt);
741         errorcnt++;
742         break;
743       }
744     }
745     argc -= optind;
746     argv += optind;
747 
748     if (argc >= 1) {
749       bmpblock_fn = argv[0];
750     } else {
751       fprintf(stderr, "%s: missing BMPBLOCK name\n", prog_name);
752       errorcnt++;
753     }
754 
755     if (errorcnt)
756       usagehelp_exit(prog_name);
757 
758     BmpBlockUtil util(debug);
759 
760     if (config_fn) {
761       if (set_compression)
762         util.force_compression(compression);
763       util.load_from_config(config_fn);
764       util.pack_bmpblock();
765       util.write_to_bmpblock(bmpblock_fn);
766     }
767 
768     else if (extract_mode) {
769       return dump_bmpblock(bmpblock_fn, 1, extract_dir, overwrite);
770     } else {
771       return dump_bmpblock(bmpblock_fn, show_as_yaml, 0, 0);
772     }
773 
774     return 0;
775   }
776 
777 #endif // FOR_LIBRARY
778