Enable EFIv1 compression in bmpbklk_utility.

This lets bmpbklk_utility generate BMPBLOCKs with EFIv1-compressed bitmaps.
It also adds the ability to display or unpack BMPBLOCK blobs.

The compression/decompression routines come from the tianocore EDK on
sourceforge and are written in C, so now there's a mix of C and C++, but it
works just fine.

BUG=chromium-os:11491
TEST=manual

cd src/platform/vboot_reference
make
make runbmptests

Review URL: http://codereview.chromium.org/6508006

Change-Id: Ie05e1a3fd42f4694447c8c440b2432af4ac0f601
diff --git a/utility/bmpblk_util.c b/utility/bmpblk_util.c
index d7b9cbf..214beda 100644
--- a/utility/bmpblk_util.c
+++ b/utility/bmpblk_util.c
@@ -4,6 +4,7 @@
 
 #include <errno.h>
 #include <fcntl.h>
+#include <limits.h>
 #include <stdio.h>
 #include <string.h>
 #include <sys/mman.h>
@@ -12,6 +13,7 @@
 #include <unistd.h>
 
 #include "bmpblk_util.h"
+#include "eficompress.h"
 
 
 // Returns pointer to buffer containing entire file, sets length.
@@ -58,14 +60,105 @@
   munmap(ptr, length);
 }
 
+//////////////////////////////////////////////////////////////////////////////
 
-// Show what's inside
-int display_bmpblock(const char *infile) {
-  char *ptr;
+static int require_dir(const char *dirname) {
+  struct stat sbuf;
+
+  if (0 == stat(dirname, &sbuf)) {
+    // Something's there. Is it a directory?
+    if (S_ISDIR(sbuf.st_mode)) {
+      return 0;
+    }
+    fprintf(stderr, "%s already exists and is not a directory\n", dirname);
+    return 1;
+  }
+
+  // dirname doesn't exist. Try to create it.
+  if (ENOENT == errno) {
+    if (0 != mkdir(dirname, 0777)) {
+      fprintf(stderr, "Unable to create directory %s: %s\n",
+              dirname, strerror(errno));
+      return 1;
+    }
+    return 0;
+  }
+
+  fprintf(stderr, "Unable to stat %s: %s\n", dirname, strerror(errno));
+  return 1;
+}
+
+
+
+static void *do_efi_decompress(ImageInfo *img) {
+  void *ibuf;
+  void *sbuf;
+  void *obuf;
+  uint32_t isize;
+  uint32_t ssize;
+  uint32_t osize;
+  EFI_STATUS r;
+
+  ibuf = ((void *)img) + sizeof(ImageInfo);
+  isize = img->compressed_size;
+
+  r = EfiGetInfo(ibuf, isize, &osize, &ssize);
+  if (EFI_SUCCESS != r) {
+    fprintf(stderr, "EfiGetInfo() failed with code %d\n",
+            r);
+    return 0;
+  }
+
+  sbuf = malloc(ssize);
+  if (!sbuf) {
+    fprintf(stderr, "Can't allocate %d bytes: %s\n",
+            ssize,
+            strerror(errno));
+    return 0;
+  }
+
+  obuf = malloc(osize);
+  if (!obuf) {
+    fprintf(stderr, "Can't allocate %d bytes: %s\n",
+            osize,
+            strerror(errno));
+    free(sbuf);
+    return 0;
+  }
+
+  r = EfiDecompress(ibuf, isize, obuf, osize, sbuf, ssize);
+  if (r != EFI_SUCCESS) {
+    fprintf(stderr, "EfiDecompress failed with code %d\n", r);
+    free(obuf);
+    free(sbuf);
+    return 0;
+  }
+
+  free(sbuf);
+  return obuf;
+}
+
+
+// Show what's inside. If todir is NULL, just print. Otherwise unpack.
+int dump_bmpblock(const char *infile, int show_as_yaml,
+                  const char *todir, int overwrite) {
+  void *ptr, *data_ptr;
   size_t length = 0;
   BmpBlockHeader *hdr;
+  ImageInfo *img;
+  ScreenLayout *scr;
+  int loc_num;
+  int screen_num;
+  int i;
+  int offset;
+  int free_data;
+  char image_name[80];
+  char full_path_name[PATH_MAX];
+  int yfd, bfd;
+  FILE *yfp = stdout;
+  FILE *bfp = stdout;
 
-  ptr = (char *)read_entire_file(infile, &length);
+  ptr = (void *)read_entire_file(infile, &length);
   if (!ptr)
     return 1;
 
@@ -81,21 +174,163 @@
     return 1;
   }
 
+  if (todir) {
+    // Unpacking everything. Create the output directory if needed.
+    if (0 != require_dir(todir)) {
+      discard_file(ptr, length);
+      return 1;
+    }
+
+    // Open yaml output.
+    show_as_yaml = 1;
+
+    sprintf(full_path_name, "%s/%s", todir, "config.yaml");
+    yfd = open(full_path_name,
+               O_WRONLY | O_CREAT | O_TRUNC | (overwrite ? 0 : O_EXCL),
+               0666);
+    if (yfd < 0) {
+      fprintf(stderr, "Unable to open %s: %s\n", full_path_name,
+              strerror(errno));
+      discard_file(ptr, length);
+      return 1;
+    }
+
+    yfp = fdopen(yfd, "wb");
+    if (!yfp) {
+      fprintf(stderr, "Unable to fdopen %s: %s\n", full_path_name,
+              strerror(errno));
+      close(yfd);
+      discard_file(ptr, length);
+      return 1;
+    }
+  }
+
   hdr = (BmpBlockHeader *)ptr;
-  printf("%s:\n", infile);
-  printf("  version %d.%d\n", hdr->major_version, hdr->minor_version);
-  printf("  %d screens\n", hdr->number_of_screenlayouts);
-  printf("  %d localizations\n", hdr->number_of_localizations);
-  printf("  %d discrete images\n", hdr->number_of_imageinfos);
+
+  if (!show_as_yaml) {
+    printf("%s:\n", infile);
+    printf("  version %d.%d\n", hdr->major_version, hdr->minor_version);
+    printf("  %d screens\n", hdr->number_of_screenlayouts);
+    printf("  %d localizations\n", hdr->number_of_localizations);
+    printf("  %d discrete images\n", hdr->number_of_imageinfos);
+    discard_file(ptr, length);
+    return 0;
+  }
+
+  // Write out yaml
+  fprintf(yfp, "bmpblock: %d.%d\n", hdr->major_version, hdr->minor_version);
+  fprintf(yfp, "images:\n");
+  offset = sizeof(BmpBlockHeader) +
+    (sizeof(ScreenLayout) *
+     hdr->number_of_localizations *
+     hdr->number_of_screenlayouts);
+  for(i=0; i<hdr->number_of_imageinfos; i++) {
+    img = (ImageInfo *)(ptr + offset);
+    sprintf(image_name, "img_%08x.bmp", offset);
+    fprintf(yfp, "  img_%08x: %s  # %dx%d  %d/%d\n", offset, image_name,
+            img->width, img->height,
+            img->compressed_size, img->original_size);
+    if (todir) {
+      sprintf(full_path_name, "%s/%s", todir, image_name);
+      bfd = open(full_path_name,
+                 O_WRONLY | O_CREAT | O_TRUNC | (overwrite ? 0 : O_EXCL),
+                 0666);
+      if (bfd < 0) {
+        fprintf(stderr, "Unable to open %s: %s\n", full_path_name,
+                strerror(errno));
+        fclose(yfp);
+        discard_file(ptr, length);
+        return 1;
+      }
+      bfp = fdopen(bfd, "wb");
+      if (!bfp) {
+        fprintf(stderr, "Unable to fdopen %s: %s\n", full_path_name,
+                strerror(errno));
+        close(bfd);
+        fclose(yfp);
+        discard_file(ptr, length);
+        return 1;
+      }
+      switch(img->compression) {
+      case COMPRESS_NONE:
+        data_ptr = ptr + offset + sizeof(ImageInfo);
+        free_data = 0;
+        break;
+      case COMPRESS_EFIv1:
+        data_ptr = do_efi_decompress(img);
+        if (!data_ptr) {
+          fclose(bfp);
+          fclose(yfp);
+          discard_file(ptr, length);
+          return 1;
+        }
+        free_data = 1;
+        break;
+      default:
+        fprintf(stderr, "Unsupported compression method encountered.\n");
+        fclose(bfp);
+        fclose(yfp);
+        discard_file(ptr, length);
+        return 1;
+      }
+      if (1 != fwrite(data_ptr, img->original_size, 1, bfp)) {
+        fprintf(stderr, "Unable to write %s: %s\n", full_path_name,
+                strerror(errno));
+        fclose(bfp);
+        fclose(yfp);
+        discard_file(ptr, length);
+        return 1;
+      }
+      fclose(bfp);
+      if (free_data)
+        free(data_ptr);
+    }
+    offset += sizeof(ImageInfo);
+    offset += img->compressed_size;
+    // 4-byte aligned
+    if ((offset & 3) > 0)
+      offset = (offset & ~3) + 4;
+  }
+  fprintf(yfp, "screens:\n");
+  for(loc_num = 0;
+      loc_num < hdr->number_of_localizations;
+      loc_num++) {
+    for(screen_num = 0;
+        screen_num < hdr->number_of_screenlayouts;
+        screen_num++) {
+      fprintf(yfp, "  scr_%d_%d:\n", loc_num, screen_num);
+      i = loc_num * hdr->number_of_screenlayouts + screen_num;
+      offset = sizeof(BmpBlockHeader) + i * sizeof(ScreenLayout);
+      scr = (ScreenLayout *)(ptr + offset);
+      for(i=0; i<MAX_IMAGE_IN_LAYOUT; i++) {
+        if (scr->images[i].image_info_offset) {
+          fprintf(yfp, "    - [%d, %d, img_%08x]\n",
+                  scr->images[i].x, scr->images[i].y,
+                  scr->images[i].image_info_offset);
+        }
+      }
+    }
+  }
+  fprintf(yfp, "localizations:\n");
+  for(loc_num = 0;
+      loc_num < hdr->number_of_localizations;
+      loc_num++) {
+    fprintf(yfp, "  - [");
+    for(screen_num = 0;
+        screen_num < hdr->number_of_screenlayouts;
+        screen_num++) {
+      fprintf(yfp, " scr_%d_%d", loc_num, screen_num);
+      if (screen_num != hdr->number_of_screenlayouts - 1)
+        fprintf(yfp, ",");
+    }
+    fprintf(yfp, " ]\n");
+  }
+
+  if (todir)
+    fclose(yfp);
 
   discard_file(ptr, length);
 
   return 0;
 }
 
-int extract_bmpblock(const char *infile, const char *dirname, int force) {
-    printf("extract parts from %s into %s %s overwriting\n",
-           infile, dirname, force ? "with" : "without");
-    printf("NOT YET IMPLEMENTED\n");
-    return 0;
-}