1/* Decompression support for libdwfl: zlib (gzip) and/or bzlib (bzip2).
2   Copyright (C) 2009 Red Hat, Inc.
3   This file is part of elfutils.
4
5   This file is free software; you can redistribute it and/or modify
6   it under the terms of either
7
8     * the GNU Lesser General Public License as published by the Free
9       Software Foundation; either version 3 of the License, or (at
10       your option) any later version
11
12   or
13
14     * the GNU General Public License as published by the Free
15       Software Foundation; either version 2 of the License, or (at
16       your option) any later version
17
18   or both in parallel, as here.
19
20   elfutils is distributed in the hope that it will be useful, but
21   WITHOUT ANY WARRANTY; without even the implied warranty of
22   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
23   General Public License for more details.
24
25   You should have received copies of the GNU General Public License and
26   the GNU Lesser General Public License along with this program.  If
27   not, see <http://www.gnu.org/licenses/>.  */
28
29#include "libdwflP.h"
30#include "system.h"
31
32#include <unistd.h>
33
34#ifdef LZMA
35# define USE_INFLATE	1
36# include <lzma.h>
37# define unzip		__libdw_unlzma
38# define DWFL_E_ZLIB	DWFL_E_LZMA
39# define MAGIC		"\xFD" "7zXZ\0" /* XZ file format.  */
40# define MAGIC2		"\x5d\0"	/* Raw LZMA format.  */
41# define Z(what)	LZMA_##what
42# define LZMA_ERRNO	LZMA_PROG_ERROR
43# define z_stream	lzma_stream
44# define inflateInit(z)	lzma_auto_decoder (z, 1 << 30, 0)
45# define do_inflate(z)	lzma_code (z, LZMA_RUN)
46# define inflateEnd(z)	lzma_end (z)
47#elif defined BZLIB
48# define USE_INFLATE	1
49# include <bzlib.h>
50# define unzip		__libdw_bunzip2
51# define DWFL_E_ZLIB	DWFL_E_BZLIB
52# define MAGIC		"BZh"
53# define Z(what)	BZ_##what
54# define BZ_ERRNO	BZ_IO_ERROR
55# define z_stream	bz_stream
56# define inflateInit(z)	BZ2_bzDecompressInit (z, 0, 0)
57# define do_inflate(z)	BZ2_bzDecompress (z)
58# define inflateEnd(z)	BZ2_bzDecompressEnd (z)
59#else
60# define USE_INFLATE	0
61# define crc32		loser_crc32
62# include <zlib.h>
63# define unzip		__libdw_gunzip
64# define MAGIC		"\037\213"
65# define Z(what)	Z_##what
66#endif
67
68#define READ_SIZE		(1 << 20)
69
70struct unzip_state {
71#if !USE_INFLATE
72  gzFile zf;
73#endif
74  size_t mapped_size;
75  void **whole;
76  void *buffer;
77  size_t size;
78  void *input_buffer;
79  off_t input_pos;
80};
81
82static inline bool
83bigger_buffer (struct unzip_state *state, size_t start)
84{
85  size_t more = state->size ? state->size * 2 : start;
86  char *b = realloc (state->buffer, more);
87  while (unlikely (b == NULL) && more >= state->size + 1024)
88    b = realloc (state->buffer, more -= 1024);
89  if (unlikely (b == NULL))
90    return false;
91  state->buffer = b;
92  state->size = more;
93  return true;
94}
95
96static inline void
97smaller_buffer (struct unzip_state *state, size_t end)
98{
99  state->buffer =
100      realloc (state->buffer, end) ?: end == 0 ? NULL : state->buffer;
101  state->size = end;
102}
103
104static inline Dwfl_Error
105fail (struct unzip_state *state, Dwfl_Error failure)
106{
107  if (state->input_pos == (off_t) state->mapped_size)
108    *state->whole = state->input_buffer;
109  else
110    {
111      free (state->input_buffer);
112      *state->whole = NULL;
113    }
114  free (state->buffer);
115  return failure;
116}
117
118static inline Dwfl_Error
119zlib_fail (struct unzip_state *state, int result)
120{
121  switch (result)
122    {
123    case Z (MEM_ERROR):
124      return fail (state, DWFL_E_NOMEM);
125    case Z (ERRNO):
126      return fail (state, DWFL_E_ERRNO);
127    default:
128      return fail (state, DWFL_E_ZLIB);
129    }
130}
131
132#if !USE_INFLATE
133static Dwfl_Error
134open_stream (int fd, off_t start_offset, struct unzip_state *state)
135{
136    int d = dup (fd);
137    if (unlikely (d < 0))
138      return DWFL_E_BADELF;
139    if (start_offset != 0)
140      {
141	off_t off = lseek (d, start_offset, SEEK_SET);
142	if (off != start_offset)
143	  {
144	    close (d);
145	    return DWFL_E_BADELF;
146	  }
147      }
148    state->zf = gzdopen (d, "r");
149    if (unlikely (state->zf == NULL))
150      {
151	close (d);
152	return zlib_fail (state, Z (MEM_ERROR));
153      }
154
155    /* From here on, zlib will close D.  */
156
157    return DWFL_E_NOERROR;
158}
159#endif
160
161/* If this is not a compressed image, return DWFL_E_BADELF.
162   If we uncompressed it into *WHOLE, *WHOLE_SIZE, return DWFL_E_NOERROR.
163   Otherwise return an error for bad compressed data or I/O failure.
164   If we return an error after reading the first part of the file,
165   leave that portion malloc'd in *WHOLE, *WHOLE_SIZE.  If *WHOLE
166   is not null on entry, we'll use it in lieu of repeating a read.  */
167
168Dwfl_Error internal_function
169unzip (int fd, off_t start_offset,
170       void *mapped, size_t _mapped_size,
171       void **_whole, size_t *whole_size)
172{
173  struct unzip_state state =
174    {
175#if !USE_INFLATE
176      .zf = NULL,
177#endif
178      .mapped_size = _mapped_size,
179      .whole = _whole,
180      .buffer = NULL,
181      .size = 0,
182      .input_buffer = NULL,
183      .input_pos = 0
184    };
185
186  if (mapped == NULL)
187    {
188      if (*state.whole == NULL)
189	{
190	  state.input_buffer = malloc (READ_SIZE);
191	  if (unlikely (state.input_buffer == NULL))
192	    return DWFL_E_NOMEM;
193
194	  ssize_t n = pread_retry (fd, state.input_buffer, READ_SIZE, start_offset);
195	  if (unlikely (n < 0))
196	    return zlib_fail (&state, Z (ERRNO));
197
198	  state.input_pos = n;
199	  mapped = state.input_buffer;
200	  state.mapped_size = n;
201	}
202      else
203	{
204	  state.input_buffer = *state.whole;
205	  state.input_pos = state.mapped_size = *whole_size;
206	}
207    }
208
209#define NOMAGIC(magic) \
210  (state.mapped_size <= sizeof magic || \
211   memcmp (mapped, magic, sizeof magic - 1))
212
213  /* First, look at the header.  */
214  if (NOMAGIC (MAGIC)
215#ifdef MAGIC2
216      && NOMAGIC (MAGIC2)
217#endif
218      )
219    /* Not a compressed file.  */
220    return DWFL_E_BADELF;
221
222#if USE_INFLATE
223
224  /* This style actually only works with bzlib and liblzma.
225     The stupid zlib interface has nothing to grok the
226     gzip file headers except the slow gzFile interface.  */
227
228  z_stream z = { .next_in = mapped, .avail_in = state.mapped_size };
229  int result = inflateInit (&z);
230  if (result != Z (OK))
231    {
232      inflateEnd (&z);
233      return zlib_fail (&state, result);
234    }
235
236  do
237    {
238      if (z.avail_in == 0 && state.input_buffer != NULL)
239	{
240	  ssize_t n = pread_retry (fd, state.input_buffer, READ_SIZE,
241				   start_offset + state.input_pos);
242	  if (unlikely (n < 0))
243	    {
244	      inflateEnd (&z);
245	      return zlib_fail (&state, Z (ERRNO));
246	    }
247	  z.next_in = state.input_buffer;
248	  z.avail_in = n;
249	  state.input_pos += n;
250	}
251      if (z.avail_out == 0)
252	{
253	  ptrdiff_t pos = (void *) z.next_out - state.buffer;
254	  if (!bigger_buffer (&state, z.avail_in))
255	    {
256	      result = Z (MEM_ERROR);
257	      break;
258	    }
259	  z.next_out = state.buffer + pos;
260	  z.avail_out = state.size - pos;
261	}
262    }
263  while ((result = do_inflate (&z)) == Z (OK));
264
265#ifdef BZLIB
266  uint64_t total_out = (((uint64_t) z.total_out_hi32 << 32)
267			| z.total_out_lo32);
268  smaller_buffer (&state, total_out);
269#else
270  smaller_buffer (&state, z.total_out);
271#endif
272
273  inflateEnd (&z);
274
275  if (result != Z (STREAM_END))
276    return zlib_fail (&state, result);
277
278#else  /* gzip only.  */
279
280  /* Let the decompression library read the file directly.  */
281
282  Dwfl_Error result = open_stream (fd, start_offset, &state);
283
284  if (result == DWFL_E_NOERROR && gzdirect (state.zf))
285    {
286      gzclose (state.zf);
287      return fail (&state, DWFL_E_BADELF);
288    }
289
290  if (result != DWFL_E_NOERROR)
291    return fail (&state, result);
292
293  ptrdiff_t pos = 0;
294  while (1)
295    {
296      if (!bigger_buffer (&state, 1024))
297	{
298	  gzclose (state.zf);
299	  return zlib_fail (&state, Z (MEM_ERROR));
300	}
301      int n = gzread (state.zf, state.buffer + pos, state.size - pos);
302      if (n < 0)
303	{
304	  int code;
305	  gzerror (state.zf, &code);
306	  gzclose (state.zf);
307	  return zlib_fail (&state, code);
308	}
309      if (n == 0)
310	break;
311      pos += n;
312    }
313
314  gzclose (state.zf);
315  smaller_buffer (&state, pos);
316#endif
317
318  free (state.input_buffer);
319
320  *state.whole = state.buffer;
321  *whole_size = state.size;
322
323  return DWFL_E_NOERROR;
324}
325