/* $Id: lzo_stream.c,v 1.4 2003/09/30 15:22:53 mjw Exp $ */ #define __FILE_ID_INFO "$Id: lzo_stream.c,v 1.4 2003/09/30 15:22:53 mjw Exp $" #include static char __rcsid[] __attribute__((unused)) = WHAT_ID __FILE_ID_INFO; /* * Copyright (C) 2003 Hewlett-Packard Company. * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /** @file * An IOStream implementation using LZO to provide compression and decompression. * This is designed to provide reasonable compression without output latency. * Flushing an LZO stream flushes all pending data to the underlying stream. * This is essential for stream-based (e.g. networked) applications. * * A compressed data stream is a sequence of blocks. * Each block except the last is the plain data size followed by the compressed data size * and the compressed data. The last block has plain data size zero and omits the rest. * Sizes are 4-byte unsigned in network order. If the compressed size is smaller than * the plain size the block data is compressed, otherwise it is plain (uncompressed). * * This format allows compressed data to be read from a stream without reading * past the logical end of compressed data. * * @author Mike Wray */ #ifndef __KERNEL__ #include #include #include #include #include "lzo1x.h" #include "allocate.h" #include "lzo_stream.h" #include "file_stream.h" #include "marshal.h" #define dprintf(fmt, args...) fprintf(stdout, "[DEBUG] LZO>%s" fmt, __FUNCTION__, ##args) #define wprintf(fmt, args...) fprintf(stderr, "[WARN] LZO>%s" fmt, __FUNCTION__, ##args) #define iprintf(fmt, args...) fprintf(stdout, "[INFO] LZO>%s" fmt, __FUNCTION__, ##args) #define eprintf(fmt, args...) fprintf(stderr, "[ERROR] LZO>%s" fmt, __FUNCTION__, ##args) static int lzo_read(IOStream *s, void *buf, size_t size, size_t count); static int lzo_write(IOStream *s, const void *buf, size_t size, size_t count); static int lzo_print(IOStream *s, const char *msg, va_list args); static int lzo_getc(IOStream *s); static int lzo_error(IOStream *s); static int lzo_close(IOStream *s); static void lzo_free(IOStream *s); static int lzo_flush(IOStream *s); enum { LZO_WRITE = 1, LZO_READ = 2, }; /** Methods used by a gzFile* IOStream. */ static const IOMethods lzo_methods = { read: lzo_read, write: lzo_write, print: lzo_print, getc: lzo_getc, error: lzo_error, close: lzo_close, free: lzo_free, flush: lzo_flush, }; //#define PLAIN_SIZE (64 * 1024) //#define PLAIN_SIZE (128 * 1024) #define PLAIN_SIZE (512 * 1024) //#define NOCOMPRESS typedef struct LZOState { /** Flags. */ int flags; /** Error indicator. */ int error; /** Underlying stream for I/O. */ IOStream *io; /** Working memory (only needed for compression, not decompression). */ lzo_byte *memory; /** Buffer for plain (uncompressed) data. */ lzo_byte *plain; /** Size of the plain buffer. */ lzo_uint plain_size; /** Pointer into the plain buffer. */ lzo_byte *plain_ptr; /** Number of bytes of plain data available. */ lzo_uint plain_n; /** Buffer for compressed data. */ lzo_byte *comp; /** Size of the compressed buffer. */ lzo_uint comp_size; int plain_bytes; int comp_bytes; } LZOState; void LZOState_free(LZOState *z){ if(!z) return; deallocate(z->memory); deallocate(z->plain); deallocate(z->comp); deallocate(z); } /** Maximum size of compressed data for the given plain data size. * * @param plain_size size of plain data * @return maximum size of compressed data */ static int comp_size(int plain_size){ return plain_size + (plain_size / 64) + 16 + 3; } static int mode_flags(const char *mode, int *flags){ int err = 0; int r=0, w=0; if(!mode){ err = -EINVAL; goto exit; } for(; *mode; mode++){ if(*mode == 'w') w = 1; if(*mode == 'r') r = 1; } if(r + w != 1){ err = -EINVAL; goto exit; } if(r) *flags |= LZO_READ; if(w) *flags |= LZO_WRITE; exit: return err; } /** Get the stream state. * * @param s lzo stream * @return stream state. */ static inline LZOState * lzo_state(IOStream *s){ return s->data; } IOStream *lzo_stream_io(IOStream *s){ LZOState *state = lzo_state(s); return state->io; } static inline void set_error(LZOState *state, int err){ if(err < 0 && !state->error){ state->error = err; } } int lzo_stream_plain_bytes(IOStream *s){ LZOState *state = lzo_state(s); return state->plain_bytes; } int lzo_stream_comp_bytes(IOStream *s){ LZOState *state = lzo_state(s); return state->comp_bytes; } float lzo_stream_ratio(IOStream *s){ LZOState *state = lzo_state(s); float ratio = 0.0; if(state->comp_bytes){ ratio = ((float) state->comp_bytes)/((float) state->plain_bytes); } return ratio; } static inline int LZOState_writeable(LZOState *state){ return (state->flags & LZO_WRITE) != 0; } static inline int LZOState_readable(LZOState *state){ return (state->flags & LZO_READ) != 0; } LZOState * LZOState_new(IOStream *io, int flags){ int err = -ENOMEM; LZOState *z = ALLOCATE(LZOState); //dprintf(">\n"); if(!z) goto exit; z->io = io; z->flags = flags; if(LZOState_writeable(z)){ z->memory = allocate(LZO1X_1_MEM_COMPRESS); if(!z->memory) goto exit; } z->plain_size = PLAIN_SIZE; z->plain = allocate(z->plain_size); if(!z->plain) goto exit; z->plain_ptr = z->plain; z->comp_size = comp_size(z->plain_size); z->comp = allocate(z->comp_size); if(!z->comp) goto exit; err = 0; exit: if(err){ LZOState_free(z); z = NULL; } //dprintf("< z=%p\n", z); return z; } static int lzo_compress(LZOState *state){ int err = 0; int k, comp_n; //dprintf(">\n"); //dprintf(">plain=%p plain_n=%d comp=%p memory=%p\n", state->plain, state->plain_n, state->comp, state->memory); // Compress the plain buffer. err = lzo1x_1_compress(state->plain, state->plain_n, state->comp, &comp_n, state->memory); //dprintf("> err=%d plain_n=%d comp_n=%d\n", err, state->plain_n, comp_n); // Write plain size, compressed size. err = marshal_uint32(state->io, state->plain_n); if(err) goto exit; err = marshal_uint32(state->io, comp_n); if(err) goto exit; //dprintf("> write data...\n"); // Write the smaller of the compressed and plain data. if(state->plain_n < comp_n){ k = state->plain_n; err = marshal_bytes(state->io, state->plain, state->plain_n); } else { k = comp_n; err = marshal_bytes(state->io, state->comp, comp_n); } if(err) goto exit; // Total output bytes. k+= 8; //dprintf("> wrote %d bytes\n", k); state->plain_bytes += state->plain_n; state->comp_bytes += k; //dprintf("> plain=%d, comp=%d, ratio=%3.2f\n", // state->plain_bytes, state->comp_bytes, // ((float)state->comp_bytes)/((float)state->plain_bytes)); // Reset the plain buffer. state->plain_ptr = state->plain; state->plain_n = 0; err = k; exit: //dprintf("< err=%d\n", err); return err; } static int lzo_decompress(LZOState *state){ int plain_n, comp_n; int err, k; //dprintf(">\n"); err = unmarshal_uint32(state->io, &plain_n); //dprintf("> err=%d plain_n=%d\n", err, plain_n); if(err) goto exit; state->comp_bytes += 4; if(plain_n == 0) goto exit; err = unmarshal_uint32(state->io, &comp_n); //dprintf("> err=%d comp_n=%d\n", err, comp_n); if(err) goto exit; state->comp_bytes += 4; if(plain_n > state->plain_size){ err = -EINVAL; goto exit; } if(comp_n > plain_n){ //dprintf("> reading plain data %d...\n", plain_n); k = plain_n; err = unmarshal_bytes(state->io, state->plain, plain_n); state->plain_n = plain_n; } else { //dprintf("> reading comp data %d...\n", comp_n); k = comp_n; err = unmarshal_bytes(state->io, state->comp, comp_n); //dprintf("> decompress comp_n=%d\n", comp_n); err = lzo1x_decompress(state->comp, comp_n, state->plain, &state->plain_n, state->memory); //dprintf("> err=%d plain=%d state->plain_n=%d\n", err, plain_n, state->plain_n); if(err != LZO_E_OK || state->plain_n != plain_n){ // Bad. Corrupted input. err = -EINVAL; eprintf("> Corrupted!\n"); goto exit; } } state->comp_bytes += k; state->plain_bytes += state->plain_n; state->plain_ptr = state->plain; err = k; exit: //dprintf("< err=%d\n", err); return err; } /** Write to the underlying stream using fwrite(); * * @param stream destination * @param buf data * @param size size of data elements * @param count number of data elements to write * @return number of data elements written */ static int lzo_write(IOStream *s, const void *buf, size_t size, size_t count){ int err = 0; int n = size * count; // Total number of bytes to write. int chunk; // Size of chunk to write. int remaining; // Number of bytes remaining to write. int space; // Amount of space left in plain buffer. LZOState *state = lzo_state(s); #ifdef NOCOMPRESS //dprintf("> buf=%p size=%d count=%d\n", buf, size, count); err = IOStream_write(state->io, buf, size, count); //dprintf("< err=%d\n", err); #else //dprintf("> buf=%p size=%d count=%d n=%d\n", buf, size, count, n); remaining = n; space = state->plain_size - state->plain_n; //dprintf("> plain=%p plain_ptr=%p plain_n=%d space=%d\n", // state->plain, state->plain_ptr, state->plain_n, space); while(remaining){ chunk = remaining; if(chunk > space) chunk = space; //dprintf("> memcpy %p %p %d\n", state->plain_ptr, buf, chunk); memcpy(state->plain_ptr, buf, chunk); remaining -= chunk; space -= chunk; state->plain_ptr += chunk; state->plain_n += chunk; if(space == 0){ // Input buffer is full. Compress and write it. err = lzo_compress(state); if(err < 0) goto exit; space = state->plain_size - state->plain_n; } } err = (size > 1 ? n / size : n); exit: set_error(state, err); #endif return err; } /** Read from the underlying stream. * * @param stream input * @param buf where to put input * @param size size of data elements * @param count number of data elements to read * @return number of data elements read */ static int lzo_read(IOStream *s, void *buf, size_t size, size_t count){ int err = 0; int k = 0; // Number of (plain) bytes read. int remaining = size * count; // Number of bytes remaining to read. int chunk; // Size of chunk to read. LZOState *state = lzo_state(s); #ifdef NOCOMPRESS //dprintf("> buf=%p size=%d count=%d\n", buf, size, count); err = IOStream_read(state->io, buf, size, count); //dprintf("< err=%d\n", err); #else if(!(state->flags & LZO_READ)){ err = -EINVAL; goto exit; } while(remaining){ if(state->plain_n == 0){ // No more plain input, decompress some more. err = lzo_decompress(state); if(err < 0) goto exit; // Stop reading if there is no more input. if(err == 0 || state->plain_n == 0) break; } chunk = remaining; if(chunk > state->plain_n) chunk = state->plain_n; memcpy(buf, state->plain_ptr, chunk); k += chunk; buf += chunk; state->plain_ptr += chunk; state->plain_n -= chunk; remaining -= chunk; } err = k; exit: set_error(state, err); #endif return err; } /** Print to the underlying stream. * Returns 0 if the formatted output is too big for the internal buffer. * * @param s lzo stream * @param msg format to use * @param args arguments * @return result of the print */ static int lzo_print(IOStream *s, const char *msg, va_list args){ char buf[1024]; int buf_n = sizeof(buf); int n; LZOState *state = lzo_state(s); if(!LZOState_writeable(state)){ n = -EINVAL; goto exit; } n = vsnprintf(buf, buf_n, (char*)msg, args); if(n < 0) goto exit; if(n > buf_n){ n = 0; } else { n = lzo_write(s, buf, 1, n); } exit: return n; } /** Read a character from the underlying stream * * @param s lzo stream * @return character read, IOSTREAM_EOF on end of file (or error) */ static int lzo_getc(IOStream *s){ int err; char c; err = lzo_read(s, &c, 1, 1); if(err < 1) c = EOF; err = (c==EOF ? IOSTREAM_EOF : c); return err; } /** Flush any pending input to the underlying stream. * * @param s lzo stream * @return 0 on success, error code otherwise */ static int lzo_flush(IOStream *s){ int err = 0; LZOState *state = lzo_state(s); //dprintf(">\n"); #ifdef NOCOMPRESS err = IOStream_flush(state->io); #else if(!LZOState_writeable(state)){ err = -EINVAL; goto exit; } if(state->plain_n){ err = lzo_compress(state); if(err < 0) goto exit; } err = IOStream_flush(state->io); exit: set_error(state, err); #endif //dprintf("< err=%d\n", err); return (err < 0 ? err : 0); } /** Check if a stream has an error. * * @param s lzo stream * @return code if has an error, 0 otherwise */ static int lzo_error(IOStream *s){ int err = 0; LZOState *state = lzo_state(s); err = state->error; if(err) goto exit; err = IOStream_error(state->io); exit: return err; } int lzo_stream_finish(IOStream *s){ int err = 0; LZOState *state = lzo_state(s); if(!LZOState_writeable(state)){ err = -EINVAL; goto exit; } err = lzo_flush(s); if(err < 0) goto exit; err = marshal_int32(state->io, 0); exit: return err; } /** Close an lzo stream. * * @param s lzo stream to close * @return result of the close */ static int lzo_close(IOStream *s){ int err = 0; LZOState *state = lzo_state(s); #ifdef NOCOMPRESS err = IOStream_close(state->io); #else if(LZOState_writeable(state)){ err = lzo_stream_finish(s); } err = IOStream_close(state->io); set_error(state, err); #endif return err; } /** Free an lzo stream. * * @param s lzo stream */ static void lzo_free(IOStream *s){ LZOState *state = lzo_state(s); IOStream_free(state->io); LZOState_free(state); s->data = NULL; } /** Create an lzo stream for an IOStream. * * @param io stream to wrap * @return new IOStream using f for i/o */ IOStream *lzo_stream_new(IOStream *io, const char *mode){ int err = -ENOMEM; int flags = 0; IOStream *zio = NULL; LZOState *state = NULL; zio = ALLOCATE(IOStream); if(!zio) goto exit; err = mode_flags(mode, &flags); if(err) goto exit; state = LZOState_new(io, flags); if(!state) goto exit; err = 0; zio->data = state; zio->methods = &lzo_methods; exit: if(err){ if(state) LZOState_free(state); if(zio) deallocate(zio); zio = NULL; } return zio; } /** IOStream version of fdopen(). * * @param fd file descriptor * @param flags giving the mode to open in (as for fdopen()) * @return new stream for the open file, or NULL if failed */ IOStream *lzo_stream_fdopen(int fd, const char *mode){ int err = -ENOMEM; IOStream *io = NULL, *zio = NULL; io = file_stream_fdopen(fd, mode); if(!io) goto exit; zio = lzo_stream_new(io, mode); if(!io) goto exit; err = 0; exit: if(err){ IOStream_free(io); IOStream_free(zio); zio = NULL; } return zio; } #endif