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
23extern "C" {
24#include "eficompress.h"
25}
26
27
28static 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
40namespace vboot_reference {
41
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
60  BmpBlockUtil::~BmpBlockUtil() {
61  }
62
63  void BmpBlockUtil::force_compression(uint32_t compression) {
64    compression_ = compression;
65    set_compression_ = true;
66  }
67
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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.
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
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