/** * Creates an incremental zlib deflater for `len' bytes starting at `data', * with specified compression `level'. * * @param data data to compress; if NULL, will be incrementally given * @param len length of data to compress (if data not NULL) or estimation * @param dest where compressed data should go, or NULL if allocated * @param destlen length of supplied output buffer, if dest != NULL * @param level compression level, between 0 and 9. * * @return new deflater, or NULL if error. */ static zlib_deflater_t * zlib_deflater_alloc( const void *data, size_t len, void *dest, size_t destlen, int level) { zlib_deflater_t *zd; z_streamp outz; int ret; g_assert(size_is_non_negative(len)); g_assert(size_is_non_negative(destlen)); g_assert(level == Z_DEFAULT_COMPRESSION || (level >= 0 && level <= 9)); WALLOC(outz); outz->zalloc = zlib_alloc_func; outz->zfree = zlib_free_func; outz->opaque = NULL; ret = deflateInit(outz, level); if (ret != Z_OK) { WFREE(outz); g_carp("%s(): unable to initialize compressor: %s", G_STRFUNC, zlib_strerror(ret)); return NULL; } WALLOC0(zd); zd->zs.magic = ZLIB_DEFLATER_MAGIC; zd->zs.z = outz; zd->zs.closed = FALSE; zlib_stream_init(&zd->zs, data, len, dest, destlen); return zd; }
/** * Get rid of the driver's private data. */ static void tx_deflate_destroy(txdrv_t *tx) { struct attr *attr = tx->opaque; int i; int ret; g_assert(attr->outz); for (i = 0; i < BUFFER_COUNT; i++) { struct buffer *b = &attr->buf[i]; wfree(b->arena, attr->buffer_size); } /* * We ignore Z_DATA_ERROR errors (discarded data, probably). */ ret = deflateEnd(attr->outz); if (Z_OK != ret && Z_DATA_ERROR != ret) g_warning("while freeing compressor for peer %s: %s", gnet_host_to_string(&tx->host), zlib_strerror(ret)); WFREE(attr->outz); cq_cancel(&attr->tm_ev); WFREE(attr); }
static bool decompress_with_fastlz(JCR *jcr, const char *last_fname, char **data, uint32_t *length, uint32_t comp_magic, bool sparse, bool want_data_stream) { int zstat; zfast_stream stream; zfast_stream_compressor compressor = COMPRESSOR_FASTLZ; char ec1[50]; /* Buffer printing huge values */ switch (comp_magic) { case COMPRESS_FZ4L: case COMPRESS_FZ4H: compressor = COMPRESSOR_LZ4; break; } /* * NOTE! We only use uInt and Bytef because they are * needed by the fastlz routines, they should not otherwise * be used in Bareos. */ memset(&stream, 0, sizeof(stream)); stream.next_in = (Bytef *)*data + sizeof(comp_stream_header); stream.avail_in = (uInt)*length - sizeof(comp_stream_header); if (sparse && want_data_stream) { stream.next_out = (Bytef *)jcr->compress.inflate_buffer + OFFSET_FADDR_SIZE; stream.avail_out = (uInt)jcr->compress.inflate_buffer_size - OFFSET_FADDR_SIZE; } else { stream.next_out = (Bytef *)jcr->compress.inflate_buffer; stream.avail_out = (uInt)jcr->compress.inflate_buffer_size; } Dmsg2(400, "Comp_len=%d msglen=%d\n", stream.avail_in, *length); if ((zstat = fastlzlibDecompressInit(&stream)) != Z_OK) { goto cleanup; } if ((zstat = fastlzlibSetCompressor(&stream, compressor)) != Z_OK) { goto cleanup; } while (1) { zstat = fastlzlibDecompress(&stream); switch (zstat) { case Z_BUF_ERROR: /* * The buffer size is too small, try with a bigger one */ jcr->compress.inflate_buffer_size = jcr->compress.inflate_buffer_size + (jcr->compress.inflate_buffer_size >> 1); jcr->compress.inflate_buffer = check_pool_memory_size(jcr->compress.inflate_buffer, jcr->compress.inflate_buffer_size); if (sparse && want_data_stream) { stream.next_out = (Bytef *)jcr->compress.inflate_buffer + OFFSET_FADDR_SIZE; stream.avail_out = (uInt)jcr->compress.inflate_buffer_size - OFFSET_FADDR_SIZE; } else { stream.next_out = (Bytef *)jcr->compress.inflate_buffer; stream.avail_out = (uInt)jcr->compress.inflate_buffer_size; } continue; case Z_OK: case Z_STREAM_END: break; default: goto cleanup; } break; } /* * We return a decompressed data stream with the fileoffset encoded when this was a sparse stream. */ if (sparse && want_data_stream) { memcpy(jcr->compress.inflate_buffer, *data, OFFSET_FADDR_SIZE); } *data = jcr->compress.inflate_buffer; *length = stream.total_out; Dmsg2(400, "Write uncompressed %d bytes, total before write=%s\n", *length, edit_uint64(jcr->JobBytes, ec1)); fastlzlibDecompressEnd(&stream); return true; cleanup: Qmsg(jcr, M_ERROR, 0, _("Uncompression error on file %s. ERR=%s\n"), last_fname, zlib_strerror(zstat)); fastlzlibDecompressEnd(&stream); return false; }
static bool decompress_with_zlib(JCR *jcr, const char *last_fname, char **data, uint32_t *length, bool sparse, bool with_header, bool want_data_stream) { char ec1[50]; /* Buffer printing huge values */ uLong compress_len; const unsigned char *cbuf; char *wbuf; int status, real_compress_len; /* * NOTE! We only use uLong and Byte because they are * needed by the zlib routines, they should not otherwise * be used in Bareos. */ if (sparse && want_data_stream) { wbuf = jcr->compress.inflate_buffer + OFFSET_FADDR_SIZE; compress_len = jcr->compress.inflate_buffer_size - OFFSET_FADDR_SIZE; } else { wbuf = jcr->compress.inflate_buffer; compress_len = jcr->compress.inflate_buffer_size; } /* * See if this is a compressed stream with the new compression header or an old one. */ if (with_header) { cbuf = (const unsigned char*)*data + sizeof(comp_stream_header); real_compress_len = *length - sizeof(comp_stream_header); } else { cbuf = (const unsigned char*)*data; real_compress_len = *length; } Dmsg2(400, "Comp_len=%d msglen=%d\n", compress_len, *length); while ((status = uncompress((Byte *)wbuf, &compress_len, (const Byte *)cbuf, (uLong)real_compress_len)) == Z_BUF_ERROR) { /* * The buffer size is too small, try with a bigger one */ jcr->compress.inflate_buffer_size = jcr->compress.inflate_buffer_size + (jcr->compress.inflate_buffer_size >> 1); jcr->compress.inflate_buffer = check_pool_memory_size(jcr->compress.inflate_buffer, jcr->compress.inflate_buffer_size); if (sparse && want_data_stream) { wbuf = jcr->compress.inflate_buffer + OFFSET_FADDR_SIZE; compress_len = jcr->compress.inflate_buffer_size - OFFSET_FADDR_SIZE; } else { wbuf = jcr->compress.inflate_buffer; compress_len = jcr->compress.inflate_buffer_size; } Dmsg2(400, "Comp_len=%d msglen=%d\n", compress_len, *length); } if (status != Z_OK) { Qmsg(jcr, M_ERROR, 0, _("Uncompression error on file %s. ERR=%s\n"), last_fname, zlib_strerror(status)); return false; } /* * We return a decompressed data stream with the fileoffset encoded when this was a sparse stream. */ if (sparse && want_data_stream) { memcpy(jcr->compress.inflate_buffer, *data, OFFSET_FADDR_SIZE); } *data = jcr->compress.inflate_buffer; *length = compress_len; Dmsg2(400, "Write uncompressed %d bytes, total before write=%s\n", compress_len, edit_uint64(jcr->JobBytes, ec1)); return true; }
/** * Incrementally process more data. * * @param zs the zlib stream object * @param amount amount of data to process * @param maxout maximum length of dynamically-allocated buffer (0 = none) * @param may_close whether to allow closing when all data was consumed * @param finish whether this is the last data to process * * @return -1 on error, 1 if work remains, 0 when done. */ static int zlib_stream_process_step(zlib_stream_t *zs, int amount, size_t maxout, bool may_close, bool finish) { z_streamp z; int remaining; int process; bool finishing; int ret = 0; g_assert(amount > 0); g_assert(!zs->closed); z = zs->z; g_assert(z != NULL); /* Stream not closed yet */ /* * Compute amount of input data to process. */ remaining = zs->inlen - ptr_diff(z->next_in, zs->in); g_assert(remaining >= 0); process = MIN(remaining, amount); finishing = process == remaining; /* * Process data. */ z->avail_in = process; resume: switch (zs->magic) { case ZLIB_DEFLATER_MAGIC: ret = deflate(z, finishing && finish ? Z_FINISH : 0); break; case ZLIB_INFLATER_MAGIC: ret = inflate(z, Z_SYNC_FLUSH); break; } switch (ret) { case Z_OK: if (0 == z->avail_out) { if (zlib_stream_grow_output(zs, maxout)) goto resume; /* Process remaining input */ goto error; /* Cannot continue */ } return 1; /* Need to call us again */ /* NOTREACHED */ case Z_BUF_ERROR: /* Output full or need more input to continue */ if (0 == z->avail_out) { if (zlib_stream_grow_output(zs, maxout)) goto resume; /* Process remaining input */ goto error; /* Cannot continue */ } if (0 == z->avail_in) return 1; /* Need to call us again */ goto error; /* Cannot continue */ /* NOTREACHED */ case Z_STREAM_END: /* Reached end of input stream */ g_assert(finishing); /* * Supersede the output length to let them probe how much data * was processed once the stream is closed, through calls to * zlib_deflater_outlen() or zlib_inflater_outlen(). */ zs->outlen = ptr_diff(z->next_out, zs->out); g_assert(zs->outlen > 0); if (may_close) { switch (zs->magic) { case ZLIB_DEFLATER_MAGIC: ret = deflateEnd(z); break; case ZLIB_INFLATER_MAGIC: ret = inflateEnd(z); break; } if (ret != Z_OK) { g_carp("%s(): while freeing zstream: %s", G_STRFUNC, zlib_strerror(ret)); } WFREE(z); zs->z = NULL; } zs->closed = TRUE; /* Signals processing stream done */ return 0; /* Done */ /* NOTREACHED */ default: break; } /* FALL THROUGH */ error: g_carp("%s(): error during %scompression: %s " "(avail_in=%u, avail_out=%u, total_in=%lu, total_out=%lu)", G_STRFUNC, ZLIB_DEFLATER_MAGIC == zs->magic ? "" : "de", zlib_strerror(ret), z->avail_in, z->avail_out, z->total_in, z->total_out); if (may_close) { switch (zs->magic) { case ZLIB_DEFLATER_MAGIC: ret = deflateEnd(z); case ZLIB_INFLATER_MAGIC: ret = inflateEnd(z); break; } if (ret != Z_OK && ret != Z_DATA_ERROR) { g_carp("%s(): while freeing stream: %s", G_STRFUNC, zlib_strerror(ret)); } WFREE(z); zs->z = NULL; } return -1; /* Error! */ }
/** * Initialize the driver. * * @return NULL if there is an initialization problem. */ static void * tx_deflate_init(txdrv_t *tx, void *args) { struct attr *attr; struct tx_deflate_args *targs = args; z_streamp outz; int ret; int i; g_assert(tx); g_assert(NULL != targs->cb); WALLOC(outz); outz->zalloc = zlib_alloc_func; outz->zfree = zlib_free_func; outz->opaque = NULL; /* * Reduce memory requirements for deflation when running as an ultrapeer. * * Memory used for deflation is: * * (1 << (window_bits +2)) + (1 << (mem_level + 9)) * * For leaves, we use window_bits = 15 and mem_level = 9, which makes * for 128 KiB + 256 KiB = 384 KiB per connection (TX side). * * For ultra peers, we use window_bits = 14 and mem_level = 6, so this * uses 64 KiB + 32 KiB = 96 KiB only. * * Since ultra peers have many more connections than leaves, the memory * savings are drastic, yet compression levels remain around 50% (varies * depending on the nature of the traffic, of course). * * --RAM, 2009-04-09 * * For Ultra <-> Ultra connections we use window_bits = 15 and mem_level = 9 * and request a best compression because the amount of ultra connections * is far less than the number of leaf connections and modern machines * can cope with a "best" compression overhead. * * This is now controlled with the "reduced" argument, so this layer does * not need to know whether we're an ultra node or even what an ultra * node is... It just knows whether we have to setup a fully compressed * connection or a reduced one (both in terms of memory usage and level * of compression). * * --RAM, 2011-11-29 */ { int window_bits = MAX_WBITS; /* Must be 8 .. MAX_WBITS */ int mem_level = MAX_MEM_LEVEL; /* Must be 1 .. MAX_MEM_LEVEL */ int level = Z_BEST_COMPRESSION; if (targs->reduced) { /* Ultra -> Leaf connection */ window_bits = 14; mem_level = 6; level = Z_DEFAULT_COMPRESSION; } g_assert(window_bits >= 8 && window_bits <= MAX_WBITS); g_assert(mem_level >= 1 && mem_level <= MAX_MEM_LEVEL); g_assert(level == Z_DEFAULT_COMPRESSION || (level >= Z_BEST_SPEED && level <= Z_BEST_COMPRESSION)); ret = deflateInit2(outz, level, Z_DEFLATED, targs->gzip ? (-window_bits) : window_bits, mem_level, Z_DEFAULT_STRATEGY); } if (Z_OK != ret) { g_warning("unable to initialize compressor for peer %s: %s", gnet_host_to_string(&tx->host), zlib_strerror(ret)); WFREE(outz); return NULL; } WALLOC0(attr); attr->cq = targs->cq; attr->cb = targs->cb; attr->buffer_size = targs->buffer_size; attr->buffer_flush = targs->buffer_flush; attr->nagle = booleanize(targs->nagle); attr->gzip.enabled = targs->gzip; attr->outz = outz; attr->tm_ev = NULL; for (i = 0; i < BUFFER_COUNT; i++) { struct buffer *b = &attr->buf[i]; b->arena = b->wptr = b->rptr = walloc(attr->buffer_size); b->end = &b->arena[attr->buffer_size]; } attr->fill_idx = 0; attr->send_idx = -1; /* Signals: none ready */ if (attr->gzip.enabled) { /* See RFC 1952 - GZIP file format specification version 4.3 */ static const unsigned char header[] = { 0x1f, 0x8b, /* gzip magic */ 0x08, /* compression method: deflate */ 0, /* flags: none */ 0, 0, 0, 0, /* modification time: unavailable */ 0, /* extra flags: none */ 0xff, /* filesystem: unknown */ }; struct buffer *b; b = &attr->buf[attr->fill_idx]; /* Buffer we fill */ g_assert(sizeof header <= (size_t) (b->end - b->wptr)); b->wptr = mempcpy(b->wptr, header, sizeof header); attr->gzip.crc = crc32(0, NULL, 0); attr->gzip.size = 0; } tx->opaque = attr; /* * Register our service routine to the lower layer. */ tx_srv_register(tx->lower, deflate_service, tx); return tx; /* OK */ }
/** * Compress as much data as possible to the output buffer, sending data * as we go along. * * @return the amount of input bytes that were consumed ("added"), -1 on error. */ static int deflate_add(txdrv_t *tx, const void *data, int len) { struct attr *attr = tx->opaque; z_streamp outz = attr->outz; int added = 0; if (tx_deflate_debugging(9)) { g_debug("TX %s: (%s) given %u bytes (buffer #%d, nagle %s, " "unflushed %zu) [%c%c]%s", G_STRFUNC, gnet_host_to_string(&tx->host), len, attr->fill_idx, (attr->flags & DF_NAGLE) ? "on" : "off", attr->unflushed, (attr->flags & DF_FLOWC) ? 'C' : '-', (attr->flags & DF_FLUSH) ? 'f' : '-', (tx->flags & TX_ERROR) ? " ERROR" : ""); } /* * If an error was already reported, the whole deflate stream is dead * and we cannot accept any more data. */ if G_UNLIKELY(tx->flags & TX_ERROR) return -1; while (added < len) { struct buffer *b = &attr->buf[attr->fill_idx]; /* Buffer we fill */ int ret; int old_added = added; bool flush_started = (attr->flags & DF_FLUSH) ? TRUE : FALSE; int old_avail; const char *in, *old_in; /* * Prepare call to deflate(). */ outz->next_out = cast_to_pointer(b->wptr); outz->avail_out = old_avail = b->end - b->wptr; in = data; old_in = &in[added]; outz->next_in = deconstify_pointer(old_in); outz->avail_in = len - added; g_assert(outz->avail_out > 0); g_assert(outz->avail_in > 0); /* * Compress data. * * If we previously started to flush, continue the operation, now * that we have more room available for the output. */ ret = deflate(outz, flush_started ? Z_SYNC_FLUSH : 0); if (Z_OK != ret) { attr->flags |= DF_SHUTDOWN; (*attr->cb->shutdown)(tx->owner, "Compression failed: %s", zlib_strerror(ret)); return -1; } /* * Update the parameters. */ b->wptr = cast_to_pointer(outz->next_out); added = ptr_diff(outz->next_in, in); g_assert(added >= old_added); attr->unflushed += added - old_added; attr->flushed += old_avail - outz->avail_out; if (NULL != attr->cb->add_tx_deflated) attr->cb->add_tx_deflated(tx->owner, old_avail - outz->avail_out); if (attr->gzip.enabled) { size_t r; r = ptr_diff(outz->next_in, old_in); attr->gzip.size += r; attr->gzip.crc = crc32(attr->gzip.crc, cast_to_constpointer(old_in), r); } if (tx_deflate_debugging(9)) { g_debug("TX %s: (%s) deflated %d bytes into %d " "(buffer #%d, nagle %s, flushed %zu, unflushed %zu) [%c%c]", G_STRFUNC, gnet_host_to_string(&tx->host), added, old_avail - outz->avail_out, attr->fill_idx, (attr->flags & DF_NAGLE) ? "on" : "off", attr->flushed, attr->unflushed, (attr->flags & DF_FLOWC) ? 'C' : '-', (attr->flags & DF_FLUSH) ? 'f' : '-'); } /* * If we filled the output buffer, check whether we have a pending * send buffer. If we do, we cannot process more data. Otherwise * send it now and continue. */ if (0 == outz->avail_out) { if (attr->send_idx >= 0) { deflate_set_flowc(tx, TRUE); /* Enter flow control */ return added; } deflate_rotate_and_send(tx); /* Can set TX_ERROR */ if (tx->flags & TX_ERROR) return -1; } /* * If we were flushing and we consumed all the input, then * the flush is done and we're starting normal compression again. * * This must be done after we made sure that we had enough output * space avaialable. */ if (flush_started && 0 == outz->avail_in) deflate_flushed(tx); } g_assert(0 == outz->avail_in); /* * Start Nagle if not already on. */ if (attr->flags & DF_NAGLE) deflate_nagle_delay(tx); else deflate_nagle_start(tx); /* * We're going to ask for a flush if not already started yet and the * amount of bytes we have written since the last flush is greater * than attr->buffer_flush. */ if (attr->unflushed > attr->buffer_flush) { if (!deflate_flush(tx)) return -1; } return added; }
/** * Flush compression within filling buffer. * * @return success status, failure meaning we shutdown. */ static bool deflate_flush(txdrv_t *tx) { struct attr *attr = tx->opaque; z_streamp outz = attr->outz; struct buffer *b; int ret; int old_avail; retry: b = &attr->buf[attr->fill_idx]; /* Buffer we fill */ if (tx_deflate_debugging(9)) { g_debug("TX %s: (%s) flushing %zu bytes " "(buffer #%d, flushed %zu, unflushed %zu) [%c%c]", G_STRFUNC, gnet_host_to_string(&tx->host), b->wptr - b->rptr, attr->fill_idx, attr->flushed, attr->unflushed, (attr->flags & DF_FLOWC) ? 'C' : '-', (attr->flags & DF_FLUSH) ? 'f' : '-'); } /* * Prepare call to deflate(). * * We force avail_in to 0, and don't touch next_in: no input should * be consumed. */ outz->next_out = cast_to_pointer(b->wptr); outz->avail_out = old_avail = b->end - b->wptr; outz->avail_in = 0; g_assert(outz->avail_out > 0); ret = deflate(outz, (tx->flags & TX_CLOSING) ? Z_FINISH : Z_SYNC_FLUSH); switch (ret) { case Z_BUF_ERROR: /* Nothing to flush */ goto done; case Z_OK: case Z_STREAM_END: break; default: attr->flags |= DF_SHUTDOWN; tx_error(tx); /* XXX: The callback must not destroy the tx! */ (*attr->cb->shutdown)(tx->owner, "Compression flush failed: %s", zlib_strerror(ret)); return FALSE; } { size_t written; written = old_avail - outz->avail_out; b->wptr += written; attr->flushed += written; if (NULL != attr->cb->add_tx_deflated) attr->cb->add_tx_deflated(tx->owner, written); } /* * Check whether avail_out is 0. * * If it is, then we lacked room to complete the flush. Try to send the * buffer and continue. */ if (0 == outz->avail_out) { if (attr->send_idx >= 0) { /* Send buffer not sent yet */ attr->flags |= DF_FLUSH; /* In flush mode */ deflate_set_flowc(tx, TRUE); /* Starting flow-control */ return TRUE; } deflate_rotate_and_send(tx); /* Can set TX_ERROR */ if (tx->flags & TX_ERROR) return FALSE; goto retry; } done: deflate_flushed(tx); return TRUE; /* Fully flushed */ }