ffi-libarchive 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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