xref: /third_party/elfutils/libdwfl/gzip.c (revision da0c48c4)
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#ifdef HAVE_CONFIG_H
30# include <config.h>
31#endif
32
33#include "libdwflP.h"
34#include "system.h"
35
36#ifdef LZMA
37# define USE_INFLATE	1
38# include <lzma.h>
39# define unzip		__libdw_unlzma
40# define DWFL_E_ZLIB	DWFL_E_LZMA
41# define MAGIC		"\xFD" "7zXZ\0" /* XZ file format.  */
42# define MAGIC2		"\x5d\0"	/* Raw LZMA format.  */
43# define Z(what)	LZMA_##what
44# define LZMA_ERRNO	LZMA_PROG_ERROR
45# define z_stream	lzma_stream
46# define inflateInit(z)	lzma_auto_decoder (z, 1 << 30, 0)
47# define do_inflate(z)	lzma_code (z, LZMA_RUN)
48# define inflateEnd(z)	lzma_end (z)
49#elif defined ZSTD
50# define USE_INFLATE	1
51# include <zstd.h>
52# define unzip		__libdw_unzstd
53# define DWFL_E_ZLIB	DWFL_E_ZSTD
54# define MAGIC		"\x28\xb5\x2f\xfd"
55#elif defined BZLIB
56# define USE_INFLATE	1
57# include <bzlib.h>
58# define unzip		__libdw_bunzip2
59# define DWFL_E_ZLIB	DWFL_E_BZLIB
60# define MAGIC		"BZh"
61# define Z(what)	BZ_##what
62# define BZ_ERRNO	BZ_IO_ERROR
63# define z_stream	bz_stream
64# define inflateInit(z)	BZ2_bzDecompressInit (z, 0, 0)
65# define do_inflate(z)	BZ2_bzDecompress (z)
66# define inflateEnd(z)	BZ2_bzDecompressEnd (z)
67#else
68# define USE_INFLATE	0
69# define crc32		loser_crc32
70# include <zlib.h>
71# define unzip		__libdw_gunzip
72# define MAGIC		"\037\213"
73# define Z(what)	Z_##what
74#endif
75
76#define READ_SIZE		(1 << 20)
77
78struct unzip_state {
79#if !USE_INFLATE
80  gzFile zf;
81#endif
82  size_t mapped_size;
83  void **whole;
84  void *buffer;
85  size_t size;
86  void *input_buffer;
87  off_t input_pos;
88};
89
90static inline bool
91bigger_buffer (struct unzip_state *state, size_t start)
92{
93  size_t more = state->size ? state->size * 2 : start;
94  char *b = realloc (state->buffer, more);
95  while (unlikely (b == NULL) && more >= state->size + 1024)
96    b = realloc (state->buffer, more -= 1024);
97  if (unlikely (b == NULL))
98    return false;
99  state->buffer = b;
100  state->size = more;
101  return true;
102}
103
104static inline void
105smaller_buffer (struct unzip_state *state, size_t end)
106{
107  state->buffer =
108      realloc (state->buffer, end) ?: end == 0 ? NULL : state->buffer;
109  state->size = end;
110}
111
112static inline Dwfl_Error
113fail (struct unzip_state *state, Dwfl_Error failure)
114{
115  if (state->input_pos == (off_t) state->mapped_size)
116    *state->whole = state->input_buffer;
117  else
118    {
119      free (state->input_buffer);
120      *state->whole = NULL;
121    }
122  free (state->buffer);
123  return failure;
124}
125
126#ifndef ZSTD
127static inline Dwfl_Error
128zlib_fail (struct unzip_state *state, int result)
129{
130  switch (result)
131    {
132    case Z (MEM_ERROR):
133      return fail (state, DWFL_E_NOMEM);
134    case Z (ERRNO):
135      return fail (state, DWFL_E_ERRNO);
136    default:
137      return fail (state, DWFL_E_ZLIB);
138    }
139}
140#endif
141
142#if !USE_INFLATE
143static Dwfl_Error
144open_stream (int fd, off_t start_offset, struct unzip_state *state)
145{
146    int d = dup (fd);
147    if (unlikely (d < 0))
148      return DWFL_E_ERRNO;
149    if (start_offset != 0)
150      {
151	off_t off = lseek (d, start_offset, SEEK_SET);
152	if (off != start_offset)
153	  {
154	    close (d);
155	    return DWFL_E_ERRNO;
156	  }
157      }
158    state->zf = gzdopen (d, "r");
159    if (unlikely (state->zf == NULL))
160      {
161	close (d);
162	return DWFL_E_NOMEM;
163      }
164
165    /* From here on, zlib will close D.  */
166
167    return DWFL_E_NOERROR;
168}
169#endif
170
171/* If this is not a compressed image, return DWFL_E_BADELF.
172   If we uncompressed it into *WHOLE, *WHOLE_SIZE, return DWFL_E_NOERROR.
173   Otherwise return an error for bad compressed data or I/O failure.
174   If we return an error after reading the first part of the file,
175   leave that portion malloc'd in *WHOLE, *WHOLE_SIZE.  If *WHOLE
176   is not null on entry, we'll use it in lieu of repeating a read.  */
177
178Dwfl_Error internal_function
179unzip (int fd, off_t start_offset,
180       void *mapped, size_t _mapped_size,
181       void **_whole, size_t *whole_size)
182{
183  struct unzip_state state =
184    {
185#if !USE_INFLATE
186      .zf = NULL,
187#endif
188      .mapped_size = _mapped_size,
189      .whole = _whole,
190      .buffer = NULL,
191      .size = 0,
192      .input_buffer = NULL,
193      .input_pos = 0
194    };
195
196  if (mapped == NULL)
197    {
198      if (*state.whole == NULL)
199	{
200	  state.input_buffer = malloc (READ_SIZE);
201	  if (unlikely (state.input_buffer == NULL))
202	    return DWFL_E_NOMEM;
203
204	  ssize_t n = pread_retry (fd, state.input_buffer, READ_SIZE, start_offset);
205	  if (unlikely (n < 0))
206	    return fail (&state, DWFL_E_ERRNO);
207
208	  state.input_pos = n;
209	  mapped = state.input_buffer;
210	  state.mapped_size = n;
211	}
212      else
213	{
214	  state.input_buffer = *state.whole;
215	  state.input_pos = state.mapped_size = *whole_size;
216	}
217    }
218
219#define NOMAGIC(magic) \
220  (state.mapped_size <= sizeof magic || \
221   memcmp (mapped, magic, sizeof magic - 1))
222
223  /* First, look at the header.  */
224  if (NOMAGIC (MAGIC)
225#ifdef MAGIC2
226      && NOMAGIC (MAGIC2)
227#endif
228      )
229    /* Not a compressed file.  */
230    return DWFL_E_BADELF;
231
232#ifdef ZSTD
233  /* special case for libzstd since it is slightly different from the
234     API provided by bzlib and liblzma.  */
235
236  void *next_in = mapped;
237  size_t avail_in = state.mapped_size;
238  void *next_out = NULL;
239  size_t avail_out = 0;
240  size_t total_out = 0;
241
242  size_t result;
243  ZSTD_DCtx *dctx = ZSTD_createDCtx();
244  if (dctx == NULL)
245    return fail (&state, DWFL_E_NOMEM);
246
247  do
248    {
249      if (avail_in == 0 && state.input_buffer != NULL)
250	{
251	  ssize_t n = pread_retry (fd, state.input_buffer, READ_SIZE,
252				   start_offset + state.input_pos);
253	  if (unlikely (n < 0))
254	    {
255	      ZSTD_freeDCtx (dctx);
256	      return fail (&state, DWFL_E_ERRNO);
257	    }
258	  next_in = state.input_buffer;
259	  avail_in = n;
260	  state.input_pos += n;
261	}
262      if (avail_out == 0)
263	{
264	  ptrdiff_t pos = (void *) next_out - state.buffer;
265	  if (!bigger_buffer (&state, avail_in))
266	    {
267	      ZSTD_freeDCtx (dctx);
268	      return fail (&state, DWFL_E_NOMEM);
269	    }
270	  next_out = state.buffer + pos;
271	  avail_out = state.size - pos;
272	}
273
274      ZSTD_inBuffer input = { next_in, avail_in, 0 };
275      ZSTD_outBuffer output = { next_out, avail_out, 0 };
276      result = ZSTD_decompressStream (dctx, &output, &input);
277
278      if (! ZSTD_isError (result))
279	{
280	  total_out += output.pos;
281	  next_out += output.pos;
282	  avail_out -= output.pos;
283	  next_in += input.pos;
284	  avail_in -= input.pos;
285	}
286
287      if (result == 0)
288	break;
289    }
290  while (avail_in > 0 && ! ZSTD_isError (result));
291
292  ZSTD_freeDCtx (dctx);
293
294  if (ZSTD_isError (result))
295    return fail (&state, DWFL_E_ZSTD);
296
297  smaller_buffer (&state, total_out);
298
299#elif USE_INFLATE
300
301  /* This style actually only works with bzlib and liblzma.
302     The stupid zlib interface has nothing to grok the
303     gzip file headers except the slow gzFile interface.  */
304
305  z_stream z = { .next_in = mapped, .avail_in = state.mapped_size };
306  int result = inflateInit (&z);
307  if (result != Z (OK))
308    {
309      inflateEnd (&z);
310      return zlib_fail (&state, result);
311    }
312
313  do
314    {
315      if (z.avail_in == 0 && state.input_buffer != NULL)
316	{
317	  ssize_t n = pread_retry (fd, state.input_buffer, READ_SIZE,
318				   start_offset + state.input_pos);
319	  if (unlikely (n < 0))
320	    {
321	      inflateEnd (&z);
322	      return zlib_fail (&state, Z (ERRNO));
323	    }
324	  z.next_in = state.input_buffer;
325	  z.avail_in = n;
326	  state.input_pos += n;
327	}
328      if (z.avail_out == 0)
329	{
330	  ptrdiff_t pos = (void *) z.next_out - state.buffer;
331	  if (!bigger_buffer (&state, z.avail_in))
332	    {
333	      result = Z (MEM_ERROR);
334	      break;
335	    }
336	  z.next_out = state.buffer + pos;
337	  z.avail_out = state.size - pos;
338	}
339    }
340  while ((result = do_inflate (&z)) == Z (OK));
341
342#ifdef BZLIB
343  uint64_t total_out = (((uint64_t) z.total_out_hi32 << 32)
344			| z.total_out_lo32);
345  smaller_buffer (&state, total_out);
346#else
347  smaller_buffer (&state, z.total_out);
348#endif
349
350  inflateEnd (&z);
351
352  if (result != Z (STREAM_END))
353    return zlib_fail (&state, result);
354
355#else  /* gzip only.  */
356
357  /* Let the decompression library read the file directly.  */
358
359  Dwfl_Error result = open_stream (fd, start_offset, &state);
360
361  if (result == DWFL_E_NOERROR && gzdirect (state.zf))
362    {
363      gzclose (state.zf);
364      /* Not a compressed stream after all.  */
365      return fail (&state, DWFL_E_BADELF);
366    }
367
368  if (result != DWFL_E_NOERROR)
369    return fail (&state, result);
370
371  ptrdiff_t pos = 0;
372  while (1)
373    {
374      if (!bigger_buffer (&state, 1024))
375	{
376	  gzclose (state.zf);
377	  return zlib_fail (&state, Z (MEM_ERROR));
378	}
379      int n = gzread (state.zf, state.buffer + pos, state.size - pos);
380      if (n < 0)
381	{
382	  int code;
383	  gzerror (state.zf, &code);
384	  gzclose (state.zf);
385	  return zlib_fail (&state, code);
386	}
387      if (n == 0)
388	break;
389      pos += n;
390    }
391
392  gzclose (state.zf);
393  smaller_buffer (&state, pos);
394#endif
395
396  free (state.input_buffer);
397
398  *state.whole = state.buffer;
399  *whole_size = state.size;
400
401  return DWFL_E_NOERROR;
402}
403