// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// Utility for manipulating firmware screen block (BMPBLOCK) in GBB.
//

#include <assert.h>
#include <errno.h>
#include <getopt.h>
#include <lzma.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <yaml.h>

#include "bmpblk_utility.h"
#include "image_types.h"
#include "vboot_api.h"

extern "C" {
#include "eficompress.h"
}


static void error(const char *format, ...) {
  va_list ap;
  va_start(ap, format);
  fprintf(stderr, "ERROR: ");
  vfprintf(stderr, format, ap);
  va_end(ap);
  exit(1);
}

///////////////////////////////////////////////////////////////////////
// BmpBlock Utility implementation

namespace vboot_reference {

  BmpBlockUtil::BmpBlockUtil(bool debug) {
    major_version_ = BMPBLOCK_MAJOR_VERSION;
    minor_version_ = BMPBLOCK_MINOR_VERSION;
    config_.config_filename.clear();
    memset(&config_.header, '\0', BMPBLOCK_SIGNATURE_SIZE);
    config_.images_map.clear();
    config_.screens_map.clear();
    config_.localizations.clear();
    bmpblock_.clear();
    set_compression_ = false;
    compression_ = COMPRESS_NONE;
    debug_ = debug;
    render_hwid_ = true;
    support_font_ = true;
    got_font_ = false;
    got_rtol_font_ = false;
  }

  BmpBlockUtil::~BmpBlockUtil() {
  }

  void BmpBlockUtil::force_compression(uint32_t compression) {
    compression_ = compression;
    set_compression_ = true;
  }

  void BmpBlockUtil::load_from_config(const char *filename) {
    load_yaml_config(filename);
    fill_bmpblock_header();
    load_all_image_files();
  }

  void BmpBlockUtil::load_yaml_config(const char *filename) {
    yaml_parser_t parser;

    config_.config_filename = filename;
    config_.images_map.clear();
    config_.screens_map.clear();
    config_.localizations.clear();
    config_.locale_names.clear();

    FILE *fp = fopen(filename, "rb");
    if (!fp) {
      perror(filename);
      exit(errno);
    }

    yaml_parser_initialize(&parser);
    yaml_parser_set_input_file(&parser, fp);
    parse_config(&parser);
    yaml_parser_delete(&parser);
    fclose(fp);


    // TODO: Check the yaml file for self-consistency. Warn on any problems.
    // All images should be used somewhere in the screens.
    // All images referenced in the screens should be defined.
    // All screens should be used somewhere in the localizations.
    // All screens referenced in the localizations should be defined.
    // The number of localizations should match the number of locale_index

    if (debug_) {
      printf("%zd image_names\n", config_.image_names.size());
      for (unsigned int i = 0; i < config_.image_names.size(); ++i) {
        printf(" %d: \"%s\"\n", i, config_.image_names[i].c_str());
      }
      printf("%zd images_map\n", config_.images_map.size());
      for (StrImageConfigMap::iterator it = config_.images_map.begin();
           it != config_.images_map.end();
           ++it) {
        printf("  \"%s\": filename=\"%s\" offset=0x%x tag=%d fmt=%d\n",
               it->first.c_str(),
               it->second.filename.c_str(),
               it->second.offset,
               it->second.data.tag,
               it->second.data.format);
      }
      printf("%zd screens_map\n", config_.screens_map.size());
      for (StrScreenConfigMap::iterator it = config_.screens_map.begin();
           it != config_.screens_map.end();
           ++it) {
        printf("  \"%s\":\n", it->first.c_str());
        for (int k=0; k<MAX_IMAGE_IN_LAYOUT; k++) {
          printf("    %d: \"%s\" (%d,%d) ofs=0x%x\n",
                 k,
                 it->second.image_names[k].c_str(),
                 it->second.data.images[k].x,
                 it->second.data.images[k].y,
                 it->second.data.images[k].image_info_offset);
        }
      }
    }
  }

  void BmpBlockUtil::expect_event(yaml_parser_t *parser,
                                  const yaml_event_type_e type) {
    yaml_event_t event;
    yaml_parser_parse(parser, &event);
    if (event.type != type) {
      error("Syntax error.\n");
    }
    yaml_event_delete(&event);
  }

  void BmpBlockUtil::parse_config(yaml_parser_t *parser) {
    expect_event(parser, YAML_STREAM_START_EVENT);
    expect_event(parser, YAML_DOCUMENT_START_EVENT);
    parse_first_layer(parser);
    expect_event(parser, YAML_DOCUMENT_END_EVENT);
    expect_event(parser, YAML_STREAM_END_EVENT);
  }

  void BmpBlockUtil::parse_first_layer(yaml_parser_t *parser) {
    yaml_event_t event;
    string keyword;
    expect_event(parser, YAML_MAPPING_START_EVENT);
    for (;;) {
      yaml_parser_parse(parser, &event);
      switch (event.type) {
      case YAML_SCALAR_EVENT:
        keyword = (char*)event.data.scalar.value;
        if (keyword == "bmpblock") {
          parse_bmpblock(parser);
        } else if (keyword == "compression") {
          parse_compression(parser);
        } else if (keyword == "images") {
          parse_images(parser);
        } else if (keyword == "screens") {
          parse_screens(parser);
        } else if (keyword == "localizations") {
          parse_localizations(parser);
        } else if (keyword == "locale_index") {
          parse_locale_index(parser);
        }
        break;
      case YAML_MAPPING_END_EVENT:
        yaml_event_delete(&event);
        return;
      default:
        error("Syntax error in parsing config file.\n");
      }
      yaml_event_delete(&event);
    }
  }

  void BmpBlockUtil::parse_bmpblock(yaml_parser_t *parser) {
    yaml_event_t event;
    yaml_parser_parse(parser, &event);
    if (event.type != YAML_SCALAR_EVENT) {
      error("Syntax error in parsing bmpblock.\n");
    }
    string gotversion = (char*)event.data.scalar.value;
    if (gotversion != "2.0") {
      error("Unsupported version specified in config file (%s)\n",
            gotversion.c_str());
    }
    yaml_event_delete(&event);
  }

  void BmpBlockUtil::parse_compression(yaml_parser_t *parser) {
    yaml_event_t event;
    yaml_parser_parse(parser, &event);
    if (event.type != YAML_SCALAR_EVENT) {
      error("Syntax error in parsing bmpblock.\n");
    }
    char *comp_str = (char *)event.data.scalar.value;
    char *e = 0;
    uint32_t comp = (uint32_t)strtoul(comp_str, &e, 0);
    if (!*comp_str || (e && *e) || comp >= MAX_COMPRESS) {
      error("Invalid compression specified in config file (%d)\n", comp);
    }
    if (!set_compression_) {
      compression_ = comp;
    }
    yaml_event_delete(&event);
  }

  void BmpBlockUtil::parse_images(yaml_parser_t *parser) {
    yaml_event_t event;
    string image_name, image_filename;
    expect_event(parser, YAML_MAPPING_START_EVENT);
    for (;;) {
      yaml_parser_parse(parser, &event);
      switch (event.type) {
      case YAML_SCALAR_EVENT:
        image_name = (char*)event.data.scalar.value;
        yaml_event_delete(&event);
        yaml_parser_parse(parser, &event);
        if (event.type != YAML_SCALAR_EVENT) {
          error("Syntax error in parsing images.\n");
        }
        image_filename = (char*)event.data.scalar.value;
        config_.image_names.push_back(image_name);
        config_.images_map[image_name] = ImageConfig();
        config_.images_map[image_name].filename = image_filename;
        if (image_name == RENDER_HWID) {
          got_font_ = true;
        }
        if (image_name == RENDER_HWID_RTOL) {
          got_rtol_font_ = true;
        }
        break;
      case YAML_MAPPING_END_EVENT:
        yaml_event_delete(&event);
        return;
      default:
        error("Syntax error in parsing images.\n");
      }
      yaml_event_delete(&event);
    }
  }

  void BmpBlockUtil::parse_layout(yaml_parser_t *parser, ScreenConfig &screen) {
    yaml_event_t event;
    int depth = 0, index1 = 0, index2 = 0;
    expect_event(parser, YAML_SEQUENCE_START_EVENT);
    for (;;) {
      yaml_parser_parse(parser, &event);
      switch (event.type) {
      case YAML_SEQUENCE_START_EVENT:
        depth++;
        break;
      case YAML_SCALAR_EVENT:
        switch (index2) {
        case 0:
          screen.data.images[index1].x = atoi((char*)event.data.scalar.value);
          break;
        case 1:
          screen.data.images[index1].y = atoi((char*)event.data.scalar.value);
          break;
        case 2:
          screen.image_names[index1] = (char*)event.data.scalar.value;
          // Detect the special case where we're rendering the HWID string
          // instead of displaying a bitmap.  The image name may not
          // exist in the list of images (v1.1), but we will still need an
          // ImageInfo struct to remember where to draw the text.
          // Note that v1.2 requires that the image name DOES exist, because
          // the corresponding file is used to hold the font glpyhs.
          if (render_hwid_) {
            if (screen.image_names[index1] == RENDER_HWID) {
              config_.images_map[RENDER_HWID].data.tag = TAG_HWID;
              if (support_font_ && !got_font_)
                error("Font required in 'image:' section for %s\n",
                      RENDER_HWID);
            } else if (screen.image_names[index1] == RENDER_HWID_RTOL) {
              config_.images_map[RENDER_HWID_RTOL].data.tag = TAG_HWID_RTOL;
              if (support_font_ && !got_rtol_font_)
                error("Font required in 'image:' section for %s\n",
                      RENDER_HWID_RTOL);
            }
          }
          break;
        default:
          error("Syntax error in parsing layout\n");
        }
        index2++;
        break;
      case YAML_SEQUENCE_END_EVENT:
        if (depth == 1) {
          index1++;
          index2 = 0;
        } else if (depth == 0) {
          yaml_event_delete(&event);
          return;
        }
        depth--;
        break;
      default:
        error("Syntax error in paring layout.\n");
      }
      yaml_event_delete(&event);
    }
  }

  void BmpBlockUtil::parse_screens(yaml_parser_t *parser) {
    yaml_event_t event;
    string screen_name;
    expect_event(parser, YAML_MAPPING_START_EVENT);
    for (;;) {
      yaml_parser_parse(parser, &event);
      switch (event.type) {
      case YAML_SCALAR_EVENT:
        screen_name = (char*)event.data.scalar.value;
        config_.screens_map[screen_name] = ScreenConfig();
        parse_layout(parser, config_.screens_map[screen_name]);
        break;
      case YAML_MAPPING_END_EVENT:
        yaml_event_delete(&event);
        return;
      default:
        error("Syntax error in parsing screens.\n");
      }
      yaml_event_delete(&event);
    }
  }

  void BmpBlockUtil::parse_localizations(yaml_parser_t *parser) {
    yaml_event_t event;
    int depth = 0, index = 0;
    expect_event(parser, YAML_SEQUENCE_START_EVENT);
    for (;;) {
      yaml_parser_parse(parser, &event);
      switch (event.type) {
      case YAML_SEQUENCE_START_EVENT:
        config_.localizations.push_back(vector<string>());
        depth++;
        break;
      case YAML_SCALAR_EVENT:
        config_.localizations[index].push_back((char*)event.data.scalar.value);
        break;
      case YAML_SEQUENCE_END_EVENT:
        if (depth == 1) {
          index++;
        } else if (depth == 0) {
          yaml_event_delete(&event);
          return;
        }
        depth--;
        break;
      default:
        error("Syntax error in parsing localizations.\n");
      }
      yaml_event_delete(&event);
    }
  }

  void BmpBlockUtil::parse_locale_index(yaml_parser_t *parser) {
    yaml_event_t event;
    expect_event(parser, YAML_SEQUENCE_START_EVENT);
    for (;;) {
      yaml_parser_parse(parser, &event);
      switch (event.type) {
      case YAML_SCALAR_EVENT:
        config_.locale_names.append((char*)event.data.scalar.value);
        config_.locale_names.append(1, (char)'\0'); // '\0' to delimit
        break;
      case YAML_SEQUENCE_END_EVENT:
        yaml_event_delete(&event);
        config_.locale_names.append(1, (char)'\0'); // double '\0' to terminate
        return;
      default:
        error("Syntax error in parsing localizations.\n");
      }
    }
  }

  void BmpBlockUtil::load_all_image_files() {
    for (unsigned int i = 0; i < config_.image_names.size(); i++) {
      StrImageConfigMap::iterator it =
        config_.images_map.find(config_.image_names[i]);
      if (debug_) {
        printf("loading image \"%s\" from \"%s\"\n",
               config_.image_names[i].c_str(),
               it->second.filename.c_str());
      }
      const string &content = read_image_file(it->second.filename.c_str());
      it->second.raw_content = content;
      it->second.data.original_size = content.size();
      it->second.data.format =
        identify_image_type(content.c_str(),
                            (uint32_t)content.size(), &it->second.data);
      if (FORMAT_INVALID == it->second.data.format) {
        error("Unsupported image format in %s\n", it->second.filename.c_str());
      }
      switch(compression_) {
      case COMPRESS_NONE:
        it->second.data.compression = compression_;
        it->second.compressed_content = content;
        it->second.data.compressed_size = content.size();
        break;
      case COMPRESS_EFIv1:
      {
        // The content will always compress smaller (so sez the docs).
        uint32_t tmpsize = content.size();
        uint8_t *tmpbuf = (uint8_t *)malloc(tmpsize);
        // The size of the compressed content is also returned.
        if (EFI_SUCCESS != EfiCompress((uint8_t *)content.c_str(), tmpsize,
                                       tmpbuf, &tmpsize)) {
          error("Unable to compress!\n");
        }
        it->second.data.compression = compression_;
        it->second.compressed_content.assign((const char *)tmpbuf, tmpsize);
        it->second.data.compressed_size = tmpsize;
        free(tmpbuf);
      }
      break;
      case COMPRESS_LZMA1:
      {
        // Calculate the worst case of buffer size.
        uint32_t tmpsize = lzma_stream_buffer_bound(content.size());
        uint8_t *tmpbuf = (uint8_t *)malloc(tmpsize);
        lzma_stream stream = LZMA_STREAM_INIT;
        lzma_options_lzma options;
        lzma_ret result;

        lzma_lzma_preset(&options, 9);
        result = lzma_alone_encoder(&stream, &options);
        if (result != LZMA_OK) {
          error("Unable to initialize easy encoder (error: %d)!\n", result);
        }

        stream.next_in = (uint8_t *)content.data();
        stream.avail_in = content.size();
        stream.next_out = tmpbuf;
        stream.avail_out = tmpsize;
        result = lzma_code(&stream, LZMA_FINISH);
        if (result != LZMA_STREAM_END) {
          error("Unable to encode data (error: %d)!\n", result);
        }

        it->second.data.compression = compression_;
        it->second.compressed_content.assign((const char *)tmpbuf,
                                             tmpsize - stream.avail_out);
        it->second.data.compressed_size = tmpsize - stream.avail_out;
        lzma_end(&stream);
        free(tmpbuf);
      }
      break;
      default:
        error("Unsupported compression method attempted.\n");
      }
    }
  }

  const string BmpBlockUtil::read_image_file(const char *filename) {
    string content;
    vector<char> buffer;

    FILE *fp = fopen(filename, "rb");
    if (!fp) {
      perror(filename);
      exit(errno);
    }

    if (fseek(fp, 0, SEEK_END) == 0) {
      buffer.resize(ftell(fp));
      rewind(fp);
    }

    if (!buffer.empty()) {
      if(fread(&buffer[0], buffer.size(), 1, fp) != 1) {
        perror(filename);
        buffer.clear();
      } else {
        content.assign(buffer.begin(), buffer.end());
      }
    }

    fclose(fp);
    return content;
  }

  void BmpBlockUtil::fill_bmpblock_header() {
    memset(&config_.header, '\0', sizeof(config_.header));
    memcpy(&config_.header.signature, BMPBLOCK_SIGNATURE,
           BMPBLOCK_SIGNATURE_SIZE);
    config_.header.major_version = major_version_;
    config_.header.minor_version = minor_version_;
    config_.header.number_of_localizations = config_.localizations.size();
    config_.header.number_of_screenlayouts = config_.localizations[0].size();
    // NOTE: this is part of the yaml consistency check
    for (unsigned int i = 1; i < config_.localizations.size(); ++i) {
      assert(config_.header.number_of_screenlayouts ==
             config_.localizations[i].size());
    }
    config_.header.number_of_imageinfos = config_.images_map.size();
    config_.header.locale_string_offset = 0; // Filled by pack_bmpblock()
  }

  void BmpBlockUtil::pack_bmpblock() {
    bmpblock_.clear();

    /* Compute the ImageInfo offsets from start of BMPBLOCK. */
    uint32_t current_offset = sizeof(BmpBlockHeader) +
      sizeof(ScreenLayout) * (config_.header.number_of_localizations *
                              config_.header.number_of_screenlayouts);
    for (StrImageConfigMap::iterator it = config_.images_map.begin();
         it != config_.images_map.end();
         ++it) {
      it->second.offset = current_offset;
      if (debug_)
        printf("  \"%s\": filename=\"%s\" offset=0x%x tag=%d fmt=%d\n",
               it->first.c_str(),
               it->second.filename.c_str(),
               it->second.offset,
               it->second.data.tag,
               it->second.data.format);
      current_offset += sizeof(ImageInfo) +
        it->second.data.compressed_size;
      /* Make it 4-byte aligned. */
      if ((current_offset & 3) > 0) {
        current_offset = (current_offset & ~3) + 4;
      }
    }
    /* And leave room for the locale_index string */
    if (config_.locale_names.size()) {
      config_.header.locale_string_offset = current_offset;
      current_offset += config_.locale_names.size();
    }

    bmpblock_.resize(current_offset);

    /* Fill BmpBlockHeader struct. */
    string::iterator current_filled = bmpblock_.begin();
    std::copy(reinterpret_cast<char*>(&config_.header),
              reinterpret_cast<char*>(&config_.header + 1),
              current_filled);
    current_filled += sizeof(config_.header);
    current_offset = sizeof(config_.header);

    /* Fill all ScreenLayout structs. */
    for (unsigned int i = 0; i < config_.localizations.size(); ++i) {
      for (unsigned int j = 0; j < config_.localizations[i].size(); ++j) {
        ScreenConfig &screen = config_.screens_map[config_.localizations[i][j]];
        for (unsigned int k = 0;
             k < MAX_IMAGE_IN_LAYOUT && !screen.image_names[k].empty();
             ++k) {
          if (config_.images_map.find(screen.image_names[k]) ==
              config_.images_map.end()) {
            error("Invalid image name \"%s\"\n", screen.image_names[k].c_str());
          }
          if (debug_)
            printf("i=%d j=%d k=%d=\"%s\" (%d,%d) ofs=%x\n", i,j,k,
                   screen.image_names[k].c_str(),
                   screen.data.images[k].x, screen.data.images[k].y,
                   config_.images_map[screen.image_names[k]].offset
              );
          screen.data.images[k].image_info_offset =
            config_.images_map[screen.image_names[k]].offset;
        }
        std::copy(reinterpret_cast<char*>(&screen.data),
                  reinterpret_cast<char*>(&screen.data + 1),
                  current_filled);
        current_filled += sizeof(screen.data);
        if (debug_)
          printf("S: current offset is 0x%08x\n", current_offset);
        current_offset += sizeof(screen.data);
      }
    }

    /* Fill all ImageInfo structs and image contents. */
    for (StrImageConfigMap::iterator it = config_.images_map.begin();
         it != config_.images_map.end();
         ++it) {
      current_filled = bmpblock_.begin() + it->second.offset;
      current_offset = it->second.offset;
      if (debug_)
        printf("I0: current offset is 0x%08x\n", current_offset);
      std::copy(reinterpret_cast<char*>(&it->second.data),
                reinterpret_cast<char*>(&it->second.data + 1),
                current_filled);
      current_filled += sizeof(it->second.data);
      current_offset += sizeof(it->second.data);
      if (debug_)
        printf("I1: current offset is 0x%08x (len %zd)\n",
               current_offset, it->second.compressed_content.length());
      std::copy(it->second.compressed_content.begin(),
                it->second.compressed_content.end(),
                current_filled);
    }

    /* Fill in locale_names. */
    if (config_.header.locale_string_offset) {
      current_offset = config_.header.locale_string_offset;
      current_filled = bmpblock_.begin() + current_offset;
      if (debug_)
        printf("locale_names: offset 0x%08x (len %zd)\n",
               current_offset, config_.locale_names.size());
      std::copy(config_.locale_names.begin(),
                config_.locale_names.end(),
                current_filled);
    }
  }

  void BmpBlockUtil::write_to_bmpblock(const char *filename) {
    assert(!bmpblock_.empty());

    FILE *fp = fopen(filename, "wb");
    if (!fp) {
      perror(filename);
      exit(errno);
    }

    int r = fwrite(bmpblock_.c_str(), bmpblock_.size(), 1, fp);
    fclose(fp);
    if (r != 1) {
      perror(filename);
      exit(errno);
    }
  }

}  // namespace vboot_reference

#ifndef FOR_LIBRARY

  //////////////////////////////////////////////////////////////////////////////
  // Command line utilities.

  extern "C" {
#include "bmpblk_util.h"
  }

  using vboot_reference::BmpBlockUtil;

  // utility function: provide usage of this utility and exit.
  static void usagehelp_exit(const char *prog_name) {
    printf(
      "\n"
      "To create a new BMPBLOCK file using config from YAML file:\n"
      "\n"
      "  %s [-z NUM] -c YAML BMPBLOCK\n"
      "\n"
      "    -z NUM  = compression algorithm to use\n"
      "              0 = none\n"
      "              1 = EFIv1\n"
      "              2 = LZMA1\n"
      "\n", prog_name);
    printf(
      "To display the contents of a BMPBLOCK:\n"
      "\n"
      "  %s [-y] BMPBLOCK\n"
      "\n"
      "    -y  = display as yaml\n"
      "\n", prog_name);
    printf(
      "To unpack a BMPBLOCK file:\n"
      "\n"
      "  %s -x [-d DIR] [-f] BMPBLOCK\n"
      "\n"
      "    -d DIR  = directory to use (default '.')\n"
      "    -f      = force overwriting existing files\n"
      "\n", prog_name);
    exit(1);
  }

  ///////////////////////////////////////////////////////////////////////
  // main

  int main(int argc, char *argv[]) {

    const char *prog_name = strrchr(argv[0], '/');
    if (prog_name)
      prog_name++;
    else
      prog_name = argv[0];

    int overwrite = 0, extract_mode = 0;
    int compression = 0;
    int set_compression = 0;
    const char *config_fn = 0, *bmpblock_fn = 0, *extract_dir = ".";
    int show_as_yaml = 0;
    bool debug = false;

    int opt;
    opterr = 0;                           // quiet
    int errorcnt = 0;
    char *e = 0;
    while ((opt = getopt(argc, argv, ":c:xz:fd:yD")) != -1) {
      switch (opt) {
      case 'c':
        config_fn = optarg;
        break;
      case 'x':
        extract_mode = 1;
        break;
      case 'y':
        show_as_yaml = 1;
        break;
      case 'z':
        compression = (int)strtoul(optarg, &e, 0);
        if (!*optarg || (e && *e)) {
          fprintf(stderr, "%s: invalid argument to -%c: \"%s\"\n",
                  prog_name, opt, optarg);
          errorcnt++;
        }
        if (compression >= MAX_COMPRESS) {
          fprintf(stderr, "%s: compression type must be less than %d\n",
                  prog_name, MAX_COMPRESS);
          errorcnt++;
        }
        set_compression = 1;
        break;
      case 'f':
        overwrite = 1;
        break;
      case 'd':
        extract_dir= optarg;
        break;
      case 'D':
        debug = true;
        break;
      case ':':
        fprintf(stderr, "%s: missing argument to -%c\n",
                prog_name, optopt);
        errorcnt++;
        break;
      default:
        fprintf(stderr, "%s: unrecognized switch: -%c\n",
                prog_name, optopt);
        errorcnt++;
        break;
      }
    }
    argc -= optind;
    argv += optind;

    if (argc >= 1) {
      bmpblock_fn = argv[0];
    } else {
      fprintf(stderr, "%s: missing BMPBLOCK name\n", prog_name);
      errorcnt++;
    }

    if (errorcnt)
      usagehelp_exit(prog_name);

    BmpBlockUtil util(debug);

    if (config_fn) {
      if (set_compression)
        util.force_compression(compression);
      util.load_from_config(config_fn);
      util.pack_bmpblock();
      util.write_to_bmpblock(bmpblock_fn);
    }

    else if (extract_mode) {
      return dump_bmpblock(bmpblock_fn, 1, extract_dir, overwrite);
    } else {
      return dump_bmpblock(bmpblock_fn, show_as_yaml, 0, 0);
    }

    return 0;
  }

#endif // FOR_LIBRARY
