/*
* compressed.c
* Layered data source for compressed files.
*
* Copyright (c) 2003 Christoph Pfisterer
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use, copy,
* modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "global.h"
#include <signal.h>
#include <sys/wait.h>
#define DEBUG 0
#if !defined(FD_ZERO)
#define DECOMPRESS 0
#warning Transparent decompression disabled, select() macros not defined
#elif defined(__amigaos__) && !defined(__ixemul__)
#define DECOMPRESS 0
#warning Transparent decompression disabled, ixemul not available
#else
#define DECOMPRESS 1
#endif
/*
* types
*/
#if DECOMPRESS
typedef struct compressed_source {
SOURCE c;
u8 offset, write_pos, write_max;
int write_pipe, read_pipe, nfds;
pid_t pid;
} COMPRESSED_SOURCE;
#endif
/*
* helper functions
*/
static void handle_compressed(SECTION *section, int level,
int off, const char *program);
#if DECOMPRESS
static SOURCE *init_compressed_source(SOURCE *foundation, u8 offset, u8 size,
const char *program);
static u8 read_compressed(SOURCE *s, u8 pos, u8 len, void *buf);
static void close_compressed(SOURCE *s);
#endif
/*
* compressed file detection
*/
void detect_compressed(SECTION *section, int level)
{
int fill, off, sector;
unsigned char *buf;
fill = get_buffer(section, 0, 4096, (void **)&buf);
/* look for signatures at sector beginnings */
for (off = 0; off + 512 <= fill; off += 512) {
sector = off >> 9;
/* compress */
if (buf[off] == 037 && buf[off+1] == 0235) {
if (sector > 0)
print_line(level, "compress-compressed data at sector %d", sector);
else
print_line(level, "compress-compressed data");
handle_compressed(section, level, off, "gzip");
break;
}
/* gzip */
if (buf[off] == 037 && (buf[off+1] == 0213 || buf[off+1] == 0236)) {
if (sector > 0)
print_line(level, "gzip-compressed data at sector %d", sector);
else
print_line(level, "gzip-compressed data");
handle_compressed(section, level, off, "gzip");
break;
}
/* bzip2 */
if (memcmp(buf + off, "BZh", 3) == 0) {
if (sector > 0)
print_line(level, "bzip2-compressed data at sector %d", sector);
else
print_line(level, "bzip2-compressed data");
handle_compressed(section, level, off, "bzip2");
break;
}
}
}
static void handle_compressed(SECTION *section, int level,
int off, const char *program)
{
#if DECOMPRESS
SOURCE *s;
u8 size;
/* create decompression data source */
size = section->size;
if (size > 0)
size -= off;
s = init_compressed_source(section->source,
section->pos + off, size, program);
analyze_source(s, level + 1);
close_source(s);
#else
print_line(level + 1, "Decompression disabled on this system");
#endif
}
/*
* initialize the decompression
*/
#if DECOMPRESS
static SOURCE *init_compressed_source(SOURCE *foundation, u8 offset, u8 size,
const char *program)
{
COMPRESSED_SOURCE *cs;
int write_pipe[2], read_pipe[2], flags;
cs = (COMPRESSED_SOURCE *)malloc(sizeof(COMPRESSED_SOURCE));
if (cs == NULL)
bailout("Out of memory");
memset(cs, 0, sizeof(COMPRESSED_SOURCE));
cs->c.sequential = 1;
cs->c.seq_pos = 0;
cs->c.foundation = foundation;
cs->c.read_bytes = read_compressed;
cs->c.close = close_compressed;
/* size is not known in advance by definition */
cs->offset = offset;
cs->write_pos = 0;
cs->write_max = size;
/* open "gzip -dc" in a dual pipe */
if (pipe(write_pipe) < 0)
bailoute("pipe for decompression");
if (pipe(read_pipe) < 0)
bailoute("pipe for decompression");
cs->write_pipe = write_pipe[1];
cs->read_pipe = read_pipe[0];
cs->pid = fork();
if (cs->pid < 0) {
bailoute("fork");
}
if (cs->pid == 0) { /* we're the child process */
/* set up pipe */
dup2(write_pipe[0], 0);
if (write_pipe[0] > 2)
close(write_pipe[0]);
close(write_pipe[1]);
close(read_pipe[0]);
dup2(read_pipe[1], 1);
if (read_pipe[1] > 2)
close(read_pipe[1]);
/* execute decompressor (gzip or bzip2) */
execlp(program, program, "-dc", NULL);
exit(0);
}
/* we're the parent process */
close(write_pipe[0]);
close(read_pipe[1]);
/* set non-blocking I/O */
if ((flags = fcntl(cs->write_pipe, F_GETFL, 0)) >= 0)
fcntl(cs->write_pipe, F_SETFL, flags | O_NONBLOCK);
else
bailoute("set pipe flags");
if ((flags = fcntl(cs->read_pipe, F_GETFL, 0)) >= 0)
fcntl(cs->read_pipe, F_SETFL, flags | O_NONBLOCK);
else
bailoute("set pipe flags");
cs->nfds = ((cs->read_pipe > cs->write_pipe) ?
cs->read_pipe : cs->write_pipe) + 1;
return (SOURCE *)cs;
}
/*
* raw read
*/
static u8 read_compressed(SOURCE *s, u8 pos, u8 len, void *buf)
{
COMPRESSED_SOURCE *cs = (COMPRESSED_SOURCE *)s;
SOURCE *fs = s->foundation;
char *p, *filebuf;
u8 got, fill;
int askfor, selresult;
ssize_t result;
fd_set read_set;
fd_set write_set;
#if DEBUG
printf("rc got asked for pos %llu len %llu\n", pos, len);
#endif
p = (char *)buf;
got = 0;
if (cs->read_pipe < 0) /* closed for reading */
return got;
while (got < len) {
result = read(cs->read_pipe, p, len - got);
#if DEBUG
printf("rc read got %d\n", result);
#endif
if (result == 0) { /* end of file */
/* remember size for buffer layer */
s->size_known = 1;
s->size = s->seq_pos + got;
/* close pipe (stops future read attempts in the track) */
close(cs->read_pipe);
cs->read_pipe = -1;
/* we're done */
break;
} else if (result > 0) { /* got data */
p += result;
got += result;
continue;
} else { /* error return */
if (errno == EINTR)
continue;
if (errno != EAGAIN) {
errore("read from pipe");
break;
}
}
/* no data available for reading right now, so try to write
some uncompressed data down the other pipe for a change */
/* calculate how much data to write */
askfor = 4096;
if (cs->write_max && cs->write_pos + askfor > cs->write_max)
askfor = cs->write_max - cs->write_pos;
if (askfor <= 0 && cs->write_pipe >= 0) {
/* there's no more data to write, close the pipe */
close(cs->write_pipe);
cs->write_pipe = -1;
}
if (cs->write_pipe < 0) {
/* no more data to write, just wait for input using select */
FD_ZERO(&read_set);
FD_SET(cs->read_pipe, &read_set);
#if DEBUG
printf("rc starting select\n");
#endif
selresult = select(cs->nfds, &read_set, NULL, NULL, NULL);
#if DEBUG
printf("rc select got %d\n", selresult);
#endif
if (selresult < 0 && errno != EINTR) {
errore("select");
break;
}
continue;
}
/* get data from lower layer */
fill = get_buffer_real(fs, cs->offset + cs->write_pos, askfor,
NULL, (void **)&filebuf);
#if DEBUG
printf("rc get_buffer asked for pos %llu len %d got %llu\n",
cs->offset + cs->write_pos, askfor, fill);
#endif
if (fill < askfor) {
/* we reached the end of compressed input, note that down */
cs->write_max = cs->write_pos + fill;
}
if (fill <= 0) {
/* didn't get any data to write, so no need trying */
/* NOTE: in this case, the above if() also caught on and the next
time through the loop, the write pipe will be closed. */
continue;
}
/* try a write right now */
result = write(cs->write_pipe, filebuf, fill);
#if DEBUG
printf("rc write got %d\n", result);
#endif
if (result >= 0) {
cs->write_pos += result;
continue; /* see if that made more data available for reading */
} else {
if (errno == EINTR)
continue;
if (errno != EAGAIN) {
errore("write to pipe");
break;
}
}
/* both pipes are blocked right now. wait using select(). */
FD_ZERO(&read_set);
FD_ZERO(&write_set);
FD_SET(cs->read_pipe, &read_set);
FD_SET(cs->write_pipe, &write_set);
#if DEBUG
printf("rc starting select\n");
#endif
selresult = select(cs->nfds, &read_set, &write_set, NULL, NULL);
#if DEBUG
printf("rc select got %d\n", selresult);
#endif
if (selresult < 0 && errno != EINTR) {
errore("select");
break;
}
}
return got;
}
/*
* close cleanup
*/
static void close_compressed(SOURCE *s)
{
COMPRESSED_SOURCE *cs = (COMPRESSED_SOURCE *)s;
int status;
if (cs->write_pipe >= 0)
close(cs->write_pipe);
if (cs->read_pipe >= 0)
close(cs->read_pipe);
kill(cs->pid, SIGHUP);
waitpid(cs->pid, &status, 0);
}
#endif /* DECOMPRESS */
/* EOF */
syntax highlighted by Code2HTML, v. 0.9.1