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#include <errno.h>
6#include <fcntl.h>
7#include <limits.h>
8#include <lzma.h>
9#include <stdint.h>
10#include <stdlib.h>
11#include <stdio.h>
12#include <string.h>
13#include <sys/mman.h>
14#include <sys/stat.h>
15#include <sys/types.h>
16#include <unistd.h>
17
18#include "bmpblk_util.h"
19#include "eficompress.h"
20#include "vboot_api.h"
21
22// Returns pointer to buffer containing entire file, sets length.
23static void *read_entire_file(const char *filename, size_t *length) {
24  int fd;
25  struct stat sbuf;
26  void *ptr;
27
28  *length = 0;                          // just in case
29
30  if (0 != stat(filename, &sbuf)) {
31    fprintf(stderr, "Unable to stat %s: %s\n", filename, strerror(errno));
32    return 0;
33  }
34
35  if (!sbuf.st_size) {
36    fprintf(stderr, "File %s is empty\n", filename);
37    return 0;
38  }
39
40  fd = open(filename, O_RDONLY);
41  if (fd < 0) {
42    fprintf(stderr, "Unable to open %s: %s\n", filename, strerror(errno));
43    return 0;
44  }
45
46  ptr = mmap(0, sbuf.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
47  if (MAP_FAILED == ptr) {
48    fprintf(stderr, "Unable to mmap %s: %s\n", filename, strerror(errno));
49    close(fd);
50    return 0;
51  }
52
53  *length = sbuf.st_size;
54
55  close(fd);
56
57  return ptr;
58}
59
60
61// Reclaims buffer from read_entire_file().
62static void discard_file(void *ptr, size_t length) {
63  munmap(ptr, length);
64}
65
66//////////////////////////////////////////////////////////////////////////////
67
68static int require_dir(const char *dirname) {
69  struct stat sbuf;
70
71  if (0 == stat(dirname, &sbuf)) {
72    // Something's there. Is it a directory?
73    if (S_ISDIR(sbuf.st_mode)) {
74      return 0;
75    }
76    fprintf(stderr, "%s already exists and is not a directory\n", dirname);
77    return 1;
78  }
79
80  // dirname doesn't exist. Try to create it.
81  if (ENOENT == errno) {
82    if (0 != mkdir(dirname, 0777)) {
83      fprintf(stderr, "Unable to create directory %s: %s\n",
84              dirname, strerror(errno));
85      return 1;
86    }
87    return 0;
88  }
89
90  fprintf(stderr, "Unable to stat %s: %s\n", dirname, strerror(errno));
91  return 1;
92}
93
94
95
96static void *do_efi_decompress(ImageInfo *img) {
97  void *ibuf;
98  void *sbuf;
99  void *obuf;
100  uint32_t isize;
101  uint32_t ssize;
102  uint32_t osize;
103  EFI_STATUS r;
104
105  ibuf = (void*)(img + 1);
106  isize = img->compressed_size;
107
108  r = EfiGetInfo(ibuf, isize, &osize, &ssize);
109  if (EFI_SUCCESS != r) {
110    fprintf(stderr, "EfiGetInfo() failed with code %d\n",
111            r);
112    return 0;
113  }
114
115  sbuf = malloc(ssize);
116  if (!sbuf) {
117    fprintf(stderr, "Can't allocate %d bytes: %s\n",
118            ssize,
119            strerror(errno));
120    return 0;
121  }
122
123  obuf = malloc(osize);
124  if (!obuf) {
125    fprintf(stderr, "Can't allocate %d bytes: %s\n",
126            osize,
127            strerror(errno));
128    free(sbuf);
129    return 0;
130  }
131
132  r = EfiDecompress(ibuf, isize, obuf, osize, sbuf, ssize);
133  if (r != EFI_SUCCESS) {
134    fprintf(stderr, "EfiDecompress failed with code %d\n", r);
135    free(obuf);
136    free(sbuf);
137    return 0;
138  }
139
140  free(sbuf);
141  return obuf;
142}
143
144
145
146static void *do_lzma_decompress(ImageInfo *img) {
147  void *ibuf;
148  void *obuf;
149  uint32_t isize;
150  uint32_t osize;
151  lzma_stream stream = LZMA_STREAM_INIT;
152  lzma_ret result;
153
154  ibuf = (void*)(img + 1);
155  isize = img->compressed_size;
156  osize = img->original_size;
157  obuf = malloc(osize);
158  if (!obuf) {
159    fprintf(stderr, "Can't allocate %d bytes: %s\n",
160            osize,
161            strerror(errno));
162    return 0;
163  }
164
165  result = lzma_auto_decoder(&stream, -1, 0);
166  if (result != LZMA_OK) {
167    fprintf(stderr, "Unable to initialize auto decoder (error: %d)!\n",
168            result);
169    free(obuf);
170    return 0;
171  }
172
173  stream.next_in = ibuf;
174  stream.avail_in = isize;
175  stream.next_out = obuf;
176  stream.avail_out = osize;
177  result = lzma_code(&stream, LZMA_FINISH);
178  if (result != LZMA_STREAM_END) {
179    fprintf(stderr, "Unalbe to decode data (error: %d)!\n", result);
180    free(obuf);
181    return 0;
182  }
183  lzma_end(&stream);
184  return obuf;
185}
186
187
188
189// Show what's inside. If todir is NULL, just print. Otherwise unpack.
190int dump_bmpblock(const char *infile, int show_as_yaml,
191                  const char *todir, int overwrite) {
192  void *ptr, *data_ptr;
193  size_t length = 0;
194  BmpBlockHeader *hdr;
195  ImageInfo *img;
196  ScreenLayout *scr;
197  int loc_num;
198  int screen_num;
199  int i;
200  int offset;
201  int free_data;
202  char image_name[80];
203  char full_path_name[PATH_MAX];
204  int yfd, bfd;
205  FILE *yfp = stdout;
206  FILE *bfp = stdout;
207
208  ptr = (void *)read_entire_file(infile, &length);
209  if (!ptr)
210    return 1;
211
212  if (length < sizeof(BmpBlockHeader)) {
213    fprintf(stderr, "File %s is too small to be a BMPBLOCK\n", infile);
214    discard_file(ptr, length);
215    return 1;
216  }
217
218  if (0 != memcmp(ptr, BMPBLOCK_SIGNATURE, BMPBLOCK_SIGNATURE_SIZE)) {
219    fprintf(stderr, "File %s is not a BMPBLOCK\n", infile);
220    discard_file(ptr, length);
221    return 1;
222  }
223
224  if (todir) {
225    // Unpacking everything. Create the output directory if needed.
226    if (0 != require_dir(todir)) {
227      discard_file(ptr, length);
228      return 1;
229    }
230
231    // Open yaml output.
232    show_as_yaml = 1;
233
234    sprintf(full_path_name, "%s/%s", todir, "config.yaml");
235    yfd = open(full_path_name,
236               O_WRONLY | O_CREAT | O_TRUNC | (overwrite ? 0 : O_EXCL),
237               0666);
238    if (yfd < 0) {
239      fprintf(stderr, "Unable to open %s: %s\n", full_path_name,
240              strerror(errno));
241      discard_file(ptr, length);
242      return 1;
243    }
244
245    yfp = fdopen(yfd, "wb");
246    if (!yfp) {
247      fprintf(stderr, "Unable to fdopen %s: %s\n", full_path_name,
248              strerror(errno));
249      close(yfd);
250      discard_file(ptr, length);
251      return 1;
252    }
253  }
254
255  hdr = (BmpBlockHeader *)ptr;
256
257  if (!show_as_yaml) {
258    printf("%s:\n", infile);
259    printf("  version %d.%d\n", hdr->major_version, hdr->minor_version);
260    printf("  %d screens\n", hdr->number_of_screenlayouts);
261    printf("  %d localizations\n", hdr->number_of_localizations);
262    printf("  %d discrete images\n", hdr->number_of_imageinfos);
263    discard_file(ptr, length);
264    return 0;
265  }
266
267  // Write out yaml
268  fprintf(yfp, "bmpblock: %d.%d\n", hdr->major_version, hdr->minor_version);
269  offset = sizeof(BmpBlockHeader) +
270    (sizeof(ScreenLayout) *
271     hdr->number_of_localizations *
272     hdr->number_of_screenlayouts);
273  // FIXME(chromium-os:12134): The bmbblock structure allows each image to be
274  // compressed differently, but we haven't provided a way for the yaml file to
275  // specify that. Additionally, we allow the yaml file to specify a default
276  // compression scheme for all images, but only if that line appears in the
277  // yaml file before any images. Accordingly, we'll just check the first image
278  // to see if it has any compression, and if it does, we'll write that out as
279  // the default. When this bug is fixed, we should just write each image's
280  // compression setting separately.
281  img = (ImageInfo *)(ptr + offset);
282  if (img->compression)
283    fprintf(yfp, "compression: %d\n", img->compression);
284  fprintf(yfp, "images:\n");
285  for(i=0; i<hdr->number_of_imageinfos; i++) {
286    img = (ImageInfo *)(ptr + offset);
287    if (img->compressed_size) {
288      sprintf(image_name, "img_%08x.bmp", offset);
289      if (img->tag == TAG_HWID) {
290        fprintf(yfp, "  %s: %s  # %dx%d  %d/%d  tag=%d fmt=%d\n",
291                RENDER_HWID, image_name,
292                img->width, img->height,
293                img->compressed_size, img->original_size,
294                img->tag, img->format);
295      } else if (img->tag == TAG_HWID_RTOL) {
296        fprintf(yfp, "  %s: %s  # %dx%d  %d/%d  tag=%d fmt=%d\n",
297                RENDER_HWID_RTOL, image_name,
298                img->width, img->height,
299                img->compressed_size, img->original_size,
300                img->tag, img->format);
301      } else {
302        fprintf(yfp, "  img_%08x: %s  # %dx%d  %d/%d  tag=%d fmt=%d\n",
303                offset, image_name,
304                img->width, img->height,
305                img->compressed_size, img->original_size,
306                img->tag, img->format);
307      }
308      if (todir) {
309        sprintf(full_path_name, "%s/%s", todir, image_name);
310        bfd = open(full_path_name,
311                   O_WRONLY | O_CREAT | O_TRUNC | (overwrite ? 0 : O_EXCL),
312                   0666);
313        if (bfd < 0) {
314          fprintf(stderr, "Unable to open %s: %s\n", full_path_name,
315                  strerror(errno));
316          fclose(yfp);
317          discard_file(ptr, length);
318          return 1;
319        }
320        bfp = fdopen(bfd, "wb");
321        if (!bfp) {
322          fprintf(stderr, "Unable to fdopen %s: %s\n", full_path_name,
323                  strerror(errno));
324          close(bfd);
325          fclose(yfp);
326          discard_file(ptr, length);
327          return 1;
328        }
329        switch(img->compression) {
330        case COMPRESS_NONE:
331          data_ptr = ptr + offset + sizeof(ImageInfo);
332          free_data = 0;
333          break;
334        case COMPRESS_EFIv1:
335          data_ptr = do_efi_decompress(img);
336          if (!data_ptr) {
337            fclose(bfp);
338            fclose(yfp);
339            discard_file(ptr, length);
340            return 1;
341          }
342          free_data = 1;
343          break;
344        case COMPRESS_LZMA1:
345          data_ptr = do_lzma_decompress(img);
346          if (!data_ptr) {
347            fclose(bfp);
348            fclose(yfp);
349            discard_file(ptr, length);
350            return 1;
351          }
352          free_data = 1;
353          break;
354        default:
355          fprintf(stderr, "Unsupported compression method encountered.\n");
356          fclose(bfp);
357          fclose(yfp);
358          discard_file(ptr, length);
359          return 1;
360        }
361        if (1 != fwrite(data_ptr, img->original_size, 1, bfp)) {
362          fprintf(stderr, "Unable to write %s: %s\n", full_path_name,
363                  strerror(errno));
364          fclose(bfp);
365          fclose(yfp);
366          discard_file(ptr, length);
367          return 1;
368        }
369        fclose(bfp);
370        if (free_data)
371          free(data_ptr);
372      }
373    }
374    offset += sizeof(ImageInfo);
375    offset += img->compressed_size;
376    // 4-byte aligned
377    if ((offset & 3) > 0)
378      offset = (offset & ~3) + 4;
379  }
380  fprintf(yfp, "screens:\n");
381  for(loc_num = 0;
382      loc_num < hdr->number_of_localizations;
383      loc_num++) {
384    for(screen_num = 0;
385        screen_num < hdr->number_of_screenlayouts;
386        screen_num++) {
387      fprintf(yfp, "  scr_%d_%d:\n", loc_num, screen_num);
388      i = loc_num * hdr->number_of_screenlayouts + screen_num;
389      offset = sizeof(BmpBlockHeader) + i * sizeof(ScreenLayout);
390      scr = (ScreenLayout *)(ptr + offset);
391      for(i=0; i<MAX_IMAGE_IN_LAYOUT; i++) {
392        if (scr->images[i].image_info_offset) {
393          ImageInfo *iptr =
394            (ImageInfo *)(ptr + scr->images[i].image_info_offset);
395          if (iptr->tag == TAG_HWID) {
396            fprintf(yfp, "    - [%d, %d, %s] # tag=%d fmt=%d c=%d %d/%d\n",
397                    scr->images[i].x, scr->images[i].y,
398                    RENDER_HWID, iptr->tag, iptr->format, iptr->compression,
399                    iptr->compressed_size, iptr->original_size);
400          } else if (iptr->tag == TAG_HWID_RTOL) {
401            fprintf(yfp, "    - [%d, %d, %s] # tag=%d fmt=%d c=%d %d/%d\n",
402                    scr->images[i].x, scr->images[i].y,
403                    RENDER_HWID_RTOL, iptr->tag,
404                    iptr->format, iptr->compression,
405                    iptr->compressed_size, iptr->original_size);
406          } else {
407            fprintf(yfp, "    - [%d, %d, img_%08x]"
408                    " # tag=%d fmt=%d c=%d %d/%d\n",
409                    scr->images[i].x, scr->images[i].y,
410                    scr->images[i].image_info_offset,
411                    iptr->tag, iptr->format, iptr->compression,
412                    iptr->compressed_size, iptr->original_size);
413          }
414        }
415      }
416    }
417  }
418  fprintf(yfp, "localizations:\n");
419  for(loc_num = 0;
420      loc_num < hdr->number_of_localizations;
421      loc_num++) {
422    fprintf(yfp, "  - [");
423    for(screen_num = 0;
424        screen_num < hdr->number_of_screenlayouts;
425        screen_num++) {
426      fprintf(yfp, " scr_%d_%d", loc_num, screen_num);
427      if (screen_num != hdr->number_of_screenlayouts - 1)
428        fprintf(yfp, ",");
429    }
430    fprintf(yfp, " ]\n");
431  }
432
433  if (hdr->locale_string_offset) {
434    char *loc_ptr = (char *)ptr + hdr->locale_string_offset;
435    char c;
436    fprintf(yfp, "locale_index:\n");
437    while ((c = *loc_ptr) != '\0') {
438      fprintf(yfp, "  - ");
439      do {
440        fputc(c, yfp);
441        loc_ptr++;
442      } while((c = *loc_ptr) != '\0');
443      loc_ptr++;
444      fputc('\n', yfp);
445    }
446  }
447
448  if (todir)
449    fclose(yfp);
450
451  discard_file(ptr, length);
452
453  return 0;
454}
455
456