+class APT_HIDDEN Bz2FileFdPrivate: public FileFdPrivate { /*{{{*/
+#ifdef HAVE_BZ2
+ BZFILE* bz2;
+public:
+ virtual bool InternalOpen(int const iFd, unsigned int const Mode) override
+ {
+ if ((Mode & FileFd::ReadWrite) == FileFd::ReadWrite)
+ bz2 = BZ2_bzdopen(iFd, "r+");
+ else if ((Mode & FileFd::WriteOnly) == FileFd::WriteOnly)
+ bz2 = BZ2_bzdopen(iFd, "w");
+ else
+ bz2 = BZ2_bzdopen(iFd, "r");
+ filefd->Flags |= FileFd::Compressed;
+ return bz2 != nullptr;
+ }
+ virtual ssize_t InternalUnbufferedRead(void * const To, unsigned long long const Size) override
+ {
+ return BZ2_bzread(bz2, To, Size);
+ }
+ virtual bool InternalReadError() override
+ {
+ int err;
+ char const * const errmsg = BZ2_bzerror(bz2, &err);
+ if (err != BZ_IO_ERROR)
+ return filefd->FileFdError("BZ2_bzread: %s %s (%d: %s)", filefd->FileName.c_str(), _("Read error"), err, errmsg);
+ return FileFdPrivate::InternalReadError();
+ }
+ virtual ssize_t InternalWrite(void const * const From, unsigned long long const Size) override
+ {
+ return BZ2_bzwrite(bz2, (void*)From, Size);
+ }
+ virtual bool InternalWriteError() override
+ {
+ int err;
+ char const * const errmsg = BZ2_bzerror(bz2, &err);
+ if (err != BZ_IO_ERROR)
+ return filefd->FileFdError("BZ2_bzwrite: %s %s (%d: %s)", filefd->FileName.c_str(), _("Write error"), err, errmsg);
+ return FileFdPrivate::InternalWriteError();
+ }
+ virtual bool InternalStream() const override { return true; }
+ virtual bool InternalClose(std::string const &) override
+ {
+ if (bz2 == nullptr)
+ return true;
+ BZ2_bzclose(bz2);
+ bz2 = nullptr;
+ return true;
+ }
+
+ explicit Bz2FileFdPrivate(FileFd * const filefd) : FileFdPrivate(filefd), bz2(nullptr) {}
+ virtual ~Bz2FileFdPrivate() { InternalClose(""); }
+#endif
+};
+ /*}}}*/
+class APT_HIDDEN LzmaFileFdPrivate: public FileFdPrivate { /*{{{*/
+#ifdef HAVE_LZMA
+ struct LZMAFILE {
+ FILE* file;
+ uint8_t buffer[4096];
+ lzma_stream stream;
+ lzma_ret err;
+ bool eof;
+ bool compressing;
+
+ LZMAFILE() : file(nullptr), eof(false), compressing(false) { buffer[0] = '\0'; }
+ ~LZMAFILE()
+ {
+ if (compressing == true)
+ {
+ size_t constexpr buffersize = sizeof(buffer)/sizeof(buffer[0]);
+ while(true)
+ {
+ stream.avail_out = buffersize;
+ stream.next_out = buffer;
+ err = lzma_code(&stream, LZMA_FINISH);
+ if (err != LZMA_OK && err != LZMA_STREAM_END)
+ {
+ _error->Error("~LZMAFILE: Compress finalisation failed");
+ break;
+ }
+ size_t const n = buffersize - stream.avail_out;
+ if (n && fwrite(buffer, 1, n, file) != n)
+ {
+ _error->Errno("~LZMAFILE",_("Write error"));
+ break;
+ }
+ if (err == LZMA_STREAM_END)
+ break;
+ }
+ }
+ lzma_end(&stream);
+ fclose(file);
+ }
+ };
+ LZMAFILE* lzma;
+ static uint32_t findXZlevel(std::vector<std::string> const &Args)
+ {
+ for (auto a = Args.rbegin(); a != Args.rend(); ++a)
+ if (a->empty() == false && (*a)[0] == '-' && (*a)[1] != '-')
+ {
+ auto const number = a->find_last_of("0123456789");
+ if (number == std::string::npos)
+ continue;
+ auto const extreme = a->find("e", number);
+ uint32_t level = (extreme != std::string::npos) ? LZMA_PRESET_EXTREME : 0;
+ switch ((*a)[number])
+ {
+ case '0': return level | 0;
+ case '1': return level | 1;
+ case '2': return level | 2;
+ case '3': return level | 3;
+ case '4': return level | 4;
+ case '5': return level | 5;
+ case '6': return level | 6;
+ case '7': return level | 7;
+ case '8': return level | 8;
+ case '9': return level | 9;
+ }
+ }
+ return 6;
+ }
+public:
+ virtual bool InternalOpen(int const iFd, unsigned int const Mode) override
+ {
+ if ((Mode & FileFd::ReadWrite) == FileFd::ReadWrite)
+ return filefd->FileFdError("ReadWrite mode is not supported for lzma/xz files %s", filefd->FileName.c_str());
+
+ if (lzma == nullptr)
+ lzma = new LzmaFileFdPrivate::LZMAFILE;
+ if ((Mode & FileFd::WriteOnly) == FileFd::WriteOnly)
+ lzma->file = fdopen(iFd, "w");
+ else
+ lzma->file = fdopen(iFd, "r");
+ filefd->Flags |= FileFd::Compressed;
+ if (lzma->file == nullptr)
+ return false;
+
+ lzma_stream tmp_stream = LZMA_STREAM_INIT;
+ lzma->stream = tmp_stream;
+
+ if ((Mode & FileFd::WriteOnly) == FileFd::WriteOnly)
+ {
+ uint32_t const xzlevel = findXZlevel(compressor.CompressArgs);
+ if (compressor.Name == "xz")
+ {
+ if (lzma_easy_encoder(&lzma->stream, xzlevel, LZMA_CHECK_CRC64) != LZMA_OK)
+ return false;
+ }
+ else
+ {
+ lzma_options_lzma options;
+ lzma_lzma_preset(&options, xzlevel);
+ if (lzma_alone_encoder(&lzma->stream, &options) != LZMA_OK)
+ return false;
+ }
+ lzma->compressing = true;
+ }
+ else
+ {
+ uint64_t const memlimit = UINT64_MAX;
+ if (compressor.Name == "xz")
+ {
+ if (lzma_auto_decoder(&lzma->stream, memlimit, 0) != LZMA_OK)
+ return false;
+ }
+ else
+ {
+ if (lzma_alone_decoder(&lzma->stream, memlimit) != LZMA_OK)
+ return false;
+ }
+ lzma->compressing = false;
+ }
+ return true;
+ }
+ virtual ssize_t InternalUnbufferedRead(void * const To, unsigned long long const Size) override
+ {
+ ssize_t Res;
+ if (lzma->eof == true)
+ return 0;
+
+ lzma->stream.next_out = (uint8_t *) To;
+ lzma->stream.avail_out = Size;
+ if (lzma->stream.avail_in == 0)
+ {
+ lzma->stream.next_in = lzma->buffer;
+ lzma->stream.avail_in = fread(lzma->buffer, 1, sizeof(lzma->buffer)/sizeof(lzma->buffer[0]), lzma->file);
+ }
+ lzma->err = lzma_code(&lzma->stream, LZMA_RUN);
+ if (lzma->err == LZMA_STREAM_END)
+ {
+ lzma->eof = true;
+ Res = Size - lzma->stream.avail_out;
+ }
+ else if (lzma->err != LZMA_OK)
+ {
+ Res = -1;
+ errno = 0;
+ }
+ else
+ {
+ Res = Size - lzma->stream.avail_out;
+ if (Res == 0)
+ {
+ // lzma run was okay, but produced no output…
+ Res = -1;
+ errno = EINTR;
+ }
+ }
+ return Res;
+ }
+ virtual bool InternalReadError() override
+ {
+ return filefd->FileFdError("lzma_read: %s (%d)", _("Read error"), lzma->err);
+ }
+ virtual ssize_t InternalWrite(void const * const From, unsigned long long const Size) override
+ {
+ lzma->stream.next_in = (uint8_t *)From;
+ lzma->stream.avail_in = Size;
+ lzma->stream.next_out = lzma->buffer;
+ lzma->stream.avail_out = sizeof(lzma->buffer)/sizeof(lzma->buffer[0]);
+ lzma->err = lzma_code(&lzma->stream, LZMA_RUN);
+ if (lzma->err != LZMA_OK)
+ return -1;
+ size_t const n = sizeof(lzma->buffer)/sizeof(lzma->buffer[0]) - lzma->stream.avail_out;
+ size_t const m = (n == 0) ? 0 : fwrite(lzma->buffer, 1, n, lzma->file);
+ if (m != n)
+ return -1;
+ else
+ return Size - lzma->stream.avail_in;
+ }
+ virtual bool InternalWriteError() override
+ {
+ return filefd->FileFdError("lzma_write: %s (%d)", _("Write error"), lzma->err);
+ }
+ virtual bool InternalStream() const override { return true; }
+ virtual bool InternalClose(std::string const &) override
+ {
+ delete lzma;
+ lzma = nullptr;
+ return true;
+ }
+
+ explicit LzmaFileFdPrivate(FileFd * const filefd) : FileFdPrivate(filefd), lzma(nullptr) {}
+ virtual ~LzmaFileFdPrivate() { InternalClose(""); }
+#endif
+};
+ /*}}}*/
+class APT_HIDDEN PipedFileFdPrivate: public FileFdPrivate /*{{{*/
+/* if we don't have a specific class dealing with library calls, we (un)compress
+ by executing a specified binary and pipe in/out what we need */
+{
+public:
+ virtual bool InternalOpen(int const, unsigned int const Mode) override
+ {
+ // collect zombies here in case we reopen
+ if (compressor_pid > 0)
+ ExecWait(compressor_pid, "FileFdCompressor", true);
+
+ if ((Mode & FileFd::ReadWrite) == FileFd::ReadWrite)
+ return filefd->FileFdError("ReadWrite mode is not supported for file %s", filefd->FileName.c_str());
+
+ bool const Comp = (Mode & FileFd::WriteOnly) == FileFd::WriteOnly;
+ if (Comp == false)
+ {
+ // Handle 'decompression' of empty files
+ struct stat Buf;
+ fstat(filefd->iFd, &Buf);
+ if (Buf.st_size == 0 && S_ISFIFO(Buf.st_mode) == false)
+ return true;
+
+ // We don't need the file open - instead let the compressor open it
+ // as he properly knows better how to efficiently read from 'his' file
+ if (filefd->FileName.empty() == false)
+ {
+ close(filefd->iFd);
+ filefd->iFd = -1;
+ }
+ }
+
+ // Create a data pipe
+ int Pipe[2] = {-1,-1};
+ if (pipe(Pipe) != 0)
+ return filefd->FileFdErrno("pipe",_("Failed to create subprocess IPC"));
+ for (int J = 0; J != 2; J++)
+ SetCloseExec(Pipe[J],true);
+
+ compressed_fd = filefd->iFd;
+ set_is_pipe(true);
+
+ if (Comp == true)
+ filefd->iFd = Pipe[1];
+ else
+ filefd->iFd = Pipe[0];
+
+ // The child..
+ compressor_pid = ExecFork();
+ if (compressor_pid == 0)
+ {
+ if (Comp == true)
+ {
+ dup2(compressed_fd,STDOUT_FILENO);
+ dup2(Pipe[0],STDIN_FILENO);
+ }
+ else
+ {
+ if (compressed_fd != -1)
+ dup2(compressed_fd,STDIN_FILENO);
+ dup2(Pipe[1],STDOUT_FILENO);
+ }
+ int const nullfd = open("/dev/null", O_WRONLY);
+ if (nullfd != -1)
+ {
+ dup2(nullfd,STDERR_FILENO);
+ close(nullfd);
+ }
+
+ SetCloseExec(STDOUT_FILENO,false);
+ SetCloseExec(STDIN_FILENO,false);
+
+ std::vector<char const*> Args;
+ Args.push_back(compressor.Binary.c_str());
+ std::vector<std::string> const * const addArgs =
+ (Comp == true) ? &(compressor.CompressArgs) : &(compressor.UncompressArgs);
+ for (std::vector<std::string>::const_iterator a = addArgs->begin();
+ a != addArgs->end(); ++a)
+ Args.push_back(a->c_str());
+ if (Comp == false && filefd->FileName.empty() == false)
+ {
+ // commands not needing arguments, do not need to be told about using standard output
+ // in reality, only testcases with tools like cat, rev, rot13, … are able to trigger this
+ if (compressor.CompressArgs.empty() == false && compressor.UncompressArgs.empty() == false)
+ Args.push_back("--stdout");
+ if (filefd->TemporaryFileName.empty() == false)
+ Args.push_back(filefd->TemporaryFileName.c_str());
+ else
+ Args.push_back(filefd->FileName.c_str());
+ }
+ Args.push_back(NULL);
+
+ execvp(Args[0],(char **)&Args[0]);
+ cerr << _("Failed to exec compressor ") << Args[0] << endl;
+ _exit(100);
+ }
+ if (Comp == true)
+ close(Pipe[0]);
+ else
+ close(Pipe[1]);
+
+ return true;
+ }
+ virtual ssize_t InternalUnbufferedRead(void * const To, unsigned long long const Size) override
+ {
+ return read(filefd->iFd, To, Size);
+ }
+ virtual ssize_t InternalWrite(void const * const From, unsigned long long const Size) override
+ {
+ return write(filefd->iFd, From, Size);
+ }
+ virtual bool InternalClose(std::string const &) override
+ {
+ bool Ret = true;
+ if (compressor_pid > 0)
+ Ret &= ExecWait(compressor_pid, "FileFdCompressor", true);
+ compressor_pid = -1;
+ return Ret;
+ }
+ explicit PipedFileFdPrivate(FileFd * const filefd) : FileFdPrivate(filefd) {}
+ virtual ~PipedFileFdPrivate() { InternalClose(""); }
+};
+ /*}}}*/
+class APT_HIDDEN DirectFileFdPrivate: public FileFdPrivate /*{{{*/
+{
+public:
+ virtual bool InternalOpen(int const, unsigned int const) override { return true; }
+ virtual ssize_t InternalUnbufferedRead(void * const To, unsigned long long const Size) override
+ {
+ return read(filefd->iFd, To, Size);
+ }
+ virtual ssize_t InternalWrite(void const * const From, unsigned long long const Size) override
+ {
+ // files opened read+write are strange and only really "supported" for direct files
+ if (buffer.size() != 0)
+ {
+ lseek(filefd->iFd, -buffer.size(), SEEK_CUR);
+ buffer.reset();
+ }
+ return write(filefd->iFd, From, Size);
+ }
+ virtual bool InternalSeek(unsigned long long const To) override
+ {
+ off_t const res = lseek(filefd->iFd, To, SEEK_SET);
+ if (res != (off_t)To)
+ return filefd->FileFdError("Unable to seek to %llu", To);
+ seekpos = To;
+ buffer.reset();
+ return true;
+ }
+ virtual bool InternalSkip(unsigned long long Over) override
+ {
+ if (Over >= buffer.size())
+ {
+ Over -= buffer.size();
+ buffer.reset();
+ }
+ else
+ {
+ buffer.bufferstart += Over;
+ return true;
+ }
+ if (Over == 0)
+ return true;
+ off_t const res = lseek(filefd->iFd, Over, SEEK_CUR);
+ if (res < 0)
+ return filefd->FileFdError("Unable to seek ahead %llu",Over);
+ seekpos = res;
+ return true;
+ }
+ virtual bool InternalTruncate(unsigned long long const To) override
+ {
+ if (buffer.size() != 0)
+ {
+ unsigned long long const seekpos = lseek(filefd->iFd, 0, SEEK_CUR);
+ if ((seekpos - buffer.size()) >= To)
+ buffer.reset();
+ else if (seekpos >= To)
+ buffer.bufferend = (To - seekpos) + buffer.bufferstart;
+ else
+ buffer.reset();
+ }
+ if (ftruncate(filefd->iFd, To) != 0)
+ return filefd->FileFdError("Unable to truncate to %llu",To);
+ return true;
+ }
+ virtual unsigned long long InternalTell() override
+ {
+ return lseek(filefd->iFd,0,SEEK_CUR) - buffer.size();
+ }
+ virtual unsigned long long InternalSize() override
+ {
+ return filefd->FileSize();
+ }
+ virtual bool InternalClose(std::string const &) override { return true; }
+ virtual bool InternalAlwaysAutoClose() const override { return false; }
+
+ explicit DirectFileFdPrivate(FileFd * const filefd) : FileFdPrivate(filefd) {}
+ virtual ~DirectFileFdPrivate() { InternalClose(""); }
+};
+ /*}}}*/
+// FileFd Constructors /*{{{*/
+FileFd::FileFd(std::string FileName,unsigned int const Mode,unsigned long AccessMode) : iFd(-1), Flags(0), d(NULL)
+{
+ Open(FileName,Mode, None, AccessMode);
+}
+FileFd::FileFd(std::string FileName,unsigned int const Mode, CompressMode Compress, unsigned long AccessMode) : iFd(-1), Flags(0), d(NULL)
+{
+ Open(FileName,Mode, Compress, AccessMode);
+}
+FileFd::FileFd() : iFd(-1), Flags(AutoClose), d(NULL) {}
+FileFd::FileFd(int const Fd, unsigned int const Mode, CompressMode Compress) : iFd(-1), Flags(0), d(NULL)
+{
+ OpenDescriptor(Fd, Mode, Compress);
+}
+FileFd::FileFd(int const Fd, bool const AutoClose) : iFd(-1), Flags(0), d(NULL)
+{
+ OpenDescriptor(Fd, ReadWrite, None, AutoClose);
+}
+ /*}}}*/
+// FileFd::Open - Open a file /*{{{*/