aboutsummaryrefslogtreecommitdiffstats
path: root/erts/emulator/drivers/common/gzio.c
blob: 86c3b07ceac1c5b8ad484a8f97785f20ea58ac4b (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
/*
 * Original version by Jean-loup Gailly. Modified for use by the
 * Erlang run-time system and efile_driver; names of all external
 * functions changed to avoid conflicts with the official gzio.c file.
 *
 * gzio.c -- IO on .gz files
 * Copyright (C) 1995-1996 Jean-loup Gailly.
 * For conditions of distribution and use, see copyright notice in zlib.h
 */
/* %ExternalCopyright% */

#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif
#include <stdio.h>
#include <string.h> /* ssize_t on Mac OS X */
#include <errno.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <ctype.h>

#include "erl_driver.h"
#include "sys.h"

#include "gzio_zutil.h"
#include "erl_zlib.h"
#include "gzio.h"

static int gz_magic[2] = {0x1f, 0x8b}; /* gzip magic header */

/* ===========================================================================
   Uncompresses the buffer given and returns a pointer to a binary.
   If the buffer was not compressed with gzip, the buffer contents
   will be copied unchanged into the binary.

   If a `gzip' header was found, but there were subsequent errors,
   a NULL pointer is returned.
*/

ErlDrvBinary*
erts_gzinflate_buffer(char* start, uLong size)
{
    ErlDrvBinary* bin;
    ErlDrvBinary* bin2;
    z_stream zstr;
    unsigned char* bptr;

    /*
     * Check for the magic bytes beginning a GZIP header.
     */
    bptr = (unsigned char *) start;
    if (size < 2 || bptr[0] != gz_magic[0] || bptr[1] != gz_magic[1]) {
	/* No GZIP header -- just copy the data into a new binary */
	if ((bin = driver_alloc_binary(size)) == NULL) {
	    return NULL;
	}
	memcpy(bin->orig_bytes, start, size);
	return bin;
    }

    /*
     * The magic bytes for a GZIP header are there. Now try to decompress.
     * It is an error if the GZIP header is not correct.
     */

    zstr.next_in = (unsigned char*) start;
    zstr.avail_in = size;
    erl_zlib_alloc_init(&zstr);
    size *= 2;
    if ((bin = driver_alloc_binary(size)) == NULL) {
	return NULL;
    }
    if (inflateInit2(&zstr, 15+16) != Z_OK) { /* Decode GZIP format */
	driver_free(bin);
	return NULL;
    }
    for (;;) {
	int status;

	zstr.next_out = (unsigned char *) bin->orig_bytes + zstr.total_out;
	zstr.avail_out = size - zstr.total_out;
	status = inflate(&zstr, Z_NO_FLUSH);
	if (status == Z_OK) {
	    size *= 2;
	    if ((bin2 = driver_realloc_binary(bin, size)) == NULL) {
	    error:
		driver_free_binary(bin);
		inflateEnd(&zstr);
		return NULL;
	    }
	    bin = bin2;
	} else if (status == Z_STREAM_END) {
	    if ((bin2 = driver_realloc_binary(bin, zstr.total_out)) == NULL) {
		goto error;
	    }
	    inflateEnd(&zstr);
	    return bin2;
	} else {
	    goto error;
	}
    }
}

/* ===========================================================================
   Compresses the buffer given and returns a pointer to a binary.
   A NULL pointer is returned if any error occurs.
   Writes a gzip header as well.
*/

#define GZIP_HD_SIZE 10
#define GZIP_TL_SIZE 8

#define GZIP_X_SIZE (GZIP_HD_SIZE+GZIP_TL_SIZE)

ErlDrvBinary*
erts_gzdeflate_buffer(char* start, uLong size)
{
    z_stream c_stream; /* compression stream */
    ErlDrvBinary* bin;
    ErlDrvBinary* bin2;
    uLong    crc;     /* crc32 of uncompressed data */
    uLong    szIn;
    Byte* ptr;
    int comprLen = size + (size/1000) + 1 + 12; /* see zlib.h */

    crc = crc32(0L, Z_NULL, 0);
    erl_zlib_alloc_init(&c_stream);

    if (deflateInit2(&c_stream, Z_DEFAULT_COMPRESSION,
		     Z_DEFLATED, -MAX_WBITS, DEF_MEM_LEVEL, 0) != Z_OK)
	return NULL;

    if ((bin = driver_alloc_binary(comprLen+GZIP_X_SIZE)) == NULL)
	return NULL;
    sprintf(bin->orig_bytes, "%c%c%c%c%c%c%c%c%c%c", gz_magic[0], gz_magic[1],
	    Z_DEFLATED, 0 /*flags*/, 0,0,0,0 /*time*/, 0 /*xflags*/, OS_CODE);

    c_stream.next_out = ((Byte*) bin->orig_bytes)+GZIP_HD_SIZE;
    c_stream.avail_out = (uInt) bin->orig_size - GZIP_HD_SIZE;
    c_stream.next_in  = (Byte*) start;
    c_stream.avail_in = (uInt) size;

    if (deflate(&c_stream, Z_FINISH) != Z_STREAM_END) {
	driver_free_binary(bin);
	return NULL;	
    }
    crc = crc32(crc, (unsigned char*)start, size);
    ptr = c_stream.next_out;
    szIn = c_stream.total_in;

    *ptr++ = (crc & 0xff); crc >>= 8;
    *ptr++ = (crc & 0xff); crc >>= 8;
    *ptr++ = (crc & 0xff); crc >>= 8;
    *ptr++ = (crc & 0xff); crc >>= 8;

    *ptr++ = (szIn & 0xff); szIn >>= 8;
    *ptr++ = (szIn & 0xff); szIn >>= 8;
    *ptr++ = (szIn & 0xff); szIn >>= 8;
    *ptr++ = (szIn & 0xff); szIn >>= 8;

    if (deflateEnd(&c_stream) != Z_OK) {
	driver_free_binary(bin);
	return NULL;	
    }	
    size = ptr - (Byte*)bin->orig_bytes;

    if ((bin2 = driver_realloc_binary(bin, size)) == NULL)
	driver_free_binary(bin);
    return bin2;
}