First few external tests with test/unit, some minor tidying of internal data
structures.
This commit is contained in:
11
Rakefile
11
Rakefile
@@ -17,13 +17,20 @@ end
|
|||||||
rule 'default' => 'flexnbd'
|
rule 'default' => 'flexnbd'
|
||||||
|
|
||||||
namespace "test" do
|
namespace "test" do
|
||||||
|
task 'run' => ["unit", "scenarios"]
|
||||||
|
|
||||||
task 'build' => TEST_MODULES.map { |n| "tests/check_#{n}" }
|
task 'build' => TEST_MODULES.map { |n| "tests/check_#{n}" }
|
||||||
task 'run' => 'build' do
|
|
||||||
|
task 'unit' => 'build' do
|
||||||
TEST_MODULES.each do |n|
|
TEST_MODULES.each do |n|
|
||||||
ENV['EF_DISABLE_BANNER'] = '1'
|
ENV['EF_DISABLE_BANNER'] = '1'
|
||||||
sh "./tests/check_#{n}"
|
sh "./tests/check_#{n}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
task 'scenarios' do
|
||||||
|
sh "cd tests; ruby nbd_scenarios"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def gcc_link(target, objects)
|
def gcc_link(target, objects)
|
||||||
@@ -46,7 +53,7 @@ rule '.o' => '.c' do |t|
|
|||||||
end
|
end
|
||||||
|
|
||||||
rule 'clean' do
|
rule 'clean' do
|
||||||
sh "rm -f flexnbd " + (
|
sh "rm -f *~ flexnbd " + (
|
||||||
OBJECTS +
|
OBJECTS +
|
||||||
TEST_MODULES.map { |n| ["tests/check_#{n}", "tests/check_#{n}.o"] }.flatten
|
TEST_MODULES.map { |n| ["tests/check_#{n}", "tests/check_#{n}.o"] }.flatten
|
||||||
).
|
).
|
||||||
|
48
control.c
48
control.c
@@ -8,6 +8,7 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <sys/un.h>
|
#include <sys/un.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
static const int longest_run = 8<<20;
|
static const int longest_run = 8<<20;
|
||||||
|
|
||||||
@@ -15,16 +16,20 @@ void* mirror_runner(void* serve_params_uncast)
|
|||||||
{
|
{
|
||||||
struct mode_serve_params *serve = (struct mode_serve_params*) serve_params_uncast;
|
struct mode_serve_params *serve = (struct mode_serve_params*) serve_params_uncast;
|
||||||
|
|
||||||
|
const int max_passes = 7; /* biblical */
|
||||||
int pass;
|
int pass;
|
||||||
struct bitset_mapping *map = serve->mirror->dirty_map;
|
struct bitset_mapping *map = serve->mirror->dirty_map;
|
||||||
|
|
||||||
for (pass=0; pass < 7 /* biblical */; pass++) {
|
for (pass=0; pass < max_passes; pass++) {
|
||||||
uint64_t current = 0;
|
uint64_t current = 0;
|
||||||
|
uint64_t written = 0;
|
||||||
|
|
||||||
debug("mirror start pass=%d", pass);
|
debug("mirror start pass=%d", pass);
|
||||||
|
|
||||||
while (current < serve->size) {
|
while (current < serve->size) {
|
||||||
int run = bitset_run_count(map, current, longest_run);
|
int run;
|
||||||
|
|
||||||
|
run = bitset_run_count(map, current, longest_run);
|
||||||
|
|
||||||
debug("mirror current=%ld, run=%d", current, run);
|
debug("mirror current=%ld, run=%d", current, run);
|
||||||
|
|
||||||
@@ -41,9 +46,13 @@ void* mirror_runner(void* serve_params_uncast)
|
|||||||
);
|
);
|
||||||
|
|
||||||
bitset_clear_range(map, current, run);
|
bitset_clear_range(map, current, run);
|
||||||
|
written += run;
|
||||||
}
|
}
|
||||||
current += run;
|
current += run;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (written == 0)
|
||||||
|
pass = max_passes-1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
@@ -58,9 +67,11 @@ int control_mirror(struct control_params* client, int linesc, char** lines)
|
|||||||
struct mirror_status *mirror;
|
struct mirror_status *mirror;
|
||||||
union mysockaddr connect_to;
|
union mysockaddr connect_to;
|
||||||
char s_ip_address[64], s_port[8];
|
char s_ip_address[64], s_port[8];
|
||||||
|
uint64_t max_bytes_per_second;
|
||||||
|
int action_at_finish;
|
||||||
|
|
||||||
if (linesc != 2) {
|
if (linesc < 2) {
|
||||||
write_socket("1: mirror only takes two parameters");
|
write_socket("1: mirror takes at least two parameters");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,6 +87,30 @@ int control_mirror(struct control_params* client, int linesc, char** lines)
|
|||||||
}
|
}
|
||||||
connect_to.v4.sin_port = htobe16(connect_to.v4.sin_port);
|
connect_to.v4.sin_port = htobe16(connect_to.v4.sin_port);
|
||||||
|
|
||||||
|
max_bytes_per_second = 0;
|
||||||
|
if (linesc > 2) {
|
||||||
|
max_bytes_per_second = atoi(lines[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
action_at_finish = ACTION_PROXY;
|
||||||
|
if (linesc > 3) {
|
||||||
|
if (strcmp("proxy", lines[3]) == 0)
|
||||||
|
action_at_finish = ACTION_PROXY;
|
||||||
|
else if (strcmp("exit", lines[3]) == 0)
|
||||||
|
action_at_finish = ACTION_EXIT;
|
||||||
|
else if (strcmp("nothing", lines[3]) == 0)
|
||||||
|
action_at_finish = ACTION_NOTHING;
|
||||||
|
else {
|
||||||
|
write_socket("1: action must be one of 'proxy', 'exit' or 'nothing'");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (linesc > 4) {
|
||||||
|
write_socket("1: unrecognised parameters to mirror");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
fd = socket_connect(&connect_to.generic);
|
fd = socket_connect(&connect_to.generic);
|
||||||
|
|
||||||
remote_size = socket_nbd_read_hello(fd);
|
remote_size = socket_nbd_read_hello(fd);
|
||||||
@@ -83,7 +118,8 @@ int control_mirror(struct control_params* client, int linesc, char** lines)
|
|||||||
|
|
||||||
mirror = xmalloc(sizeof(struct mirror_status));
|
mirror = xmalloc(sizeof(struct mirror_status));
|
||||||
mirror->client = fd;
|
mirror->client = fd;
|
||||||
mirror->max_bytes_per_second = 0;
|
mirror->max_bytes_per_second = max_bytes_per_second;
|
||||||
|
mirror->action_at_finish = action_at_finish;
|
||||||
|
|
||||||
CLIENT_ERROR_ON_FAILURE(
|
CLIENT_ERROR_ON_FAILURE(
|
||||||
open_and_mmap(
|
open_and_mmap(
|
||||||
@@ -122,7 +158,7 @@ int control_acl(struct control_params* client, int linesc, char** lines)
|
|||||||
|
|
||||||
parsed = parse_acl(&acl, linesc, lines);
|
parsed = parse_acl(&acl, linesc, lines);
|
||||||
if (parsed != linesc) {
|
if (parsed != linesc) {
|
||||||
write(client->socket, "3: bad spec ", 12);
|
write(client->socket, "1: bad spec ", 12);
|
||||||
write(client->socket, s_acl_entry[parsed],
|
write(client->socket, s_acl_entry[parsed],
|
||||||
strlen(s_acl_entry[parsed]));
|
strlen(s_acl_entry[parsed]));
|
||||||
write(client->socket, "\n", 1);
|
write(client->socket, "\n", 1);
|
||||||
|
26
params.h
26
params.h
@@ -8,11 +8,18 @@
|
|||||||
|
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
enum mirror_finish_action {
|
||||||
|
ACTION_PROXY,
|
||||||
|
ACTION_EXIT,
|
||||||
|
ACTION_NOTHING
|
||||||
|
};
|
||||||
|
|
||||||
struct mirror_status {
|
struct mirror_status {
|
||||||
pthread_t thread;
|
pthread_t thread;
|
||||||
int client;
|
int client;
|
||||||
char *filename;
|
char *filename;
|
||||||
off64_t max_bytes_per_second;
|
off64_t max_bytes_per_second;
|
||||||
|
enum mirror_finish_action action_at_finish;
|
||||||
|
|
||||||
char *mapped;
|
char *mapped;
|
||||||
struct bitset_mapping *dirty_map;
|
struct bitset_mapping *dirty_map;
|
||||||
@@ -24,13 +31,29 @@ struct control_params {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct mode_serve_params {
|
struct mode_serve_params {
|
||||||
|
/* address/port to bind to */
|
||||||
union mysockaddr bind_to;
|
union mysockaddr bind_to;
|
||||||
|
/* number of entries in current access control list*/
|
||||||
int acl_entries;
|
int acl_entries;
|
||||||
|
/* pointer to access control list entries*/
|
||||||
struct ip_and_mask (*acl)[0];
|
struct ip_and_mask (*acl)[0];
|
||||||
|
/* file name to serve */
|
||||||
char* filename;
|
char* filename;
|
||||||
|
/* TCP backlog for listen() */
|
||||||
int tcp_backlog;
|
int tcp_backlog;
|
||||||
|
/* file name of UNIX control socket (or NULL if none) */
|
||||||
char* control_socket_name;
|
char* control_socket_name;
|
||||||
|
/* size of file */
|
||||||
off64_t size;
|
off64_t size;
|
||||||
|
/* if you want the main thread to pause, set this to an writeable
|
||||||
|
* file descriptor. The main thread will then write a byte once it
|
||||||
|
* promises to hang any further writes.
|
||||||
|
*/
|
||||||
|
int pause_fd;
|
||||||
|
/* the main thread will set this when writes will be paused */
|
||||||
|
int paused;
|
||||||
|
/* set to non-zero to use given destination connection as proxy */
|
||||||
|
int proxy_fd;
|
||||||
|
|
||||||
struct mirror_status* mirror;
|
struct mirror_status* mirror;
|
||||||
int server;
|
int server;
|
||||||
@@ -49,13 +72,10 @@ struct mode_readwrite_params {
|
|||||||
|
|
||||||
struct client_params {
|
struct client_params {
|
||||||
int socket;
|
int socket;
|
||||||
char* filename;
|
|
||||||
|
|
||||||
int fileno;
|
int fileno;
|
||||||
off64_t size;
|
|
||||||
char* mapped;
|
char* mapped;
|
||||||
|
|
||||||
char* block_allocation_map;
|
|
||||||
struct mode_serve_params* serve; /* FIXME: remove above duplication */
|
struct mode_serve_params* serve; /* FIXME: remove above duplication */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -97,7 +97,7 @@ void socket_nbd_write(int fd, off64_t from, int len, int in_fd, void* in_buf)
|
|||||||
|
|
||||||
#define CHECK_RANGE(error_type) { \
|
#define CHECK_RANGE(error_type) { \
|
||||||
off64_t size = socket_nbd_read_hello(params->client); \
|
off64_t size = socket_nbd_read_hello(params->client); \
|
||||||
if (params->from < 0 || (params->from + params->len) >= size) \
|
if (params->from < 0 || (params->from + params->len) > size) \
|
||||||
SERVER_ERROR(error_type \
|
SERVER_ERROR(error_type \
|
||||||
" request %d+%d is out of range given size %d", \
|
" request %d+%d is out of range given size %d", \
|
||||||
params->from, params->len, size\
|
params->from, params->len, size\
|
||||||
|
19
serve.c
19
serve.c
@@ -37,7 +37,7 @@ static inline void dirty(struct mode_serve_params *serve, off64_t from, int len)
|
|||||||
*/
|
*/
|
||||||
void write_not_zeroes(struct client_params* client, off64_t from, int len)
|
void write_not_zeroes(struct client_params* client, off64_t from, int len)
|
||||||
{
|
{
|
||||||
char *map = client->block_allocation_map;
|
char *map = client->serve->block_allocation_map;
|
||||||
|
|
||||||
while (len > 0) {
|
while (len > 0) {
|
||||||
/* so we have to calculate how much of our input to consider
|
/* so we have to calculate how much of our input to consider
|
||||||
@@ -152,7 +152,7 @@ int client_serve_request(struct client_params* client)
|
|||||||
case REQUEST_WRITE:
|
case REQUEST_WRITE:
|
||||||
/* check it's not out of range */
|
/* check it's not out of range */
|
||||||
if (be64toh(request.from) < 0 ||
|
if (be64toh(request.from) < 0 ||
|
||||||
be64toh(request.from)+be32toh(request.len) > client->size) {
|
be64toh(request.from)+be32toh(request.len) > client->serve->size) {
|
||||||
debug("request read %ld+%d out of range",
|
debug("request read %ld+%d out of range",
|
||||||
be64toh(request.from),
|
be64toh(request.from),
|
||||||
be32toh(request.len)
|
be32toh(request.len)
|
||||||
@@ -193,7 +193,7 @@ int client_serve_request(struct client_params* client)
|
|||||||
|
|
||||||
case REQUEST_WRITE:
|
case REQUEST_WRITE:
|
||||||
debug("request write %ld+%d", be64toh(request.from), be32toh(request.len));
|
debug("request write %ld+%d", be64toh(request.from), be32toh(request.len));
|
||||||
if (client->block_allocation_map) {
|
if (client->serve->block_allocation_map) {
|
||||||
write_not_zeroes(
|
write_not_zeroes(
|
||||||
client,
|
client,
|
||||||
be64toh(request.from),
|
be64toh(request.from),
|
||||||
@@ -226,7 +226,7 @@ void client_send_hello(struct client_params* client)
|
|||||||
|
|
||||||
memcpy(init.passwd, INIT_PASSWD, sizeof(INIT_PASSWD));
|
memcpy(init.passwd, INIT_PASSWD, sizeof(INIT_PASSWD));
|
||||||
init.magic = htobe64(INIT_MAGIC);
|
init.magic = htobe64(INIT_MAGIC);
|
||||||
init.size = htobe64(client->size);
|
init.size = htobe64(client->serve->size);
|
||||||
memset(init.reserved, 0, 128);
|
memset(init.reserved, 0, 128);
|
||||||
CLIENT_ERROR_ON_FAILURE(
|
CLIENT_ERROR_ON_FAILURE(
|
||||||
writeloop(client->socket, &init, sizeof(init)),
|
writeloop(client->socket, &init, sizeof(init)),
|
||||||
@@ -241,12 +241,12 @@ void* client_serve(void* client_uncast)
|
|||||||
//client_open_file(client);
|
//client_open_file(client);
|
||||||
CLIENT_ERROR_ON_FAILURE(
|
CLIENT_ERROR_ON_FAILURE(
|
||||||
open_and_mmap(
|
open_and_mmap(
|
||||||
client->filename,
|
client->serve->filename,
|
||||||
&client->fileno,
|
&client->fileno,
|
||||||
&client->size,
|
NULL,
|
||||||
(void**) &client->mapped
|
(void**) &client->mapped
|
||||||
),
|
),
|
||||||
"Couldn't open/mmap file %s", client->filename
|
"Couldn't open/mmap file %s", client->serve->filename
|
||||||
);
|
);
|
||||||
client_send_hello(client);
|
client_send_hello(client);
|
||||||
|
|
||||||
@@ -262,7 +262,7 @@ void* client_serve(void* client_uncast)
|
|||||||
|
|
||||||
close(client->socket);
|
close(client->socket);
|
||||||
close(client->fileno);
|
close(client->fileno);
|
||||||
munmap(client->mapped, client->size);
|
munmap(client->mapped, client->serve->size);
|
||||||
|
|
||||||
free(client);
|
free(client);
|
||||||
return NULL;
|
return NULL;
|
||||||
@@ -357,9 +357,6 @@ void accept_nbd_client(struct mode_serve_params* params, int client_fd, struct s
|
|||||||
|
|
||||||
client_params = xmalloc(sizeof(struct client_params));
|
client_params = xmalloc(sizeof(struct client_params));
|
||||||
client_params->socket = client_fd;
|
client_params->socket = client_fd;
|
||||||
client_params->filename = params->filename;
|
|
||||||
client_params->block_allocation_map =
|
|
||||||
params->block_allocation_map;
|
|
||||||
client_params->serve = params;
|
client_params->serve = params;
|
||||||
|
|
||||||
SERVER_ERROR_ON_FAILURE(
|
SERVER_ERROR_ON_FAILURE(
|
||||||
|
63
tests/flexnbd.rb
Normal file
63
tests/flexnbd.rb
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
require 'socket'
|
||||||
|
|
||||||
|
# Noddy test class to exercise FlexNBD from the outside for testing.
|
||||||
|
#
|
||||||
|
class FlexNBD
|
||||||
|
attr_reader :bin, :ctrl, :pid, :ip, :port
|
||||||
|
|
||||||
|
def initialize(bin, ip, port)
|
||||||
|
@bin = bin
|
||||||
|
raise "#{bin} not executable" unless File.executable?(bin)
|
||||||
|
@ctrl = "/tmp/.flexnbd.ctrl.#{Time.now.to_i}.#{rand}"
|
||||||
|
@ip = ip
|
||||||
|
@port = port
|
||||||
|
end
|
||||||
|
|
||||||
|
def serve(ip, port, file, *acl)
|
||||||
|
@pid = fork do
|
||||||
|
exec("#{@bin} serve #{ip} #{port} #{file} #{ctrl} #{acl.join(' ')}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def kill
|
||||||
|
Process.kill("INT", @pid)
|
||||||
|
Process.wait(@pid)
|
||||||
|
end
|
||||||
|
|
||||||
|
def read(offset, length)
|
||||||
|
IO.popen("#{@bin} read #{ip} #{port} #{offset} #{length}","r") do |fh|
|
||||||
|
return fh.read
|
||||||
|
end
|
||||||
|
raise "read failed" unless $?.success?
|
||||||
|
end
|
||||||
|
|
||||||
|
def write(offset, data)
|
||||||
|
IO.popen("#{@bin} write #{ip} #{port} #{offset} #{data.length}","w") do |fh|
|
||||||
|
fh.write(data)
|
||||||
|
end
|
||||||
|
raise "write failed" unless $?.success?
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def mirror(bandwidth=nil, action=nil)
|
||||||
|
control_command("mirror", ip, port, bandwidth, action)
|
||||||
|
end
|
||||||
|
|
||||||
|
def acl(*acl)
|
||||||
|
control_command("acl", *acl)
|
||||||
|
end
|
||||||
|
|
||||||
|
def status
|
||||||
|
end
|
||||||
|
|
||||||
|
protected
|
||||||
|
def control_command(*args)
|
||||||
|
raise "Server not running" unless @pid
|
||||||
|
args = args.compact
|
||||||
|
UNIXSocket.open(@ctrl) do |u|
|
||||||
|
u.write(args.join("\n") + "\n")
|
||||||
|
code, message = u.readline.split(": ", 2)
|
||||||
|
return [code, message]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
71
tests/nbd_scenarios
Normal file
71
tests/nbd_scenarios
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
#!/usr/bin/ruby
|
||||||
|
|
||||||
|
require 'test/unit'
|
||||||
|
require 'flexnbd'
|
||||||
|
require 'test_file_writer'
|
||||||
|
|
||||||
|
class NBDScenarios < Test::Unit::TestCase
|
||||||
|
def setup
|
||||||
|
@blocksize = 1024
|
||||||
|
@filename1 = ".flexnbd.test.#{$$}.#{Time.now.to_i}.1"
|
||||||
|
@filename2 = ".flexnbd.test.#{$$}.#{Time.now.to_i}.2"
|
||||||
|
@ip = "127.0.0.1"
|
||||||
|
@available_ports = [*40000..41000] - listening_ports
|
||||||
|
@port1 = @available_ports.shift
|
||||||
|
@port2 = @available_ports.shift
|
||||||
|
@nbd1 = FlexNBD.new("../flexnbd", @ip, @port1)
|
||||||
|
end
|
||||||
|
|
||||||
|
def teardown
|
||||||
|
@nbd1.kill rescue nil
|
||||||
|
[@filename1, @filename2].each do |f|
|
||||||
|
File.unlink(f) if File.exists?(f)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_read1
|
||||||
|
writefile1("f"*64)
|
||||||
|
serve1
|
||||||
|
|
||||||
|
[0, 12, 63].each do |num|
|
||||||
|
|
||||||
|
assert_equal(
|
||||||
|
@nbd1.read(num*@blocksize, @blocksize),
|
||||||
|
@file1.read(num*@blocksize, @blocksize)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
[124, 1200, 10028, 25488].each do |num|
|
||||||
|
assert_equal(@nbd1.read(num, 4), @file1.read(num, 4))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_writeread1
|
||||||
|
writefile1("0"*64)
|
||||||
|
serve1
|
||||||
|
|
||||||
|
[0, 12, 63].each do |num|
|
||||||
|
data = "X"*@blocksize
|
||||||
|
@nbd1.write(num*@blocksize, data)
|
||||||
|
assert_equal(data, @file1.read(num*@blocksize, data.size))
|
||||||
|
assert_equal(data, @nbd1.read(num*@blocksize, data.size))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
protected
|
||||||
|
def serve1(*acl)
|
||||||
|
@nbd1.serve(@ip, @port1, @filename1, *acl)
|
||||||
|
end
|
||||||
|
|
||||||
|
def writefile1(data)
|
||||||
|
@file1 = TestFileWriter.new(@filename1, @blocksize).write(data)
|
||||||
|
end
|
||||||
|
|
||||||
|
def listening_ports
|
||||||
|
`netstat -ltn`.
|
||||||
|
split("\n").
|
||||||
|
map { |x| x.split(/\s+/) }[2..-1].
|
||||||
|
map { |l| l[3].split(":")[-1].to_i }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
83
tests/test_file_writer.rb
Normal file
83
tests/test_file_writer.rb
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
# Noddy test class for writing files to disc in predictable patterns
|
||||||
|
# in order to test FlexNBD.
|
||||||
|
#
|
||||||
|
class TestFileWriter
|
||||||
|
def initialize(filename, blocksize)
|
||||||
|
@fh = File.open(filename, "w+")
|
||||||
|
@blocksize = blocksize
|
||||||
|
@pattern = ""
|
||||||
|
end
|
||||||
|
|
||||||
|
# We write in fixed block sizes, given by "blocksize"
|
||||||
|
# _ means skip a block
|
||||||
|
# 0 means write a block full of zeroes
|
||||||
|
# f means write a block with the file offset packed every 4 bytes
|
||||||
|
#
|
||||||
|
def write(data)
|
||||||
|
@pattern += data
|
||||||
|
|
||||||
|
data.split("").each do |code|
|
||||||
|
if code == "_"
|
||||||
|
@fh.seek(@blocksize, IO::SEEK_CUR)
|
||||||
|
else
|
||||||
|
@fh.write(data(code))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
@fh.flush
|
||||||
|
self
|
||||||
|
end
|
||||||
|
|
||||||
|
# Returns what the data ought to be at the given offset and length
|
||||||
|
#
|
||||||
|
def read_original(off, len)
|
||||||
|
r=""
|
||||||
|
current = 0
|
||||||
|
@pattern.split("").each do |block|
|
||||||
|
if off >= current && (off+len) < current + blocksize
|
||||||
|
current += data(block, current)[
|
||||||
|
current-off..(current+blocksize)-(off+len)
|
||||||
|
]
|
||||||
|
end
|
||||||
|
current += @blocksize
|
||||||
|
end
|
||||||
|
r
|
||||||
|
end
|
||||||
|
|
||||||
|
# Read what's actually in the file
|
||||||
|
#
|
||||||
|
def read(off, len)
|
||||||
|
@fh.seek(off, IO::SEEK_SET)
|
||||||
|
@fh.read(len)
|
||||||
|
end
|
||||||
|
|
||||||
|
def untouched?(offset, len)
|
||||||
|
read(off, len) == read_original(off, len)
|
||||||
|
end
|
||||||
|
|
||||||
|
def close
|
||||||
|
@fh.close
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
protected
|
||||||
|
|
||||||
|
def data(code, at=@fh.tell)
|
||||||
|
case code
|
||||||
|
when "0", "_"
|
||||||
|
"\0" * @blocksize
|
||||||
|
when "X"
|
||||||
|
"X" * @blocksize
|
||||||
|
when "f"
|
||||||
|
r = ""
|
||||||
|
(@blocksize/4).times do
|
||||||
|
r += [at].pack("I")
|
||||||
|
at += 4
|
||||||
|
end
|
||||||
|
r
|
||||||
|
else
|
||||||
|
raise "Unknown character '#{block}'"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
Reference in New Issue
Block a user