Commit 17dd21ac authored by Max Kellermann's avatar Max Kellermann

archive/iso9660: fix unaligned reads

Oh the horror! This plugin cannot possibly ever have worked. It was broken from the start, when it was added in commit 37796699 nearly twelve (!) years ago. The plugin would always read at sector boundaries, so it could only ever work at multiples of 2 kB.
parent 1a5e0ef7
...@@ -5,6 +5,7 @@ ver 0.21.26 (not yet released) ...@@ -5,6 +5,7 @@ ver 0.21.26 (not yet released)
* archive * archive
- bzip2: fix crash on corrupt bzip2 file - bzip2: fix crash on corrupt bzip2 file
- bzip2: flush output at end of input file - bzip2: flush output at end of input file
- iso9660: fix unaligned reads
- zzip: fix crash on corrupt ZIP file - zzip: fix crash on corrupt ZIP file
* decoder * decoder
- sndfile: fix lost samples at end of file - sndfile: fix lost samples at end of file
......
...@@ -29,9 +29,12 @@ ...@@ -29,9 +29,12 @@
#include "fs/Path.hxx" #include "fs/Path.hxx"
#include "util/RuntimeError.hxx" #include "util/RuntimeError.hxx"
#include "util/StringCompare.hxx" #include "util/StringCompare.hxx"
#include "util/WritableBuffer.hxx"
#include <cdio/iso9660.h> #include <cdio/iso9660.h>
#include <array>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
...@@ -144,6 +147,47 @@ class Iso9660InputStream final : public InputStream { ...@@ -144,6 +147,47 @@ class Iso9660InputStream final : public InputStream {
const lsn_t lsn; const lsn_t lsn;
/**
* libiso9660 can only read whole sectors at a time, and this
* buffer is used to store one whole sector and allow Read()
* to handle partial sector reads.
*/
class BlockBuffer {
size_t position = 0, fill = 0;
std::array<uint8_t, ISO_BLOCKSIZE> data;
public:
ConstBuffer<uint8_t> Read() const noexcept {
assert(fill <= data.size());
assert(position <= fill);
return {&data[position], &data[fill]};
}
void Consume(size_t nbytes) noexcept {
assert(nbytes <= Read().size);
position += nbytes;
}
WritableBuffer<uint8_t> Write() noexcept {
assert(Read().empty());
return {data.data(), data.size()};
}
void Append(size_t nbytes) noexcept {
assert(Read().empty());
assert(nbytes <= data.size());
fill = nbytes;
position = 0;
}
};
BlockBuffer buffer;
public: public:
Iso9660InputStream(const std::shared_ptr<Iso9660> &_iso, Iso9660InputStream(const std::shared_ptr<Iso9660> &_iso,
const char *_uri, const char *_uri,
...@@ -182,35 +226,56 @@ Iso9660ArchiveFile::OpenStream(const char *pathname, ...@@ -182,35 +226,56 @@ Iso9660ArchiveFile::OpenStream(const char *pathname,
size_t size_t
Iso9660InputStream::Read(void *ptr, size_t read_size) Iso9660InputStream::Read(void *ptr, size_t read_size)
{ {
const offset_type remaining = size - offset;
if (remaining == 0)
return 0;
if (offset_type(read_size) > remaining)
read_size = remaining;
auto r = buffer.Read();
if (r.empty()) {
/* the buffer is empty - read more data from the ISO file */
assert(offset % ISO_BLOCKSIZE == 0);
const ScopeUnlock unlock(mutex); const ScopeUnlock unlock(mutex);
int readed = 0; const lsn_t read_lsn = lsn + offset / ISO_BLOCKSIZE;
int no_blocks, cur_block;
size_t left_bytes = size - offset;
if (left_bytes < read_size) { if (read_size >= ISO_BLOCKSIZE) {
no_blocks = CEILING(left_bytes, ISO_BLOCKSIZE); /* big read - read right into the caller's buffer */
} else {
no_blocks = read_size / ISO_BLOCKSIZE;
}
if (no_blocks == 0) auto nbytes = iso->SeekRead(ptr, read_lsn,
return 0; read_size / ISO_BLOCKSIZE);
if (nbytes <= 0)
throw std::runtime_error("Failed to read ISO9660 file");
cur_block = offset / ISO_BLOCKSIZE; offset += nbytes;
return nbytes;
}
/* fill the buffer */
readed = iso->SeekRead(ptr, lsn + cur_block, no_blocks); auto w = buffer.Write();
auto nbytes = iso->SeekRead(w.data, read_lsn,
w.size / ISO_BLOCKSIZE);
if (nbytes <= 0)
throw std::runtime_error("Failed to read ISO9660 file");
if (readed != no_blocks * ISO_BLOCKSIZE) buffer.Append(nbytes);
throw FormatRuntimeError("error reading ISO file at lsn %lu",
(unsigned long)cur_block);
if (left_bytes < read_size) { r = buffer.Read();
readed = left_bytes;
} }
offset += readed; assert(!r.empty());
return readed;
size_t nbytes = std::min(read_size, r.size);
memcpy(ptr, r.data, nbytes);
buffer.Consume(nbytes);
offset += nbytes;
return nbytes;
} }
bool bool
......
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