Commit d26d804c authored by topjohnwu's avatar topjohnwu

Migrate to generic stream implementation

parent 4f9a25ee
...@@ -21,19 +21,18 @@ using namespace std; ...@@ -21,19 +21,18 @@ using namespace std;
uint32_t dyn_img_hdr::j32 = 0; uint32_t dyn_img_hdr::j32 = 0;
uint64_t dyn_img_hdr::j64 = 0; uint64_t dyn_img_hdr::j64 = 0;
static int64_t one_step(unique_ptr<Compression> &&ptr, int fd, const void *in, size_t size) { static void decompress(format_t type, int fd, const void *in, size_t size) {
ptr->setOut(make_unique<FDOutStream>(fd)); unique_ptr<stream> ptr(get_decoder(type, open_stream<fd_stream>(fd)));
if (!ptr->write(in, size)) ptr->write(in, size);
return -1;
return ptr->finalize();
}
static int64_t decompress(format_t type, int fd, const void *in, size_t size) {
return one_step(unique_ptr<Compression>(get_decoder(type)), fd, in, size);
} }
static int64_t compress(format_t type, int fd, const void *in, size_t size) { static int64_t compress(format_t type, int fd, const void *in, size_t size) {
return one_step(unique_ptr<Compression>(get_encoder(type)), fd, in, size); auto prev = lseek(fd, 0, SEEK_CUR);
unique_ptr<stream> ptr(get_encoder(type, open_stream<fd_stream>(fd)));
ptr->write(in, size);
ptr->close();
auto now = lseek(fd, 0, SEEK_CUR);
return now - prev;
} }
static void dump(void *buf, size_t size, const char *filename) { static void dump(void *buf, size_t size, const char *filename) {
......
...@@ -6,6 +6,13 @@ ...@@ -6,6 +6,13 @@
#include <memory> #include <memory>
#include <functional> #include <functional>
#include <zlib.h>
#include <bzlib.h>
#include <lzma.h>
#include <lz4.h>
#include <lz4frame.h>
#include <lz4hc.h>
#include <logging.h> #include <logging.h>
#include <utils.h> #include <utils.h>
...@@ -14,524 +21,639 @@ ...@@ -14,524 +21,639 @@
using namespace std; using namespace std;
static bool read_file(FILE *fp, const function<void (void *, size_t)> &fn) { #define bwrite filter_stream::write
char buf[4096]; #define bclose filter_stream::close
size_t len;
while ((len = fread(buf, 1, sizeof(buf), fp)))
fn(buf, len);
return true;
}
void decompress(char *infile, const char *outfile) { constexpr size_t CHUNK = 0x40000;
bool in_std = strcmp(infile, "-") == 0; constexpr size_t LZ4_UNCOMPRESSED = 0x800000;
bool rm_in = false; constexpr size_t LZ4_COMPRESSED = LZ4_COMPRESSBOUND(LZ4_UNCOMPRESSED);
FILE *in_file = in_std ? stdin : xfopen(infile, "re"); class cpr_stream : public filter_stream {
int out_fd = -1; public:
unique_ptr<Compression> cmp; explicit cpr_stream(FILE *fp) : filter_stream(fp) {}
read_file(in_file, [&](void *buf, size_t len) -> void { int read(void *buf, size_t len) final {
if (out_fd < 0) { return stream::read(buf, len);
format_t type = check_fmt(buf, len); }
fprintf(stderr, "Detected format: [%s]\n", fmt2name[type]); int close() final {
finish();
return bclose();
}
if (!COMPRESSED(type)) protected:
LOGE("Input file is not a supported compressed type!\n"); // If finish is overridden, destroy should be called in the destructor
virtual void finish() {}
void destroy() { if (fp) finish(); }
};
cmp.reset(get_decoder(type)); class gz_strm : public cpr_stream {
public:
~gz_strm() override { destroy(); }
/* If user does not provide outfile, infile has to be either int write(const void *buf, size_t len) override {
* <path>.[ext], or '-'. Outfile will be either <path> or '-'. return len ? write(buf, len, Z_NO_FLUSH) : 0;
* If the input does not have proper format, abort */ }
char *ext = nullptr; protected:
if (outfile == nullptr) { enum mode_t {
outfile = infile; DECODE,
if (!in_std) { ENCODE
ext = strrchr(infile, '.'); } mode;
if (ext == nullptr || strcmp(ext, fmt2ext[type]) != 0)
LOGE("Input file is not a supported type!\n");
// Strip out extension and remove input gz_strm(mode_t mode, FILE *fp) : cpr_stream(fp), mode(mode) {
*ext = '\0'; switch(mode) {
rm_in = true; case DECODE:
fprintf(stderr, "Decompressing to [%s]\n", outfile); inflateInit2(&strm, 15 | 16);
} break;
} case ENCODE:
deflateInit2(&strm, 9, Z_DEFLATED, 15 | 16, 8, Z_DEFAULT_STRATEGY);
break;
}
}
out_fd = strcmp(outfile, "-") == 0 ? void finish() override {
STDOUT_FILENO : xopen(outfile, O_WRONLY | O_CREAT | O_TRUNC, 0644); write(nullptr, 0, Z_FINISH);
cmp->setOut(make_unique<FDOutStream>(out_fd)); switch(mode) {
if (ext) *ext = '.'; case DECODE:
inflateEnd(&strm);
break;
case ENCODE:
deflateEnd(&strm);
break;
} }
if (!cmp->write(buf, len)) }
LOGE("Decompression error!\n");
});
cmp->finalize(); private:
fclose(in_file); z_stream strm;
close(out_fd); uint8_t outbuf[CHUNK];
int write(const void *buf, size_t len, int flush) {
strm.next_in = (Bytef *) buf;
strm.avail_in = len;
do {
int code;
strm.next_out = outbuf;
strm.avail_out = sizeof(outbuf);
switch(mode) {
case DECODE:
code = inflate(&strm, flush);
break;
case ENCODE:
code = deflate(&strm, flush);
break;
}
if (code == Z_STREAM_ERROR) {
LOGW("gzip %s failed (%d)\n", mode ? "encode" : "decode", code);
return -1;
}
bwrite(outbuf, sizeof(outbuf) - strm.avail_out);
} while (strm.avail_out == 0);
return len;
}
};
if (rm_in) class gz_decoder : public gz_strm {
unlink(infile); public:
} explicit gz_decoder(FILE *fp) : gz_strm(DECODE, fp) {};
};
void compress(const char *method, const char *infile, const char *outfile) { class gz_encoder : public gz_strm {
auto it = name2fmt.find(method); public:
if (it == name2fmt.end()) explicit gz_encoder(FILE *fp) : gz_strm(ENCODE, fp) {};
LOGE("Unsupported compression method: [%s]\n", method); };
unique_ptr<Compression> cmp(get_encoder(it->second)); class bz_strm : public cpr_stream {
public:
~bz_strm() override { destroy(); }
bool in_std = strcmp(infile, "-") == 0; int write(const void *buf, size_t len) override {
bool rm_in = false; return len ? write(buf, len, BZ_RUN) : 0;
}
FILE *in_file = in_std ? stdin : xfopen(infile, "re"); protected:
int out_fd; enum mode_t {
DECODE,
ENCODE
} mode;
if (outfile == nullptr) { bz_strm(mode_t mode, FILE *fp) : cpr_stream(fp), mode(mode) {
if (in_std) { switch(mode) {
out_fd = STDOUT_FILENO; case DECODE:
} else { BZ2_bzDecompressInit(&strm, 0, 0);
/* If user does not provide outfile and infile is not break;
* STDIN, output to <infile>.[ext] */ case ENCODE:
char *tmp = new char[strlen(infile) + 5]; BZ2_bzCompressInit(&strm, 9, 0, 0);
sprintf(tmp, "%s%s", infile, fmt2ext[it->second]); break;
out_fd = xopen(tmp, O_WRONLY | O_CREAT | O_TRUNC, 0644);
fprintf(stderr, "Compressing to [%s]\n", tmp);
delete[] tmp;
rm_in = true;
} }
} else {
out_fd = strcmp(outfile, "-") == 0 ?
STDOUT_FILENO : xopen(outfile, O_WRONLY | O_CREAT | O_TRUNC, 0644);
} }
cmp->setOut(make_unique<FDOutStream>(out_fd)); void finish() override {
switch(mode) {
case DECODE:
BZ2_bzDecompressEnd(&strm);
break;
case ENCODE:
write(nullptr, 0, BZ_FINISH);
BZ2_bzCompressEnd(&strm);
break;
}
}
read_file(in_file, [&](void *buf, size_t len) -> void { private:
if (!cmp->write(buf, len)) bz_stream strm;
LOGE("Compression error!\n"); char outbuf[CHUNK];
});
int write(const void *buf, size_t len, int flush) {
strm.next_in = (char *) buf;
strm.avail_in = len;
do {
int code;
strm.avail_out = sizeof(outbuf);
strm.next_out = outbuf;
switch(mode) {
case DECODE:
code = BZ2_bzDecompress(&strm);
break;
case ENCODE:
code = BZ2_bzCompress(&strm, flush);
break;
}
if (code < 0) {
LOGW("bzip2 %s failed (%d)\n", mode ? "encode" : "decode", code);
return -1;
}
bwrite(outbuf, sizeof(outbuf) - strm.avail_out);
} while (strm.avail_out == 0);
return len;
}
};
cmp->finalize(); class bz_decoder : public bz_strm {
fclose(in_file); public:
close(out_fd); explicit bz_decoder(FILE *fp) : bz_strm(DECODE, fp) {};
};
if (rm_in) class bz_encoder : public bz_strm {
unlink(infile); public:
} explicit bz_encoder(FILE *fp) : bz_strm(ENCODE, fp) {};
};
Compression *get_encoder(format_t type) { class lzma_strm : public cpr_stream {
switch (type) { public:
case XZ: ~lzma_strm() override { destroy(); }
return new XZEncoder();
case LZMA:
return new LZMAEncoder();
case BZIP2:
return new BZEncoder();
case LZ4:
return new LZ4FEncoder();
case LZ4_LEGACY:
return new LZ4Encoder();
case GZIP:
default:
return new GZEncoder();
}
}
Compression *get_decoder(format_t type) { int write(const void *buf, size_t len) override {
switch (type) { return len ? write(buf, len, LZMA_RUN) : 0;
case XZ:
case LZMA:
return new LZMADecoder();
case BZIP2:
return new BZDecoder();
case LZ4:
return new LZ4FDecoder();
case LZ4_LEGACY:
return new LZ4Decoder();
case GZIP:
default:
return new GZDecoder();
} }
}
GZStream::GZStream(int mode) : mode(mode), strm({}) { protected:
switch(mode) { enum mode_t {
case 0: DECODE,
inflateInit2(&strm, 15 | 16); ENCODE_XZ,
break; ENCODE_LZMA
case 1: } mode;
deflateInit2(&strm, 9, Z_DEFLATED, 15 | 16, 8, Z_DEFAULT_STRATEGY);
break;
}
}
bool GZStream::write(const void *in, size_t size) { lzma_strm(mode_t mode, FILE *fp) : cpr_stream(fp), mode(mode), strm(LZMA_STREAM_INIT) {
return size ? write(in, size, Z_NO_FLUSH) : true; lzma_options_lzma opt;
}
uint64_t GZStream::finalize() { // Initialize preset
write(nullptr, 0, Z_FINISH); lzma_lzma_preset(&opt, 9);
uint64_t total = strm.total_out; lzma_filter filters[] = {
switch(mode) { { .id = LZMA_FILTER_LZMA2, .options = &opt },
case 0: { .id = LZMA_VLI_UNKNOWN, .options = nullptr },
inflateEnd(&strm); };
break;
case 1:
deflateEnd(&strm);
break;
}
return total;
}
bool GZStream::write(const void *in, size_t size, int flush) { lzma_ret ret;
int ret;
strm.next_in = (Bytef *) in;
strm.avail_in = size;
do {
strm.next_out = outbuf;
strm.avail_out = sizeof(outbuf);
switch(mode) { switch(mode) {
case 0: case DECODE:
ret = inflate(&strm, flush); ret = lzma_auto_decoder(&strm, UINT64_MAX, 0);
break; break;
case 1: case ENCODE_XZ:
ret = deflate(&strm, flush); ret = lzma_stream_encoder(&strm, filters, LZMA_CHECK_CRC32);
break;
case ENCODE_LZMA:
ret = lzma_alone_encoder(&strm, &opt);
break; break;
} }
if (ret == Z_STREAM_ERROR) { }
LOGW("Gzip %s failed (%d)\n", mode ? "encode" : "decode", ret);
return false;
}
FilterOutStream::write(outbuf, sizeof(outbuf) - strm.avail_out);
} while (strm.avail_out == 0);
return true;
}
BZStream::BZStream(int mode) : mode(mode), strm({}) { void finish() override {
switch(mode) { write(nullptr, 0, LZMA_FINISH);
case 0: lzma_end(&strm);
BZ2_bzDecompressInit(&strm, 0, 0);
break;
case 1:
BZ2_bzCompressInit(&strm, 9, 0, 0);
break;
} }
}
bool BZStream::write(const void *in, size_t size) { private:
return size ? write(in, size, BZ_RUN) : true; lzma_stream strm;
} uint8_t outbuf[CHUNK];
int write(const void *buf, size_t len, lzma_action flush) {
strm.next_in = (uint8_t *) buf;
strm.avail_in = len;
do {
strm.avail_out = sizeof(outbuf);
strm.next_out = outbuf;
int code = lzma_code(&strm, flush);
if (code != LZMA_OK && code != LZMA_STREAM_END) {
LOGW("LZMA %s failed (%d)\n", mode ? "encode" : "decode", code);
return -1;
}
bwrite(outbuf, sizeof(outbuf) - strm.avail_out);
} while (strm.avail_out == 0);
return len;
}
};
class lzma_decoder : public lzma_strm {
public:
lzma_decoder(FILE *fp) : lzma_strm(DECODE, fp) {}
};
class xz_encoder : public lzma_strm {
public:
xz_encoder(FILE *fp) : lzma_strm(ENCODE_XZ, fp) {}
};
class lzma_encoder : public lzma_strm {
public:
lzma_encoder(FILE *fp) : lzma_strm(ENCODE_LZMA, fp) {}
};
class LZ4F_decoder : public cpr_stream {
public:
explicit LZ4F_decoder(FILE *fp) : cpr_stream(fp), outbuf(nullptr) {
LZ4F_createDecompressionContext(&ctx, LZ4F_VERSION);
}
uint64_t BZStream::finalize() { ~LZ4F_decoder() override {
if (mode) LZ4F_freeDecompressionContext(ctx);
write(nullptr, 0, BZ_FINISH); delete[] outbuf;
uint64_t total = ((uint64_t) strm.total_out_hi32 << 32) + strm.total_out_lo32; }
switch(mode) {
case 0:
BZ2_bzDecompressEnd(&strm);
break;
case 1:
BZ2_bzCompressEnd(&strm);
break;
}
return total;
}
bool BZStream::write(const void *in, size_t size, int flush) { int write(const void *buf, size_t len) override {
int ret; auto ret = len;
strm.next_in = (char *) in; auto inbuf = reinterpret_cast<const uint8_t *>(buf);
strm.avail_in = size; if (!outbuf)
do { read_header(inbuf, len);
strm.avail_out = sizeof(outbuf); size_t read, write;
strm.next_out = outbuf; LZ4F_errorCode_t code;
switch(mode) { do {
case 0: read = len;
ret = BZ2_bzDecompress(&strm); write = outCapacity;
break; code = LZ4F_decompress(ctx, outbuf, &write, inbuf, &read, nullptr);
case 1: if (LZ4F_isError(code)) {
ret = BZ2_bzCompress(&strm, flush); LOGW("LZ4F decode error: %s\n", LZ4F_getErrorName(code));
break; return -1;
} }
if (ret < 0) { len -= read;
LOGW("Bzip2 %s failed (%d)\n", mode ? "encode" : "decode", ret); inbuf += read;
return false; bwrite(outbuf, write);
} while (len != 0 || write != 0);
return ret;
}
private:
LZ4F_decompressionContext_t ctx;
uint8_t *outbuf;
size_t outCapacity;
void read_header(const uint8_t *&in, size_t &size) {
size_t read = size;
LZ4F_frameInfo_t info;
LZ4F_getFrameInfo(ctx, &info, in, &read);
switch (info.blockSizeID) {
case LZ4F_default:
case LZ4F_max64KB: outCapacity = 1 << 16; break;
case LZ4F_max256KB: outCapacity = 1 << 18; break;
case LZ4F_max1MB: outCapacity = 1 << 20; break;
case LZ4F_max4MB: outCapacity = 1 << 22; break;
} }
FilterOutStream::write(outbuf, sizeof(outbuf) - strm.avail_out); outbuf = new uint8_t[outCapacity];
} while (strm.avail_out == 0); in += read;
return true; size -= read;
} }
};
LZMAStream::LZMAStream(int mode) : mode(mode), strm(LZMA_STREAM_INIT) { class LZ4F_encoder : public cpr_stream {
lzma_options_lzma opt; public:
int ret; explicit LZ4F_encoder(FILE *fp) : cpr_stream(fp), outbuf(nullptr), outCapacity(0) {
LZ4F_createCompressionContext(&ctx, LZ4F_VERSION);
}
// Initialize preset ~LZ4F_encoder() override {
lzma_lzma_preset(&opt, 9); destroy();
lzma_filter filters[] = { LZ4F_freeCompressionContext(ctx);
{ .id = LZMA_FILTER_LZMA2, .options = &opt }, delete[] outbuf;
{ .id = LZMA_VLI_UNKNOWN, .options = nullptr }, }
};
switch(mode) { int write(const void *buf, size_t len) override {
case 0: auto ret = len;
ret = lzma_auto_decoder(&strm, UINT64_MAX, 0); if (!outbuf)
break; write_header();
case 1: if (len == 0)
ret = lzma_stream_encoder(&strm, filters, LZMA_CHECK_CRC32); return 0;
break; auto inbuf = reinterpret_cast<const uint8_t *>(buf);
case 2: size_t read, write;
ret = lzma_alone_encoder(&strm, &opt); do {
break; read = len > BLOCK_SZ ? BLOCK_SZ : len;
write = LZ4F_compressUpdate(ctx, outbuf, outCapacity, inbuf, read, nullptr);
if (LZ4F_isError(write)) {
LOGW("LZ4F encode error: %s\n", LZ4F_getErrorName(write));
return -1;
}
len -= read;
inbuf += read;
bwrite(outbuf, write);
} while (len != 0);
return ret;
} }
}
bool LZMAStream::write(const void *in, size_t size) { protected:
return size ? write(in, size, LZMA_RUN) : true; void finish() override {
} size_t len = LZ4F_compressEnd(ctx, outbuf, outCapacity, nullptr);
bwrite(outbuf, len);
}
uint64_t LZMAStream::finalize() { private:
write(nullptr, 0, LZMA_FINISH); LZ4F_compressionContext_t ctx;
uint64_t total = strm.total_out; uint8_t *outbuf;
lzma_end(&strm); size_t outCapacity;
return total;
} static constexpr size_t BLOCK_SZ = 1 << 22;
void write_header() {
LZ4F_preferences_t prefs {
.autoFlush = 1,
.compressionLevel = 9,
.frameInfo = {
.blockMode = LZ4F_blockIndependent,
.blockSizeID = LZ4F_max4MB,
.blockChecksumFlag = LZ4F_noBlockChecksum,
.contentChecksumFlag = LZ4F_contentChecksumEnabled
}
};
outCapacity = LZ4F_compressBound(BLOCK_SZ, &prefs);
outbuf = new uint8_t[outCapacity];
size_t write = LZ4F_compressBegin(ctx, outbuf, outCapacity, &prefs);
bwrite(outbuf, write);
}
};
class LZ4_decoder : public cpr_stream {
public:
explicit LZ4_decoder(FILE *fp)
: cpr_stream(fp), out_buf(new char[LZ4_UNCOMPRESSED]), buffer(new char[LZ4_COMPRESSED]),
init(false), block_sz(0), buf_off(0) {}
~LZ4_decoder() override {
delete[] out_buf;
delete[] buffer;
}
bool LZMAStream::write(const void *in, size_t size, lzma_action flush) { int write(const void *in, size_t size) override {
int ret; auto ret = size;
strm.next_in = (uint8_t *) in; auto inbuf = static_cast<const char *>(in);
strm.avail_in = size; if (!init) {
do { // Skip magic
strm.avail_out = sizeof(outbuf); inbuf += 4;
strm.next_out = outbuf; size -= 4;
ret = lzma_code(&strm, flush); init = true;
if (ret != LZMA_OK && ret != LZMA_STREAM_END) {
LOGW("LZMA %s failed (%d)\n", mode ? "encode" : "decode", ret);
return false;
} }
FilterOutStream::write(outbuf, sizeof(outbuf) - strm.avail_out); int write;
} while (strm.avail_out == 0); size_t consumed;
return true; do {
} if (block_sz == 0) {
block_sz = *((unsigned *) inbuf);
inbuf += sizeof(unsigned);
size -= sizeof(unsigned);
} else if (buf_off + size >= block_sz) {
consumed = block_sz - buf_off;
memcpy(buffer + buf_off, inbuf, consumed);
inbuf += consumed;
size -= consumed;
write = LZ4_decompress_safe(buffer, out_buf, block_sz, LZ4_UNCOMPRESSED);
if (write < 0) {
LOGW("LZ4HC decompression failure (%d)\n", write);
return -1;
}
bwrite(out_buf, write);
// Reset
buf_off = 0;
block_sz = 0;
} else {
// Copy to internal buffer
memcpy(buffer + buf_off, inbuf, size);
buf_off += size;
size = 0;
}
} while (size != 0);
return ret;
}
LZ4FDecoder::LZ4FDecoder() : outbuf(nullptr), total(0) { private:
LZ4F_createDecompressionContext(&ctx, LZ4F_VERSION); char *out_buf;
} char *buffer;
bool init;
unsigned block_sz;
int buf_off;
};
class LZ4_encoder : public cpr_stream {
public:
explicit LZ4_encoder(FILE *fp)
: cpr_stream(fp), outbuf(new char[LZ4_COMPRESSED]), buf(new char[LZ4_UNCOMPRESSED]),
init(false), buf_off(0), in_total(0) {}
~LZ4_encoder() override {
destroy();
delete[] outbuf;
delete[] buf;
}
LZ4FDecoder::~LZ4FDecoder() { int write(const void *in, size_t size) override {
LZ4F_freeDecompressionContext(ctx); if (!init) {
delete[] outbuf; bwrite("\x02\x21\x4c\x18", 4);
} init = true;
}
if (size == 0)
return 0;
in_total += size;
const char *inbuf = (const char *) in;
size_t consumed;
int write;
do {
if (buf_off + size >= LZ4_UNCOMPRESSED) {
consumed = LZ4_UNCOMPRESSED - buf_off;
memcpy(buf + buf_off, inbuf, consumed);
inbuf += consumed;
size -= consumed;
write = LZ4_compress_HC(buf, outbuf, LZ4_UNCOMPRESSED, LZ4_COMPRESSED, 9);
if (write == 0) {
LOGW("LZ4HC compression failure\n");
return false;
}
bwrite(&write, sizeof(write));
bwrite(outbuf, write);
// Reset buffer
buf_off = 0;
} else {
// Copy to internal buffer
memcpy(buf + buf_off, inbuf, size);
buf_off += size;
size = 0;
}
} while (size != 0);
return true;
}
bool LZ4FDecoder::write(const void *in, size_t size) { protected:
auto inbuf = (const uint8_t *) in; void finish() override {
if (!outbuf) if (buf_off) {
read_header(inbuf, size); int write = LZ4_compress_HC(buf, outbuf, buf_off, LZ4_COMPRESSED, 9);
size_t read, write; bwrite(&write, sizeof(write));
LZ4F_errorCode_t ret; bwrite(outbuf, write);
do {
read = size;
write = outCapacity;
ret = LZ4F_decompress(ctx, outbuf, &write, inbuf, &read, nullptr);
if (LZ4F_isError(ret)) {
LOGW("LZ4 decode error: %s\n", LZ4F_getErrorName(ret));
return false;
} }
size -= read; bwrite(&in_total, sizeof(in_total));
inbuf += read; }
total += write;
FilterOutStream::write(outbuf, write);
} while (size != 0 || write != 0);
return true;
}
uint64_t LZ4FDecoder::finalize() { private:
return total; char *outbuf;
} char *buf;
bool init;
int buf_off;
unsigned in_total;
};
void LZ4FDecoder::read_header(const uint8_t *&in, size_t &size) { filter_stream *get_encoder(format_t type, FILE *fp) {
size_t read = size; switch (type) {
LZ4F_frameInfo_t info; case XZ:
LZ4F_getFrameInfo(ctx, &info, in, &read); return new xz_encoder(fp);
switch (info.blockSizeID) { case LZMA:
case LZ4F_default: return new lzma_encoder(fp);
case LZ4F_max64KB: outCapacity = 1 << 16; break; case BZIP2:
case LZ4F_max256KB: outCapacity = 1 << 18; break; return new bz_encoder(fp);
case LZ4F_max1MB: outCapacity = 1 << 20; break; case LZ4:
case LZ4F_max4MB: outCapacity = 1 << 22; break; return new LZ4F_encoder(fp);
} case LZ4_LEGACY:
outbuf = new uint8_t[outCapacity]; return new LZ4_encoder(fp);
in += read; case GZIP:
size -= read; default:
return new gz_encoder(fp);
}
} }
LZ4FEncoder::LZ4FEncoder() : outbuf(nullptr), outCapacity(0), total(0) { filter_stream *get_decoder(format_t type, FILE *fp) {
LZ4F_createCompressionContext(&ctx, LZ4F_VERSION); switch (type) {
case XZ:
case LZMA:
return new lzma_decoder(fp);
case BZIP2:
return new bz_decoder(fp);
case LZ4:
return new LZ4F_decoder(fp);
case LZ4_LEGACY:
return new LZ4_decoder(fp);
case GZIP:
default:
return new gz_decoder(fp);
}
} }
LZ4FEncoder::~LZ4FEncoder() { void decompress(char *infile, const char *outfile) {
LZ4F_freeCompressionContext(ctx); bool in_std = infile == "-"sv;
delete[] outbuf; bool rm_in = false;
}
bool LZ4FEncoder::write(const void *in, size_t size) { FILE *in_fp = in_std ? stdin : xfopen(infile, "re");
if (!outbuf) unique_ptr<stream> strm;
write_header();
if (size == 0)
return true;
auto inbuf = (const uint8_t *) in;
size_t read, write;
do {
read = size > BLOCK_SZ ? BLOCK_SZ : size;
write = LZ4F_compressUpdate(ctx, outbuf, outCapacity, inbuf, read, nullptr);
if (LZ4F_isError(write)) {
LOGW("LZ4 encode error: %s\n", LZ4F_getErrorName(write));
return false;
}
size -= read;
inbuf += read;
total += write;
FilterOutStream::write(outbuf, write);
} while (size != 0);
return true;
}
uint64_t LZ4FEncoder::finalize() { char buf[4096];
size_t write = LZ4F_compressEnd(ctx, outbuf, outCapacity, nullptr); size_t len;
total += write; while ((len = fread(buf, 1, sizeof(buf), in_fp))) {
FilterOutStream::write(outbuf, write); if (!strm) {
return total; format_t type = check_fmt(buf, len);
}
void LZ4FEncoder::write_header() { if (!COMPRESSED(type))
LZ4F_preferences_t prefs { LOGE("Input file is not a supported compressed type!\n");
.autoFlush = 1,
.compressionLevel = 9,
.frameInfo = {
.blockMode = LZ4F_blockIndependent,
.blockSizeID = LZ4F_max4MB,
.blockChecksumFlag = LZ4F_noBlockChecksum,
.contentChecksumFlag = LZ4F_contentChecksumEnabled
}
};
outCapacity = LZ4F_compressBound(BLOCK_SZ, &prefs);
outbuf = new uint8_t[outCapacity];
size_t write = LZ4F_compressBegin(ctx, outbuf, outCapacity, &prefs);
total += write;
FilterOutStream::write(outbuf, write);
}
LZ4Decoder::LZ4Decoder() : outbuf(new char[LZ4_UNCOMPRESSED]), buf(new char[LZ4_COMPRESSED]), fprintf(stderr, "Detected format: [%s]\n", fmt2name[type]);
init(false), block_sz(0), buf_off(0), total(0) {}
LZ4Decoder::~LZ4Decoder() { /* If user does not provide outfile, infile has to be either
delete[] outbuf; * <path>.[ext], or '-'. Outfile will be either <path> or '-'.
delete[] buf; * If the input does not have proper format, abort */
}
char *ext = nullptr;
if (outfile == nullptr) {
outfile = infile;
if (!in_std) {
ext = strrchr(infile, '.');
if (ext == nullptr || strcmp(ext, fmt2ext[type]) != 0)
LOGE("Input file is not a supported type!\n");
bool LZ4Decoder::write(const void *in, size_t size) { // Strip out extension and remove input
const char *inbuf = (const char *) in; *ext = '\0';
if (!init) { rm_in = true;
// Skip magic fprintf(stderr, "Decompressing to [%s]\n", outfile);
inbuf += 4; }
size -= 4;
init = true;
}
int write;
size_t consumed;
do {
if (block_sz == 0) {
block_sz = *((unsigned *) inbuf);
inbuf += sizeof(unsigned);
size -= sizeof(unsigned);
} else if (buf_off + size >= block_sz) {
consumed = block_sz - buf_off;
memcpy(buf + buf_off, inbuf, consumed);
inbuf += consumed;
size -= consumed;
write = LZ4_decompress_safe(buf, outbuf, block_sz, LZ4_UNCOMPRESSED);
if (write < 0) {
LOGW("LZ4HC decompression failure (%d)\n", write);
return false;
} }
FilterOutStream::write(outbuf, write);
total += write;
// Reset FILE *out_fp = outfile == "-"sv ? stdout : xfopen(outfile, "we");
buf_off = 0; strm.reset(get_decoder(type, out_fp));
block_sz = 0; if (ext) *ext = '.';
} else {
// Copy to internal buffer
memcpy(buf + buf_off, inbuf, size);
buf_off += size;
size = 0;
} }
} while (size != 0); if (strm->write(buf, len) < 0)
return true; LOGE("Decompression error!\n");
} }
uint64_t LZ4Decoder::finalize() { strm->close();
return total; fclose(in_fp);
if (rm_in)
unlink(infile);
} }
LZ4Encoder::LZ4Encoder() : outbuf(new char[LZ4_COMPRESSED]), buf(new char[LZ4_UNCOMPRESSED]), void compress(const char *method, const char *infile, const char *outfile) {
init(false), buf_off(0), out_total(0), in_total(0) {} auto it = name2fmt.find(method);
if (it == name2fmt.end())
LOGE("Unknown compression method: [%s]\n", method);
LZ4Encoder::~LZ4Encoder() { bool in_std = infile == "-"sv;
delete[] outbuf; bool rm_in = false;
delete[] buf;
}
bool LZ4Encoder::write(const void *in, size_t size) { FILE *in_fp = in_std ? stdin : xfopen(infile, "re");
if (!init) { FILE *out_fp;
FilterOutStream::write("\x02\x21\x4c\x18", 4);
init = true;
}
if (size == 0)
return true;
in_total += size;
const char *inbuf = (const char *) in;
size_t consumed;
int write;
do {
if (buf_off + size >= LZ4_UNCOMPRESSED) {
consumed = LZ4_UNCOMPRESSED - buf_off;
memcpy(buf + buf_off, inbuf, consumed);
inbuf += consumed;
size -= consumed;
write = LZ4_compress_HC(buf, outbuf, LZ4_UNCOMPRESSED, LZ4_COMPRESSED, 9);
if (write == 0) {
LOGW("LZ4HC compression failure\n");
return false;
}
FilterOutStream::write(&write, sizeof(write));
FilterOutStream::write(outbuf, write);
out_total += write + sizeof(write);
// Reset buffer if (outfile == nullptr) {
buf_off = 0; if (in_std) {
out_fp = stdout;
} else { } else {
// Copy to internal buffer /* If user does not provide outfile and infile is not
memcpy(buf + buf_off, inbuf, size); * STDIN, output to <infile>.[ext] */
buf_off += size; string tmp(infile);
size = 0; tmp += fmt2ext[it->second];
out_fp = xfopen(tmp.data(), "we");
fprintf(stderr, "Compressing to [%s]\n", tmp.data());
rm_in = true;
} }
} while (size != 0); } else {
return true; out_fp = outfile == "-"sv ? stdout : xfopen(outfile, "we");
}
uint64_t LZ4Encoder::finalize() {
if (buf_off) {
int write = LZ4_compress_HC(buf, outbuf, buf_off, LZ4_COMPRESSED, 9);
FilterOutStream::write(&write, sizeof(write));
FilterOutStream::write(outbuf, write);
out_total += write + sizeof(write);
} }
FilterOutStream::write(&in_total, sizeof(in_total));
return out_total + sizeof(in_total); unique_ptr<stream> strm(get_encoder(it->second, out_fp));
char buf[4096];
size_t len;
while ((len = fread(buf, 1, sizeof(buf), in_fp))) {
if (strm->write(buf, len) < 0)
LOGE("Compression error!\n");
};
strm->close();
fclose(in_fp);
if (rm_in)
unlink(infile);
} }
#pragma once #pragma once
#include <zlib.h>
#include <bzlib.h>
#include <lzma.h>
#include <lz4.h>
#include <lz4frame.h>
#include <lz4hc.h>
#include <stream.h> #include <stream.h>
#include "format.h" #include "format.h"
#define CHUNK 0x40000 filter_stream *get_encoder(format_t type, FILE *fp = nullptr);
filter_stream *get_decoder(format_t type, FILE *fp = nullptr);
class Compression : public FilterOutStream {
public:
virtual uint64_t finalize() = 0;
};
class GZStream : public Compression {
public:
bool write(const void *in, size_t size) override;
uint64_t finalize() override;
protected:
explicit GZStream(int mode);
private:
int mode;
z_stream strm;
uint8_t outbuf[CHUNK];
bool write(const void *in, size_t size, int flush);
};
class GZDecoder : public GZStream {
public:
GZDecoder() : GZStream(0) {};
};
class GZEncoder : public GZStream {
public:
GZEncoder() : GZStream(1) {};
};
class BZStream : public Compression {
public:
bool write(const void *in, size_t size) override;
uint64_t finalize() override;
protected:
explicit BZStream(int mode);
private:
int mode;
bz_stream strm;
char outbuf[CHUNK];
bool write(const void *in, size_t size, int flush);
};
class BZDecoder : public BZStream {
public:
BZDecoder() : BZStream(0) {};
};
class BZEncoder : public BZStream {
public:
BZEncoder() : BZStream(1) {};
};
class LZMAStream : public Compression {
public:
bool write(const void *in, size_t size) override;
uint64_t finalize() override;
protected:
explicit LZMAStream(int mode);
private:
int mode;
lzma_stream strm;
uint8_t outbuf[CHUNK];
bool write(const void *in, size_t size, lzma_action flush);
};
class LZMADecoder : public LZMAStream {
public:
LZMADecoder() : LZMAStream(0) {}
};
class XZEncoder : public LZMAStream {
public:
XZEncoder() : LZMAStream(1) {}
};
class LZMAEncoder : public LZMAStream {
public:
LZMAEncoder() : LZMAStream(2) {}
};
class LZ4FDecoder : public Compression {
public:
LZ4FDecoder();
~LZ4FDecoder() override;
bool write(const void *in, size_t size) override;
uint64_t finalize() override;
private:
LZ4F_decompressionContext_t ctx;
uint8_t *outbuf;
size_t outCapacity;
uint64_t total;
void read_header(const uint8_t *&in, size_t &size);
};
class LZ4FEncoder : public Compression {
public:
LZ4FEncoder();
~LZ4FEncoder() override;
bool write(const void *in, size_t size) override;
uint64_t finalize() override;
private:
static constexpr size_t BLOCK_SZ = 1 << 22;
LZ4F_compressionContext_t ctx;
uint8_t *outbuf;
size_t outCapacity;
uint64_t total;
void write_header();
};
#define LZ4_UNCOMPRESSED 0x800000
#define LZ4_COMPRESSED LZ4_COMPRESSBOUND(LZ4_UNCOMPRESSED)
class LZ4Decoder : public Compression {
public:
LZ4Decoder();
~LZ4Decoder() override;
bool write(const void *in, size_t size) override;
uint64_t finalize() override;
private:
char *outbuf;
char *buf;
bool init;
unsigned block_sz;
int buf_off;
uint64_t total;
};
class LZ4Encoder : public Compression {
public:
LZ4Encoder();
~LZ4Encoder() override;
bool write(const void *in, size_t size) override;
uint64_t finalize() override;
private:
char *outbuf;
char *buf;
bool init;
int buf_off;
uint64_t out_total;
unsigned in_total;
};
Compression *get_encoder(format_t type);
Compression *get_decoder(format_t type);
void compress(const char *method, const char *infile, const char *outfile); void compress(const char *method, const char *infile, const char *outfile);
void decompress(char *infile, const char *outfile); void decompress(char *infile, const char *outfile);
...@@ -241,14 +241,17 @@ void magisk_cpio::compress() { ...@@ -241,14 +241,17 @@ void magisk_cpio::compress() {
return; return;
fprintf(stderr, "Compressing cpio -> [%s]\n", RAMDISK_XZ); fprintf(stderr, "Compressing cpio -> [%s]\n", RAMDISK_XZ);
auto init = entries.extract("init"); auto init = entries.extract("init");
XZEncoder encoder;
encoder.setOut(make_unique<BufOutStream>()); uint8_t *data;
output(encoder); size_t len;
encoder.finalize(); FILE *fp = open_stream(get_encoder(XZ, open_stream<byte_stream>(data, len)));
dump(fp);
entries.clear(); entries.clear();
entries.insert(std::move(init)); entries.insert(std::move(init));
auto xz = new cpio_entry(RAMDISK_XZ, S_IFREG); auto xz = new cpio_entry(RAMDISK_XZ, S_IFREG);
static_cast<BufOutStream *>(encoder.getOut())->release(xz->data, xz->filesize); xz->data = data;
xz->filesize = len;
insert(xz); insert(xz);
} }
...@@ -257,15 +260,16 @@ void magisk_cpio::decompress() { ...@@ -257,15 +260,16 @@ void magisk_cpio::decompress() {
if (it == entries.end()) if (it == entries.end())
return; return;
fprintf(stderr, "Decompressing cpio [%s]\n", RAMDISK_XZ); fprintf(stderr, "Decompressing cpio [%s]\n", RAMDISK_XZ);
LZMADecoder decoder;
decoder.setOut(make_unique<BufOutStream>()); char *data;
decoder.write(it->second->data, it->second->filesize); size_t len;
decoder.finalize(); auto strm = get_decoder(XZ, open_stream<byte_stream>(data, len));
strm->write(it->second->data, it->second->filesize);
delete strm;
entries.erase(it); entries.erase(it);
char *buf; load_cpio(data, len);
size_t sz; free(data);
static_cast<BufOutStream *>(decoder.getOut())->getbuf(buf, sz);
load_cpio(buf, sz);
} }
int cpio_commands(int argc, char *argv[]) { int cpio_commands(int argc, char *argv[]) {
...@@ -338,4 +342,4 @@ int cpio_commands(int argc, char *argv[]) { ...@@ -338,4 +342,4 @@ int cpio_commands(int argc, char *argv[]) {
cpio.dump(incpio); cpio.dump(incpio);
return 0; return 0;
} }
\ No newline at end of file
...@@ -49,8 +49,7 @@ cpio_entry_base::cpio_entry_base(const cpio_newc_header *h) ...@@ -49,8 +49,7 @@ cpio_entry_base::cpio_entry_base(const cpio_newc_header *h)
void cpio::dump(const char *file) { void cpio::dump(const char *file) {
fprintf(stderr, "Dump cpio: [%s]\n", file); fprintf(stderr, "Dump cpio: [%s]\n", file);
FDOutStream fd_out(xopen(file, O_WRONLY | O_CREAT | O_TRUNC, 0644), true); dump(xfopen(file, "we"));
output(fd_out);
} }
void cpio::rm(entry_map::iterator &it) { void cpio::rm(entry_map::iterator &it) {
...@@ -110,9 +109,9 @@ bool cpio::exists(const char *name) { ...@@ -110,9 +109,9 @@ bool cpio::exists(const char *name) {
return entries.count(name) != 0; return entries.count(name) != 0;
} }
#define do_out(b, l) out.write(b, l); pos += (l) #define do_out(buf, len) pos += fwrite(buf, len, 1, out);
#define out_align() out.write(zeros, align_off(pos, 4)); pos = do_align(pos, 4) #define out_align() do_out(zeros, align_off(pos, 4))
void cpio::output(OutStream &out) { void cpio::dump(FILE *out) {
size_t pos = 0; size_t pos = 0;
unsigned inode = 300000; unsigned inode = 300000;
char header[111]; char header[111];
...@@ -147,6 +146,7 @@ void cpio::output(OutStream &out) { ...@@ -147,6 +146,7 @@ void cpio::output(OutStream &out) {
do_out(header, 110); do_out(header, 110);
do_out("TRAILER!!!\0", 11); do_out("TRAILER!!!\0", 11);
out_align(); out_align();
fclose(out);
} }
cpio_rw::cpio_rw(const char *file) { cpio_rw::cpio_rw(const char *file) {
...@@ -221,12 +221,12 @@ bool cpio_rw::mv(const char *from, const char *to) { ...@@ -221,12 +221,12 @@ bool cpio_rw::mv(const char *from, const char *to) {
#define pos_align(p) p = do_align(p, 4) #define pos_align(p) p = do_align(p, 4)
void cpio_rw::load_cpio(char *buf, size_t sz) { void cpio_rw::load_cpio(const char *buf, size_t sz) {
size_t pos = 0; size_t pos = 0;
cpio_newc_header *header; const cpio_newc_header *header;
unique_ptr<cpio_entry> entry; unique_ptr<cpio_entry> entry;
while (pos < sz) { while (pos < sz) {
header = (cpio_newc_header *)(buf + pos); header = reinterpret_cast<const cpio_newc_header *>(buf + pos);
entry = make_unique<cpio_entry>(header); entry = make_unique<cpio_entry>(header);
pos += sizeof(*header); pos += sizeof(*header);
string_view name_view(buf + pos); string_view name_view(buf + pos);
......
...@@ -30,7 +30,7 @@ struct cpio_entry : public cpio_entry_base { ...@@ -30,7 +30,7 @@ struct cpio_entry : public cpio_entry_base {
explicit cpio_entry(const char *name, uint32_t mode) : filename(name) { explicit cpio_entry(const char *name, uint32_t mode) : filename(name) {
this->mode = mode; this->mode = mode;
} }
explicit cpio_entry(cpio_newc_header *h) : cpio_entry_base(h) {} explicit cpio_entry(const cpio_newc_header *h) : cpio_entry_base(h) {}
~cpio_entry() override { free(data); }; ~cpio_entry() override { free(data); };
}; };
...@@ -48,7 +48,7 @@ public: ...@@ -48,7 +48,7 @@ public:
protected: protected:
entry_map entries; entry_map entries;
void rm(entry_map::iterator &it); void rm(entry_map::iterator &it);
void output(OutStream &out); void dump(FILE *out);
}; };
class cpio_rw : public cpio { class cpio_rw : public cpio {
...@@ -64,7 +64,7 @@ public: ...@@ -64,7 +64,7 @@ public:
protected: protected:
void insert(cpio_entry *e); void insert(cpio_entry *e);
void mv(entry_map::iterator &it, const char *to); void mv(entry_map::iterator &it, const char *to);
void load_cpio(char *buf, size_t sz); void load_cpio(const char *buf, size_t sz);
}; };
class cpio_mmap : public cpio { class cpio_mmap : public cpio {
......
...@@ -15,8 +15,6 @@ FILE *open_stream(Args &&... args) { ...@@ -15,8 +15,6 @@ FILE *open_stream(Args &&... args) {
return open_stream(new T(args...)); return open_stream(new T(args...));
} }
/* Base classes */
class stream { class stream {
public: public:
virtual int read(void *buf, size_t len); virtual int read(void *buf, size_t len);
...@@ -26,17 +24,17 @@ public: ...@@ -26,17 +24,17 @@ public:
virtual ~stream() = default; virtual ~stream() = default;
}; };
// Delegates all operations to the base FILE pointer
class filter_stream : public stream { class filter_stream : public stream {
public: public:
filter_stream(FILE *fp) : fp(fp) {} filter_stream(FILE *fp) : fp(fp) {}
int close() override { return fclose(fp); } ~filter_stream() override { if (fp) close(); }
virtual ~filter_stream() { close(); }
void set_base(FILE *f) { int read(void *buf, size_t len) override;
if (fp) fclose(fp); int write(const void *buf, size_t len) override;
fp = f; int close() override;
}
void set_base(FILE *f);
template <class T, class... Args > template <class T, class... Args >
void set_base(Args&&... args) { void set_base(Args&&... args) {
set_base(open_stream<T>(args...)); set_base(open_stream<T>(args...));
...@@ -46,18 +44,7 @@ protected: ...@@ -46,18 +44,7 @@ protected:
FILE *fp; FILE *fp;
}; };
class filter_in_stream : public filter_stream { // Handy interface for classes that need custom seek logic
public:
filter_in_stream(FILE *fp = nullptr) : filter_stream(fp) {}
int read(void *buf, size_t len) override { return fread(buf, len, 1, fp); }
};
class filter_out_stream : public filter_stream {
public:
filter_out_stream(FILE *fp = nullptr) : filter_stream(fp) {}
int write(const void *buf, size_t len) override { return fwrite(buf, len, 1, fp); }
};
class seekable_stream : public stream { class seekable_stream : public stream {
protected: protected:
size_t _pos = 0; size_t _pos = 0;
...@@ -66,8 +53,7 @@ protected: ...@@ -66,8 +53,7 @@ protected:
virtual size_t end_pos() = 0; virtual size_t end_pos() = 0;
}; };
/* Concrete classes */ // Byte stream that dynamically allocates memory
class byte_stream : public seekable_stream { class byte_stream : public seekable_stream {
public: public:
byte_stream(uint8_t *&buf, size_t &len); byte_stream(uint8_t *&buf, size_t &len);
...@@ -76,7 +62,6 @@ public: ...@@ -76,7 +62,6 @@ public:
int read(void *buf, size_t len) override; int read(void *buf, size_t len) override;
int write(const void *buf, size_t len) override; int write(const void *buf, size_t len) override;
off_t seek(off_t off, int whence) override; off_t seek(off_t off, int whence) override;
virtual ~byte_stream() = default;
private: private:
uint8_t *&_buf; uint8_t *&_buf;
...@@ -87,101 +72,14 @@ private: ...@@ -87,101 +72,14 @@ private:
size_t end_pos() override { return _len; } size_t end_pos() override { return _len; }
}; };
class fd_stream : stream { // File stream but does not close the file descriptor at any time
class fd_stream : public stream {
public: public:
fd_stream(int fd) : fd(fd) {} fd_stream(int fd) : fd(fd) {}
int read(void *buf, size_t len) override; int read(void *buf, size_t len) override;
int write(const void *buf, size_t len) override; int write(const void *buf, size_t len) override;
off_t seek(off_t off, int whence) override; off_t seek(off_t off, int whence) override;
virtual ~fd_stream() = default;
private: private:
int fd; int fd;
}; };
/* TODO: Replace classes below to new implementation */
class OutStream {
public:
virtual bool write(const void *buf, size_t len) = 0;
virtual ~OutStream() = default;
};
typedef std::unique_ptr<OutStream> strm_ptr;
class FilterOutStream : public OutStream {
public:
FilterOutStream() = default;
FilterOutStream(strm_ptr &&ptr) : out(std::move(ptr)) {}
void setOut(strm_ptr &&ptr) { out = std::move(ptr); }
OutStream *getOut() { return out.get(); }
bool write(const void *buf, size_t len) override {
return out ? out->write(buf, len) : false;
}
protected:
strm_ptr out;
};
class FDOutStream : public OutStream {
public:
FDOutStream(int fd, bool close = false) : fd(fd), close(close) {}
bool write(const void *buf, size_t len) override {
return ::write(fd, buf, len) == len;
}
~FDOutStream() override {
if (close)
::close(fd);
}
protected:
int fd;
bool close;
};
class BufOutStream : public OutStream {
public:
BufOutStream() : buf(nullptr), off(0), cap(0) {};
bool write(const void *b, size_t len) override {
bool resize = false;
while (off + len > cap) {
cap = cap ? cap << 1 : 1 << 19;
resize = true;
}
if (resize)
buf = (char *) xrealloc(buf, cap);
memcpy(buf + off, b, len);
off += len;
return true;
}
template <typename bytes, typename length>
void release(bytes *&b, length &len) {
b = buf;
len = off;
buf = nullptr;
off = cap = 0;
}
template <typename bytes, typename length>
void getbuf(bytes *&b, length &len) const {
b = buf;
len = off;
}
~BufOutStream() override {
free(buf);
}
protected:
char *buf;
size_t off;
size_t cap;
};
...@@ -49,6 +49,25 @@ int stream::close() { ...@@ -49,6 +49,25 @@ int stream::close() {
return 0; return 0;
} }
int filter_stream::read(void *buf, size_t len) {
return fread(buf, len, 1, fp);
}
int filter_stream::write(const void *buf, size_t len) {
return fwrite(buf, len, 1, fp);
}
int filter_stream::close() {
int ret = fclose(fp);
fp = nullptr;
return ret;
}
void filter_stream::set_base(FILE *f) {
if (fp) fclose(fp);
fp = f;
}
off_t seekable_stream::new_pos(off_t off, int whence) { off_t seekable_stream::new_pos(off_t off, int whence) {
off_t new_pos; off_t new_pos;
switch (whence) { switch (whence) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment