higgs 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.
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: