ffi-libarchive 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,131 @@
1
+ module Archive
2
+
3
+ # TODO: Remove this forward-declaration
4
+ class BaseArchive
5
+ end
6
+
7
+ class Reader < BaseArchive
8
+
9
+ private_class_method :new
10
+
11
+ def self.open_filename file_name, command = nil
12
+ if block_given?
13
+ reader = open_filename file_name, command
14
+ begin
15
+ yield reader
16
+ ensure
17
+ reader.close
18
+ end
19
+ else
20
+ new :file_name => file_name, :command => command
21
+ end
22
+ end
23
+
24
+ def self.open_memory string, command = nil
25
+ if block_given?
26
+ reader = open_memory string, command
27
+ begin
28
+ yield reader
29
+ ensure
30
+ reader.close
31
+ end
32
+ else
33
+ new :memory => string, :command => command
34
+ end
35
+ end
36
+
37
+ def initialize params = {}
38
+ super C::method(:archive_read_new), C::method(:archive_read_finish)
39
+
40
+ if params[:command]
41
+ cmd = params[:command]
42
+ raise Error, @archive if C::archive_read_support_compression_program(archive, cmd) != C::OK
43
+ else
44
+ raise Error, @archive if C::archive_read_support_compression_all(archive) != C::OK
45
+ end
46
+
47
+ raise Error, @archive if C::archive_read_support_format_all(archive) != C::OK
48
+
49
+ if params[:file_name]
50
+ raise Error, @archive if C::archive_read_open_filename(archive, params[:file_name], 1024) != C::OK
51
+ elsif params[:memory]
52
+ str = params[:memory]
53
+ @data = FFI::MemoryPointer.new str.size
54
+ @data.put_bytes 0, str
55
+ raise Error, @archive if C::archive_read_open_memory(archive, @data, @data.size) != C::OK
56
+ end
57
+ rescue
58
+ close
59
+ raise
60
+ end
61
+
62
+ def extract entry, flags = 0
63
+ raise ArgumentError, "Expected Archive::Entry as first argument" unless entry.kind_of? Entry
64
+ raise ArgumentError, "Expected Integer as second argument" unless entry.kind_of? Integer
65
+
66
+ flags &= EXTRACT_FLAGS
67
+ raise Error, @archive if C::archive_read_extract(archive, entry, flags) != C::OK
68
+ end
69
+
70
+ def header_position
71
+ raise Error, @archive if C::archive_read_header_position archive
72
+ end
73
+
74
+ def next_header
75
+ entry_ptr = FFI::MemoryPointer.new(:pointer)
76
+ case C::archive_read_next_header(archive, entry_ptr)
77
+ when C::OK
78
+ Entry.from_pointer entry_ptr.read_pointer
79
+ when C::EOF
80
+ @eof = true
81
+ nil
82
+ else
83
+ raise Error, @archive
84
+ end
85
+ end
86
+
87
+ def each_entry
88
+ while entry = next_header
89
+ yield entry
90
+ end
91
+ end
92
+
93
+ def each_entry_with_data( size = C::DATA_BUFFER_SIZE )
94
+ while entry = next_header
95
+ yield entry, read_data
96
+ end
97
+ end
98
+
99
+ def read_data size = C::DATA_BUFFER_SIZE, &block
100
+ raise ArgumentError, "Buffer size must be > 0 (was: #{size})" unless size.kind_of?(Integer) and size > 0
101
+
102
+ data = nil
103
+ unless block
104
+ data = ""
105
+ block = data.method :concat
106
+ end
107
+
108
+ buffer = FFI::Buffer.alloc_out(size)
109
+ len = 0
110
+ while (n = C::archive_read_data(archive, buffer, size)) > 0
111
+ case n
112
+ when C::FATAL, C::WARN, C::RETRY
113
+ raise Error, @archive
114
+ else
115
+ block.call buffer.get_bytes(0,n)
116
+ end
117
+ len += n
118
+ end
119
+
120
+ data || len
121
+ end
122
+
123
+ def save_data file_name
124
+ IO.sysopen(file_name, "wb") do |fd|
125
+ raise Error, @archive if C::archive_read_data_into_fd(archive, fd) != C::OK
126
+ end
127
+ end
128
+
129
+ end
130
+
131
+ end
@@ -0,0 +1,31 @@
1
+ require 'ffi-inliner'
2
+
3
+ module Archive
4
+ module Stat
5
+ extend Inliner
6
+ inline do |builder|
7
+ builder.include 'stdlib.h'
8
+ builder.include 'sys/types.h'
9
+ builder.include 'sys/stat.h'
10
+ builder.c %q{
11
+ void* ffi_libarchive_create_stat(const char* filename) {
12
+ struct stat* s = malloc(sizeof(struct stat));
13
+ if (stat(filename, s) != 0) return NULL;
14
+ return s;
15
+ }
16
+ }
17
+ builder.c %q{
18
+ void* ffi_libarchive_create_lstat(const char* filename) {
19
+ struct stat* s = malloc(sizeof(struct stat));
20
+ lstat(filename, s);
21
+ return s;
22
+ }
23
+ }
24
+ builder.c %q{
25
+ void ffi_libarchive_free_stat(void* s) {
26
+ free((struct stat*)s);
27
+ }
28
+ }
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,143 @@
1
+ module Archive
2
+
3
+ # TODO: Remove this forward-declaration
4
+ class BaseArchive
5
+ end
6
+
7
+ class Writer < BaseArchive
8
+
9
+ private_class_method :new
10
+
11
+ def self.open_filename file_name, compression, format
12
+ if block_given?
13
+ writer = open_filename file_name, compression, format
14
+ begin
15
+ yield writer
16
+ ensure
17
+ writer.close
18
+ end
19
+ else
20
+ new :file_name => file_name, :compression => compression, :format => format
21
+ end
22
+ end
23
+
24
+ def self.open_memory string, compression, format
25
+ if block_given?
26
+ writer = open_memory string, compression, format
27
+ begin
28
+ yield writer
29
+ ensure
30
+ writer.close
31
+ end
32
+ else
33
+ if compression.kind_of? String
34
+ command = compression
35
+ compression = -1
36
+ else
37
+ command = nil
38
+ end
39
+ new :memory => string, :compression => compression, :format => format
40
+ end
41
+ end
42
+
43
+ def initialize params = {}
44
+ super C::method(:archive_write_new), C::method(:archive_write_finish)
45
+
46
+ compression = params[:compression]
47
+ case compression
48
+ when Symbol
49
+ compression = Archive::const_get("COMPRESSION_#{compression.to_s.upcase}".intern)
50
+ end
51
+
52
+ format = params[:format]
53
+ case format
54
+ when Symbol
55
+ format = Archive::const_get("FORMAT_#{format.to_s.upcase}".intern)
56
+ end
57
+
58
+ raise Error, @archive if C::archive_write_set_compression(archive, compression) != C::OK
59
+
60
+ raise Error, @archive if C::archive_write_set_format(archive, format) != C::OK
61
+
62
+ if params[:file_name]
63
+ raise Error, @archive if C::archive_write_open_filename(archive, params[:file_name]) != C::OK
64
+ elsif params[:memory]
65
+ @data = write_callback params[:memory]
66
+ raise Error, @archive if C::archive_write_open(archive, nil,
67
+ method(:open_callback),
68
+ @data,
69
+ nil) != C::OK
70
+ end
71
+ rescue => e
72
+ close
73
+ raise
74
+ end
75
+
76
+ def open_callback archive, client
77
+ if C::archive_write_get_bytes_in_last_block(archive) == -1
78
+ C::archive_write_set_bytes_in_last_block(archive, 1)
79
+ end
80
+ C::OK
81
+ end
82
+ private :open_callback
83
+
84
+ def write_callback data
85
+ Proc.new { |ar, client, buffer, length|
86
+ data.concat buffer.get_bytes(0,length)
87
+ length
88
+ }
89
+ end
90
+ private :write_callback
91
+
92
+ def new_entry
93
+ entry = Entry.new
94
+ if block_given?
95
+ begin
96
+ result = yield entry
97
+ ensure
98
+ entry.close
99
+ end
100
+ result
101
+ else
102
+ entry
103
+ end
104
+ end
105
+
106
+ def add_entry &block
107
+ raise ArgumentError, "No block given" unless block_given?
108
+
109
+ entry = Entry.new
110
+ data = yield entry
111
+ write_header entry
112
+ write_data data if data
113
+ ensure
114
+ entry.close
115
+ end
116
+
117
+ def write_data *args
118
+ if block_given?
119
+ raise ArgumentError, "wrong number of argument (#{args.size} for 0)" if args.size > 0
120
+
121
+ ar = archive
122
+ len = 0
123
+ while true do
124
+ str = yield
125
+ if ((n = C::archive_write_data(ar, str, str.size)) < 1)
126
+ return len
127
+ end
128
+ len += n
129
+ end
130
+ else
131
+ raise ArgumentError, "wrong number of argument (#{args.size}) for 1)" if args.size != 1
132
+ str = args[0]
133
+ C::archive_write_data(archive, str, str.size)
134
+ end
135
+ end
136
+
137
+ def write_header entry
138
+ raise Error, @archive if C::archive_write_header(archive, entry.entry) != C::OK
139
+ end
140
+
141
+ end
142
+
143
+ end
Binary file
@@ -0,0 +1,119 @@
1
+ require 'ffi-libarchive'
2
+ require 'tmpdir'
3
+ require 'test/unit'
4
+
5
+ class TS_ReadArchive < Test::Unit::TestCase
6
+
7
+ CONTENT_SPEC =
8
+ [
9
+ ['test/', 'directory', 0755, nil ],
10
+ ['test/b/', 'directory', 0755, nil ],
11
+ ['test/b/c/', 'directory', 0755, nil ],
12
+ ['test/b/c/c.dat', 'file', 0600, "\266\262\v_\266\243\305\3601\204\277\351\354\265\003\036\036\365f\377\210\205\032\222\346\370b\360u\032Y\301" ],
13
+ ['test/b/c/d/', 'directory', 0711, nil ],
14
+ ['test/b/c/d/d.dat', 'symbolic_link', 0777, "../c.dat" ],
15
+ ['test/b/b.dat', 'file', 0640, "s&\245\354(M\331=\270\000!s\355\240\252\355'N\304\343\bY\317\t\274\210\3128\321\347\234!" ],
16
+ ['test/a.dat', 'file', 0777, "\021\216\231Y\354\236\271\372\336\213\224R\211{D{\277\262\304\211xu\330\\\275@~\035\vSRM" ]
17
+ ]
18
+
19
+ def setup
20
+ File.open('data/test.tar.gz', 'rb') do |f|
21
+ @archive_content = f.read
22
+ end
23
+ end
24
+
25
+
26
+ def test_read_tar_gz_from_file
27
+ Archive.read_open_filename('data/test.tar.gz') do |ar|
28
+ verify_content(ar)
29
+ end
30
+ end
31
+
32
+ def test_read_tar_gz_from_file_with_external_gunzip
33
+ Archive.read_open_filename('data/test.tar.gz', 'gunzip') do |ar|
34
+ verify_content(ar)
35
+ end
36
+ end
37
+
38
+ def test_read_tar_gz_from_memory
39
+ Archive.read_open_memory(@archive_content) do |ar|
40
+ verify_content(ar)
41
+ end
42
+ end
43
+
44
+ def test_read_tar_gz_from_memory_with_external_gunzip
45
+ Archive.read_open_memory(@archive_content, 'gunzip') do |ar|
46
+ verify_content(ar)
47
+ end
48
+ end
49
+
50
+ def test_read_entry_bigger_than_internal_buffer
51
+ alphabet = "abcdefghijklmnopqrstuvwxyz"
52
+ entry_size = 1024 * 4 - 3
53
+
54
+ srand
55
+ content = ""
56
+ 1.upto(entry_size) do |i|
57
+ index = rand(alphabet.size)
58
+ content += alphabet[index,1]
59
+ end
60
+
61
+ Dir.mktmpdir do |dir|
62
+ Archive.write_open_filename(dir + '/test.tar.gz',
63
+ Archive::COMPRESSION_BZIP2, Archive::FORMAT_TAR) do |ar|
64
+ ar.new_entry do |entry|
65
+ entry.pathname = "chubby.dat"
66
+ entry.mode = 0666
67
+ entry.filetype = Archive::Entry::FILE
68
+ entry.atime = Time.now.to_i
69
+ entry.mtime = Time.now.to_i
70
+ entry.size = entry_size
71
+ ar.write_header(entry)
72
+ ar.write_data(content)
73
+ end
74
+ end
75
+
76
+ Archive.read_open_filename(dir + '/test.tar.gz') do |ar|
77
+ entry = ar.next_header
78
+ data = ar.read_data
79
+
80
+ assert_equal entry_size, data.size
81
+ assert_equal content.size, data.size
82
+ assert_equal content, data
83
+ end
84
+
85
+ Archive.read_open_filename(dir + '/test.tar.gz') do |ar|
86
+ entry = ar.next_header
87
+ data = ""
88
+ ar.read_data(128) { |chunk| data += chunk }
89
+
90
+ assert_equal content, data
91
+ end
92
+ end
93
+ end
94
+
95
+ private
96
+
97
+ def verify_content(ar)
98
+ content_spec_idx = 0
99
+
100
+ while entry = ar.next_header
101
+ expect_pathname, expect_type, expect_mode, expect_content =\
102
+ CONTENT_SPEC[content_spec_idx]
103
+
104
+ assert_equal expect_pathname, entry.pathname
105
+ assert_equal entry.send("#{expect_type}?"), true
106
+ assert_equal expect_mode, (entry.mode & 07777)
107
+
108
+ if entry.symbolic_link?
109
+ assert_equal expect_content, entry.symlink
110
+ elsif entry.file?
111
+ content = ar.read_data(1024)
112
+ assert_equal expect_content, content
113
+ end
114
+
115
+ content_spec_idx += 1
116
+ end
117
+ end
118
+
119
+ end
@@ -0,0 +1,133 @@
1
+ require 'ffi-libarchive'
2
+ require 'tmpdir'
3
+ require 'test/unit'
4
+
5
+ class TS_WriteArchive < Test::Unit::TestCase
6
+
7
+ CONTENT_SPEC =
8
+ [
9
+ ['test/', 'directory', 0755, nil ],
10
+ ['test/b/', 'directory', 0755, nil ],
11
+ ['test/b/c/', 'directory', 0755, nil ],
12
+ ['test/b/c/c.dat', 'file', 0600, "\266\262\v_\266\243\305\3601\204\277\351\354\265\003\036\036\365f\377\210\205\032\222\346\370b\360u\032Y\301" ],
13
+ ['test/b/c/d/', 'directory', 0711, nil ],
14
+ ['test/b/c/d/d.dat', 'symbolic_link', 0777, "../c.dat" ],
15
+ ['test/b/b.dat', 'file', 0640, "s&\245\354(M\331=\270\000!s\355\240\252\355'N\304\343\bY\317\t\274\210\3128\321\347\234!" ],
16
+ ['test/a.dat', 'file', 0777, "\021\216\231Y\354\236\271\372\336\213\224R\211{D{\277\262\304\211xu\330\\\275@~\035\vSRM" ]
17
+ ]
18
+
19
+ def test_end_to_end_write_read_tar_gz
20
+ Dir.mktmpdir do |dir|
21
+ Archive.write_open_filename(dir + '/test.tar.gz', :gzip, :tar) do |ar|
22
+ write_content(ar)
23
+ end
24
+
25
+ verify_content(dir + '/test.tar.gz')
26
+ end
27
+ end
28
+
29
+ def test_end_to_end_write_read_memory
30
+ memory = ""
31
+ Archive.write_open_memory(memory, Archive::COMPRESSION_GZIP, Archive::FORMAT_TAR) do |ar|
32
+ write_content ar
33
+ end
34
+ verify_content_memory(memory)
35
+ end
36
+
37
+ def test_end_to_end_write_read_tar_gz_with_external_gzip
38
+ Dir.mktmpdir do |dir|
39
+ Archive.write_open_filename(dir + '/test.tar.gz', 'gzip', :tar) do |ar|
40
+ write_content(ar)
41
+ end
42
+
43
+ verify_content(dir + '/test.tar.gz')
44
+ end
45
+ end
46
+
47
+ def test_end_to_end_write_read_tar_gz
48
+ Dir.mktmpdir do |dir|
49
+ Archive.write_open_filename(dir + '/test.tar.gz', :gzip, :tar) do |ar|
50
+ write_content(ar)
51
+ end
52
+
53
+ verify_content(dir + '/test.tar.gz')
54
+ end
55
+ end
56
+
57
+ private
58
+
59
+ def write_content(ar)
60
+ content_spec_idx = 0
61
+
62
+ while content_spec_idx < CONTENT_SPEC.size()
63
+ entry_path, entry_type, entry_mode, entry_content = \
64
+ CONTENT_SPEC[content_spec_idx]
65
+
66
+ ar.new_entry do |entry|
67
+ entry.pathname = entry_path
68
+ entry.mode = entry_mode
69
+ entry.filetype = eval "Archive::Entry::#{entry_type.upcase}"
70
+ entry.size = entry_content.size if entry_content
71
+ entry.symlink = entry_content if entry_type == 'symbolic_link'
72
+ entry.atime = Time.now.to_i
73
+ entry.mtime = Time.now.to_i
74
+ ar.write_header(entry)
75
+
76
+ if entry_type == 'file'
77
+ ar.write_data(entry_content)
78
+ end
79
+ end
80
+
81
+ content_spec_idx += 1
82
+ end
83
+ end
84
+
85
+ def verify_content_memory(memory)
86
+ Archive.read_open_memory(memory) do |ar|
87
+ content_spec_idx = 0
88
+
89
+ while entry = ar.next_header
90
+ expect_pathname, expect_type, expect_mode, expect_content =\
91
+ CONTENT_SPEC[content_spec_idx]
92
+
93
+ assert_equal expect_pathname, entry.pathname
94
+ assert_equal entry.send("#{expect_type}?"), true
95
+ assert_equal expect_mode, (entry.mode & 07777)
96
+
97
+ if entry.symbolic_link?
98
+ assert_equal expect_content, entry.symlink
99
+ elsif entry.file?
100
+ content = ar.read_data(1024)
101
+ assert_equal expect_content, content
102
+ end
103
+
104
+ content_spec_idx += 1
105
+ end
106
+ end
107
+ end
108
+
109
+ def verify_content(filename)
110
+ Archive.read_open_filename(filename) do |ar|
111
+ content_spec_idx = 0
112
+
113
+ while entry = ar.next_header
114
+ expect_pathname, expect_type, expect_mode, expect_content =\
115
+ CONTENT_SPEC[content_spec_idx]
116
+
117
+ assert_equal expect_pathname, entry.pathname
118
+ assert_equal entry.send("#{expect_type}?"), true
119
+ assert_equal expect_mode, (entry.mode & 07777)
120
+
121
+ if entry.symbolic_link?
122
+ assert_equal expect_content, entry.symlink
123
+ elsif entry.file?
124
+ content = ar.read_data(1024)
125
+ assert_equal expect_content, content
126
+ end
127
+
128
+ content_spec_idx += 1
129
+ end
130
+ end
131
+ end
132
+
133
+ end