higgs 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. data/ChangeLog +208 -0
  2. data/LICENSE +26 -0
  3. data/README +2 -0
  4. data/Rakefile +75 -0
  5. data/bin/higgs_backup +67 -0
  6. data/bin/higgs_dump_index +43 -0
  7. data/bin/higgs_dump_jlog +42 -0
  8. data/bin/higgs_verify +37 -0
  9. data/lib/cgi/session/higgs.rb +72 -0
  10. data/lib/higgs/block.rb +192 -0
  11. data/lib/higgs/cache.rb +117 -0
  12. data/lib/higgs/dbm.rb +55 -0
  13. data/lib/higgs/exceptions.rb +31 -0
  14. data/lib/higgs/flock.rb +77 -0
  15. data/lib/higgs/index.rb +164 -0
  16. data/lib/higgs/jlog.rb +159 -0
  17. data/lib/higgs/lock.rb +189 -0
  18. data/lib/higgs/storage.rb +1086 -0
  19. data/lib/higgs/store.rb +228 -0
  20. data/lib/higgs/tar.rb +390 -0
  21. data/lib/higgs/thread.rb +370 -0
  22. data/lib/higgs/tman.rb +513 -0
  23. data/lib/higgs/utils/bman.rb +285 -0
  24. data/lib/higgs/utils.rb +22 -0
  25. data/lib/higgs/version.rb +21 -0
  26. data/lib/higgs.rb +59 -0
  27. data/misc/cache_bench/cache_bench.rb +43 -0
  28. data/misc/dbm_bench/.strc +8 -0
  29. data/misc/dbm_bench/Rakefile +78 -0
  30. data/misc/dbm_bench/dbm_multi_thread.rb +199 -0
  31. data/misc/dbm_bench/dbm_rnd_delete.rb +43 -0
  32. data/misc/dbm_bench/dbm_rnd_read.rb +44 -0
  33. data/misc/dbm_bench/dbm_rnd_update.rb +44 -0
  34. data/misc/dbm_bench/dbm_seq_read.rb +45 -0
  35. data/misc/dbm_bench/dbm_seq_write.rb +44 -0
  36. data/misc/dbm_bench/st_verify.rb +28 -0
  37. data/misc/io_bench/cksum_bench.rb +48 -0
  38. data/misc/io_bench/jlog_bench.rb +71 -0
  39. data/misc/io_bench/write_bench.rb +128 -0
  40. data/misc/thread_bench/lock_bench.rb +132 -0
  41. data/mkrdoc.rb +8 -0
  42. data/rdoc.yml +13 -0
  43. data/sample/count.rb +60 -0
  44. data/sample/dbmtest.rb +38 -0
  45. data/test/Rakefile +45 -0
  46. data/test/run.rb +32 -0
  47. data/test/test_block.rb +163 -0
  48. data/test/test_cache.rb +214 -0
  49. data/test/test_cgi_session.rb +142 -0
  50. data/test/test_flock.rb +162 -0
  51. data/test/test_index.rb +258 -0
  52. data/test/test_jlog.rb +180 -0
  53. data/test/test_lock.rb +320 -0
  54. data/test/test_online_backup.rb +169 -0
  55. data/test/test_storage.rb +439 -0
  56. data/test/test_storage_conf.rb +202 -0
  57. data/test/test_storage_init_opts.rb +89 -0
  58. data/test/test_store.rb +211 -0
  59. data/test/test_tar.rb +432 -0
  60. data/test/test_thread.rb +541 -0
  61. data/test/test_tman.rb +875 -0
  62. data/test/test_tman_init_opts.rb +56 -0
  63. data/test/test_utils_bman.rb +234 -0
  64. metadata +115 -0
@@ -0,0 +1,228 @@
1
+ # = storage like pstore
2
+ #
3
+ # Author:: $Author: toki $
4
+ # Date:: $Date: 2007-09-26 00:20:20 +0900 (Wed, 26 Sep 2007) $
5
+ # Revision:: $Revision: 559 $
6
+ #
7
+ # == license
8
+ # :include:LICENSE
9
+ #
10
+
11
+ require 'higgs/storage'
12
+ require 'higgs/tman'
13
+
14
+ module Higgs
15
+ # = storage like pstore
16
+ # == tutorial
17
+ #
18
+ # === 1. open a new store
19
+ #
20
+ # % irb
21
+ # irb(main):001:0> require 'higgs'
22
+ # => true
23
+ # irb(main):002:0> st = Higgs::Store.new('foo')
24
+ # => #<Higgs::Store:0xb7cac7d8
25
+ #
26
+ # === 2. write an object
27
+ #
28
+ # irb(main):003:0> st.transaction{|tx|
29
+ # irb(main):004:1* tx[:foo] = Object.new
30
+ # irb(main):005:1> }
31
+ # => #<Object:0xb7d18adc>
32
+ #
33
+ # === 3. read an object
34
+ #
35
+ # irb(main):006:0> st.transaction{|tx|
36
+ # irb(main):007:1* tx[:foo]
37
+ # irb(main):008:1> }
38
+ # => #<Object:0xb7a3b1e8>
39
+ #
40
+ # === 4. custom property
41
+ #
42
+ # custom property name shuold be a string.
43
+ #
44
+ # irb(main):009:0> st.transaction{|tx|
45
+ # irb(main):010:1* tx.set_property(:foo, 'list', %w[ apple banana orange ])
46
+ # irb(main):011:1> }
47
+ # => nil
48
+ # irb(main):012:0> st.transaction{|tx|
49
+ # irb(main):013:1* tx.property(:foo, 'list')
50
+ # irb(main):014:1> }
51
+ # => ["apple", "banana", "orange"]
52
+ #
53
+ # === 4. write a string with <tt>:string_only</tt> property
54
+ #
55
+ # <tt>:string_only</tt> is system property.
56
+ # system property name should be a symbol.
57
+ #
58
+ # irb(main):015:0> st.transaction{|tx|
59
+ # irb(main):016:1* tx['bar'] = "Hello world.\n"
60
+ # irb(main):017:1> tx.set_property('bar', :string_only, true)
61
+ # irb(main):018:1> }
62
+ # => nil
63
+ #
64
+ # === 5. read a string
65
+ #
66
+ # irb(main):019:0> st.transaction{|tx|
67
+ # irb(main):020:1* tx['bar']
68
+ # irb(main):021:1> }
69
+ # => "Hello world.\n"
70
+ #
71
+ # === 6. <tt>:string_only</tt> property rejects an object
72
+ #
73
+ # irb(main):022:0> st.transaction{|tx|
74
+ # irb(main):023:1* tx['bar'] = Object.new
75
+ # irb(main):024:1> }
76
+ # TypeError: can't convert Object (value) to String
77
+ #
78
+ # === 7. read-only transaction
79
+ #
80
+ # irb(main):025:0> st.transaction{|tx|
81
+ # irb(main):026:1* p tx[:foo]
82
+ # irb(main):027:1> p tx.property(:foo, 'list')
83
+ # irb(main):028:1> p tx['bar']
84
+ # irb(main):029:1> p tx.property('bar', :string_only)
85
+ # irb(main):030:1> }
86
+ # #<Object:0xb79dd764>
87
+ # ["apple", "banana", "orange"]
88
+ # "Hello world.\n"
89
+ # true
90
+ # => nil
91
+ #
92
+ # === 8. shutdown
93
+ #
94
+ # irb(main):031:0> st.shutdown
95
+ # => nil
96
+ # irb(main):032:0> exit
97
+ #
98
+ # === 9. unix TAR storage and some files
99
+ #
100
+ # % ls -ln
101
+ # total 20
102
+ # -rw-r----- 1 1000 1000 1024 Sep 24 20:59 foo.idx
103
+ # -rw-r----- 1 1000 1000 4096 Sep 24 20:59 foo.jlog
104
+ # -rw-r----- 1 1000 1000 0 Sep 24 20:57 foo.lock
105
+ # -rw-r--r-- 1 1000 1000 57 Sep 24 20:57 foo.log
106
+ # -rw-r----- 1 1000 1000 5120 Sep 24 20:58 foo.tar
107
+ #
108
+ # === 10. unix TAR contents
109
+ #
110
+ # % tar tvf foo.tar
111
+ # -rw-r--r-- 1000/1000 12 2007-09-24 20:58 foo
112
+ # -rw-r--r-- 1000/1000 316 2007-09-24 20:58 foo.p
113
+ # -rw-r--r-- 1000/1000 13 2007-09-24 20:58 bar
114
+ # -rw-r--r-- 1000/1000 250 2007-09-24 20:58 bar.p
115
+ # % tar xvf foo.tar
116
+ # foo
117
+ # foo.p
118
+ # bar
119
+ # bar.p
120
+ #
121
+ # === 11. marshaled object
122
+ #
123
+ # `foo' is marshaled object
124
+ #
125
+ # % ls -ln foo
126
+ # -rw-r--r-- 1 1000 1000 12 Sep 24 20:58 foo
127
+ # % od -c foo
128
+ # 0000000 004 \b o : \v O b j e c t \0
129
+ # 0000014
130
+ #
131
+ # `foo.p' is properties.
132
+ # <tt>"list"</tt> of custom property is defined.
133
+ #
134
+ # % ls -ln foo.p
135
+ # -rw-r--r-- 1 1000 1000 316 Sep 24 20:58 foo.p
136
+ # % cat foo.p
137
+ # # SUM16 21993
138
+ # ---
139
+ # custom_properties:
140
+ # list:
141
+ # - apple
142
+ # - banana
143
+ # - orange
144
+ # system_properties:
145
+ # hash_type: MD5
146
+ # modified_time: &id001 2007-09-24 20:58:03.568701 +09:00
147
+ # hash_value: 5490d172635c560daaff1de92779d605
148
+ # created_time: *id001
149
+ # changed_time: 2007-09-24 20:58:24.502465 +09:00
150
+ # string_only: false
151
+ #
152
+ # === 12. string with <tt>:string_only</tt> property
153
+ #
154
+ # `bar' is a string
155
+ #
156
+ # % ls -ln bar
157
+ # -rw-r--r-- 1 1000 1000 13 Sep 24 20:58 bar
158
+ # % od -c bar
159
+ # 0000000 H e l l o w o r l d . \n
160
+ # 0000015
161
+ # % cat bar
162
+ # Hello world.
163
+ #
164
+ # `bar.p' is properties.
165
+ # <tt>:string_only</tt> of system property is <tt>true</tt>.
166
+ #
167
+ # % ls -ln bar.p
168
+ # -rw-r--r-- 1 1000 1000 250 Sep 24 20:58 bar.p
169
+ # % cat bar.p
170
+ # # SUM16 18034
171
+ # ---
172
+ # custom_properties: {}
173
+ #
174
+ # system_properties:
175
+ # hash_type: MD5
176
+ # modified_time: &id001 2007-09-24 20:58:45.420319 +09:00
177
+ # created_time: *id001
178
+ # hash_value: fa093de5fc603823f08524f9801f0546
179
+ # changed_time: *id001
180
+ # string_only: true
181
+ #
182
+ # === 13. other operations
183
+ #
184
+ # <tt>tx</tt> of block argument is an instance of
185
+ # Higgs::TransactionContext. see Higgs::TransactionContext for all
186
+ # operations of transaction.
187
+ #
188
+ # == sample script
189
+ #
190
+ # sample/count.rb
191
+ # :include: sample/count.rb
192
+ #
193
+ class Store
194
+ # for ident(1)
195
+ CVS_ID = '$Id: store.rb 559 2007-09-25 15:20:20Z toki $'
196
+
197
+ include Storage::Export
198
+ include TransactionManager::Export
199
+
200
+ DECODE = proc{|r| Marshal.load(r) }
201
+ ENCODE = proc{|w| Marshal.dump(w) }
202
+
203
+ # <tt>name</tt> is a storage name and see Higgs::Storage.new for
204
+ # detail. see Higgs::Storage::InitOptions and
205
+ # Higgs::TransactionManager::InitOptions for <tt>options</tt>.
206
+ def initialize(name, options={})
207
+ options[:decode] = DECODE
208
+ options[:encode] = ENCODE
209
+ @storage = Storage.new(name, options)
210
+ @tman = TransactionManager.new(@storage, options)
211
+ end
212
+
213
+ def self.open(*args)
214
+ store = new(*args)
215
+ begin
216
+ r = yield(store)
217
+ ensure
218
+ store.shutdown
219
+ end
220
+ r
221
+ end
222
+ end
223
+ end
224
+
225
+ # Local Variables:
226
+ # mode: Ruby
227
+ # indent-tabs-mode: nil
228
+ # End:
data/lib/higgs/tar.rb ADDED
@@ -0,0 +1,390 @@
1
+ # = unix TAR utilities
2
+ #
3
+ # Author:: $Author: toki $
4
+ # Date:: $Date: 2007-09-26 00:20:20 +0900 (Wed, 26 Sep 2007) $
5
+ # Revision:: $Revision: 559 $
6
+ #
7
+ # == license
8
+ # :include:LICENSE
9
+ #
10
+
11
+ require 'forwardable'
12
+ require 'higgs/exceptions'
13
+
14
+ module Higgs
15
+ module Tar
16
+ # for ident(1)
17
+ CVS_ID = '$Id: tar.rb 559 2007-09-25 15:20:20Z toki $'
18
+
19
+ include Exceptions
20
+
21
+ class Error < HiggsError
22
+ end
23
+
24
+ class FormatError < Error
25
+ end
26
+
27
+ class MagicError < FormatError
28
+ end
29
+
30
+ class VersionError < FormatError
31
+ end
32
+
33
+ class CheckSumError < FormatError
34
+ end
35
+
36
+ class TooLongPathError < FormatError
37
+ end
38
+
39
+ class IOError < Error
40
+ end
41
+
42
+ # tar header format
43
+ #
44
+ # name : Z100 : null terminated string, primary hard link name
45
+ # mode : A8 : octet number format ascii string
46
+ # uid : A8 : octet number format ascii string
47
+ # gid : A8 : octet number format ascii string
48
+ # size : A12 : octet number format ascii string
49
+ # mtime : A12 : octet number format ascii string, seconds since epoch date-time (UTC 1970-01-01 00:00:00)
50
+ # cksum : A8 : octet number format ascii string
51
+ # typeflag : a1 : ascii number character (null character is old tar format)
52
+ # linkname : Z100 : null terminated string, secondly hard link name
53
+ # magic : A6 : white space terminated string
54
+ # version : a2 : 2 ascii characters
55
+ # uname : Z32 : null terminated string
56
+ # gname : Z32 : null terminated string
57
+ # devmajor : Z8 : octet number format ascii string
58
+ # devminor : Z8 : octet number format ascii string
59
+ # prefix : Z155 : null terminated string
60
+ #
61
+ module Block
62
+ # block size
63
+ BLKSIZ = 512
64
+
65
+ # limit name length
66
+ MAX_LEN = 100
67
+
68
+ # unix tar format parameters
69
+ MAGIC = 'ustar'
70
+ VERSION = '00'
71
+ REGTYPE = '0'
72
+ AREGTYPE = "\0"
73
+ LNKTYPE = '1'
74
+ SYMTYPE = '2'
75
+ CHRTYPE = '3'
76
+ BLKTYPE = '4'
77
+ DIRTYPE = '5'
78
+ FIFOTYPE = '6'
79
+ CONTTYPE = '7' # reserved
80
+
81
+ # for pack/unpack
82
+ HEAD_FMT = 'Z100 A8 A8 A8 A12 A12 A8 a1 Z100 A6 a2 Z32 Z32 Z8 Z8 Z155'
83
+
84
+ # end of archive
85
+ EOA = "\0" * BLKSIZ
86
+
87
+ def padding_size(bytes)
88
+ r = bytes % BLKSIZ
89
+ (r > 0) ? BLKSIZ - r : 0
90
+ end
91
+ module_function :padding_size
92
+
93
+ def blocked_size(bytes)
94
+ bytes + padding_size(bytes)
95
+ end
96
+ module_function :blocked_size
97
+
98
+ def tar?(path)
99
+ if (File.file? path) then
100
+ head = File.open(path, 'rb') {|r_io|
101
+ r_io.binmode
102
+ r_io.read(BLKSIZ)
103
+ }
104
+ if (head && head.length == BLKSIZ) then
105
+ if (head.unpack(HEAD_FMT)[9] == MAGIC) then
106
+ return true
107
+ end
108
+ end
109
+ end
110
+ false
111
+ end
112
+ module_function :tar?
113
+ end
114
+
115
+ class RawIO
116
+ extend Forwardable
117
+
118
+ def initialize(io)
119
+ @io = io
120
+ end
121
+
122
+ def read(*args)
123
+ begin
124
+ @io.sysread(*args)
125
+ rescue EOFError
126
+ nil
127
+ end
128
+ end
129
+
130
+ def write(*args)
131
+ @io.syswrite(*args)
132
+ end
133
+
134
+ def seek(*args)
135
+ @io.sysseek(*args)
136
+ end
137
+
138
+ def tell
139
+ @io.sysseek(0, IO::SEEK_CUR)
140
+ end
141
+
142
+ alias pos tell
143
+
144
+ def pos=(pos)
145
+ @io.sysseek(pos, IO::SEEK_SET)
146
+ end
147
+
148
+ def_delegator :@io, :flush
149
+ def_delegator :@io, :fsync
150
+ def_delegator :@io, :truncate
151
+ def_delegator :@io, :close
152
+ def_delegator :@io, :closed?
153
+ end
154
+
155
+ class IOHandler
156
+ extend Forwardable
157
+ include Block
158
+
159
+ def initialize(io)
160
+ @io = io
161
+ end
162
+
163
+ def to_io
164
+ @io
165
+ end
166
+
167
+ def_delegator :@io, :seek
168
+ def_delegator :@io, :tell
169
+ def_delegator :@io, :pos
170
+ def_delegator :@io, :pos=
171
+ def_delegator :@io, :flush
172
+ def_delegator :@io, :fsync
173
+ def_delegator :@io, :truncate
174
+ def_delegator :@io, :close
175
+ def_delegator :@io, :closed?
176
+ end
177
+
178
+ class ArchiveReader < IOHandler
179
+ include Enumerable
180
+
181
+ def read_header(skip_body=false)
182
+ head_data = @io.read(BLKSIZ)
183
+ unless (head_data) then
184
+ raise FormatError, 'unexpected EOF'
185
+ end
186
+ if (head_data.length != BLKSIZ) then
187
+ raise FormatError, 'too short header'
188
+ end
189
+ if (head_data == EOA) then
190
+ next_head_data = @io.read(BLKSIZ)
191
+ if (next_head_data && next_head_data == EOA) then
192
+ return nil
193
+ else
194
+ raise FormatError, "not of EOA: #{head_data.inspect}, #{next_head_data.inspect}"
195
+ end
196
+ end
197
+ chksum = (head_data[0...148] + ' ' * 8 + head_data[156...BLKSIZ]).sum(20)
198
+ head_list = head_data.unpack(HEAD_FMT)
199
+ head = {}
200
+ [ :name, :mode, :uid, :gid, :size, :mtime,
201
+ :chksum, :typeflag, :linkname, :magic, :version,
202
+ :uname, :gname, :devmajor, :devminor, :prefix
203
+ ].each_with_index do |k, i|
204
+ head[k] = head_list[i]
205
+ end
206
+ if (head[:typeflag] == AREGTYPE) then
207
+ head[:typeflag] = REGTYPE
208
+ end
209
+ if (head[:magic] != MAGIC) then
210
+ raise MagicError, "unknown format: #{head[:magic].inspect}"
211
+ end
212
+ # why?
213
+ #if (head[:version] != VERSION) then
214
+ # raise VersionError, "unknown version: #{head[:version].inspect}"
215
+ #end
216
+ [ :mode,
217
+ :uid,
218
+ :gid,
219
+ :size,
220
+ :mtime,
221
+ :chksum
222
+ ].each do |sym|
223
+ head[sym] = head[sym].oct
224
+ end
225
+ if (head[:chksum] != chksum) then
226
+ raise CheckSumError, 'broken tar'
227
+ end
228
+ head[:mtime] = Time.at(head[:mtime])
229
+ if (skip_body) then
230
+ skip_size = head[:size] + padding_size(head[:size])
231
+ @io.read(skip_size)
232
+ end
233
+ head
234
+ end
235
+
236
+ def fetch
237
+ head_and_body = read_header or return
238
+ if (head_and_body[:size] > 0) then
239
+ blocked_size = blocked_size(head_and_body[:size])
240
+ head_and_body[:body] = @io.read(blocked_size)
241
+ unless (head_and_body) then
242
+ raise FormatError, 'unexpected EOF'
243
+ end
244
+ if (head_and_body[:body].size != blocked_size) then
245
+ raise FormatError, 'mismatch body size'
246
+ end
247
+ head_and_body[:body][head_and_body[:size]...blocked_size] = ''
248
+ else
249
+ case (head_and_body[:typeflag])
250
+ when REGTYPE, CONTTYPE
251
+ head_and_body[:body] = ''
252
+ else
253
+ head_and_body[:body] = nil
254
+ end
255
+ end
256
+ head_and_body
257
+ end
258
+
259
+ def each(skip_body=false)
260
+ if (skip_body) then
261
+ while (head = read_header(true))
262
+ yield(head)
263
+ end
264
+ else
265
+ while (head_and_body = fetch)
266
+ yield(head_and_body)
267
+ end
268
+ end
269
+ self
270
+ end
271
+ end
272
+
273
+ class ArchiveWriter < IOHandler
274
+ include Block
275
+
276
+ def write_header(head)
277
+ name = head[:name] or raise FormatError, "required name: #{head.inspect}"
278
+ if (name.length > MAX_LEN) then
279
+ raise TooLongPathError, "too long path: #{name}"
280
+ end
281
+ mode = format('%-8o', head[:mode] || 0100644) # 0100644 => -rw-r--r--
282
+ uid = format('%-8o', head[:uid] || Process.euid)
283
+ gid = format('%-8o', head[:gid] || Process.egid)
284
+ size = format('%-12o', head[:size])
285
+ mtime = format('%-12o', (head[:mtime] || Time.now).to_i)
286
+ dummy_chksum = ' ' * 8
287
+ typeflag = head[:typeflag] || REGTYPE
288
+ linkname = head[:linkname] || ''
289
+ magic = head[:magic] || MAGIC
290
+ version = head[:version] || VERSION
291
+ uname = head[:uname] || ''
292
+ gname = head[:gname] || ''
293
+ devmajor = head[:devmajor] || ''
294
+ devminor = head[:devminor] || ''
295
+ prefix = ''
296
+ header = [
297
+ name, mode, uid, gid, size, mtime,
298
+ dummy_chksum, typeflag, linkname, magic, version,
299
+ uname, gname, devmajor, devminor, prefix
300
+ ].pack(HEAD_FMT)
301
+ header += "\0" * 12
302
+ if (head.key? :chksum) then
303
+ chksum = head[:chksum]
304
+ else
305
+ chksum = header.sum(20)
306
+ end
307
+ header[148, 8] = format('%-8o', chksum)
308
+ @io.write(header)
309
+ nil
310
+ end
311
+
312
+ def write_EOA
313
+ @io.write(EOA * 2)
314
+ nil
315
+ end
316
+
317
+ FTYPE_TO_TAR = {
318
+ 'file' => REGTYPE,
319
+ 'directory' => DIRTYPE,
320
+ 'characterSpecial' => CHRTYPE,
321
+ 'blockSpecial' => BLKTYPE,
322
+ 'fifo' => FIFOTYPE,
323
+ 'link' => SYMTYPE,
324
+ 'socket' => FIFOTYPE
325
+ }
326
+
327
+ def add(name, body, options=nil)
328
+ head = {
329
+ :name => name,
330
+ :size => body.length
331
+ }
332
+ if (options) then
333
+ head.update(options)
334
+ end
335
+ if (block_given?) then
336
+ yield(head)
337
+ end
338
+ write_header(head)
339
+ body += "\0" * padding_size(body.length)
340
+ @io.write(body)
341
+ nil
342
+ end
343
+
344
+ def add_file(path)
345
+ stat = File.stat(path)
346
+ unless (FTYPE_TO_TAR.key? stat.ftype) then
347
+ raise FormatError, "unknown file type: #{stat.ftype}"
348
+ end
349
+ head = {
350
+ :name => path,
351
+ :mode => stat.mode,
352
+ :uid => stat.uid,
353
+ :gid => stat.gid,
354
+ :size => (stat.file?) ? stat.size : 0,
355
+ :mtime => stat.mtime,
356
+ :typeflag => FTYPE_TO_TAR[stat.ftype]
357
+ }
358
+ if (block_given?) then
359
+ yield(head)
360
+ end
361
+ write_header(head)
362
+ if (stat.ftype == 'file') then
363
+ File.open(path, 'rb') {|r_io|
364
+ chunk_size = BLKSIZ * 128
365
+ remaining_size = stat.size
366
+ while (remaining_size > chunk_size)
367
+ s = r_io.read(chunk_size) or raise IOError, 'unexpected EOF'
368
+ @io.write(s)
369
+ remaining_size -= chunk_size
370
+ end
371
+ s = r_io.read(chunk_size) or raise IOError, 'unexpected EOF'
372
+ s += "\0" * padding_size(stat.size)
373
+ @io.write(s)
374
+ }
375
+ end
376
+ nil
377
+ end
378
+
379
+ def close(eoa=true)
380
+ write_EOA if eoa
381
+ super()
382
+ end
383
+ end
384
+ end
385
+ end
386
+
387
+ # Local Variables:
388
+ # mode: Ruby
389
+ # indent-tabs-mode: nil
390
+ # End: