ioposrw 0.4

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,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 4a73f9689f954e9a73d234ab71568e67f1ec97b8
4
+ data.tar.gz: 85a997f0de8af73b8ed2d7d686da6e547987e240
5
+ SHA512:
6
+ metadata.gz: 59e351ee0a4b470aabec3e2c3bc426e6db067839ed2ddb7e1a06df8ffff3b03a4e94965754b744dcafab0d7ef38541e8e9a52244808cfeb23547d3bb505abf65
7
+ data.tar.gz: 13a4080d5465fd244647949bd7618f5238ee6bc80ead9e0d1ba804dd3d2a36139cea707d3a84868a7919363d31eef4cf81fbb68d21dc14f49e1ddee46586f3d4
@@ -0,0 +1,21 @@
1
+ # ioposrw 変更履歴
2
+
3
+ ## 0.4 (平成29年12月26日 火曜日)
4
+
5
+ * ``IO#pread``/``IO#pwrite`` を ``IO#readat``/``IO#writeat`` に変更
6
+
7
+ Ruby-2.5 で ``IO#pread``/``IO#pwrite`` が組み込まれましたが引数のとり方が異なるため、 ``IO#readat``/``IO#writeat`` として利用できるように変更しました。
8
+
9
+ * ファイル終端位置の指定を行えるようになった
10
+
11
+ ``readat``/``writeat`` の ``offset`` 引数に nil を与えることで、ファイルの終端位置を示すことが出来るように機能を変更しました。
12
+
13
+ * ``IOPositioningReadWrite`` モジュールを追加し、``IO`` クラスへ include するように変更
14
+
15
+ 実装の実体を ``IOPositioningReadWrite`` モジュールに行い、IO クラスへ include するようにしました。
16
+
17
+ * "ioposrw/stringio" ライブラリの追加
18
+
19
+ ``IO.ioposrw_enable_stringio_extend`` メソッドは廃止する予定です。
20
+
21
+ 代わりに ``require "ioposrw/stringio"`` を使って下さい。
@@ -0,0 +1,13 @@
1
+ # QUICK REFERENCE FOR IOPOSRW
2
+
3
+ ## LIBRARIES
4
+
5
+ * ``require "ioposrw"`` - main library
6
+ * ``require "ioposrw/stringio"`` - need when use ``StringIO#pread/pwrite``
7
+
8
+ ## METHODS
9
+
10
+ * ``IO#pread`` - Similar functionality to the pread(2) system call. Implemented as ``IOPositioningReadWrite::IO#pread``.
11
+ * ``IO#pwrite`` - Similar functionality to the pwrite(2) system call. Implemented as ``IOPositioningReadWrite::IO#pwrite``.
12
+ * ``StringIO#pread`` - StringIO version pread(2). Implemented as ``IOPositioningReadWrite::StringIO#pread``.
13
+ * ``StringIO#pwrite`` - StringIO version pwrite(2). Implemented as ``IOPositioningReadWrite::StringIO#pwrite``.
@@ -0,0 +1,68 @@
1
+ # ioposrw
2
+
3
+ 位置指定IO読み書きメソッド追加拡張ライブラリ
4
+
5
+ * Product Name: ioposrw
6
+ * Version: 0.4
7
+ * Project Page: https://github.com/dearblue/ruby-ioposrw
8
+ * License: 2-clause BSD License (二条項 BSD ライセンス)
9
+ * Author: dearblue <dearblue@users.noreply.github.com>
10
+
11
+
12
+ ## これは何?
13
+
14
+ ioposrw は IO インスタンスに位置指定読み書き機能を追加する拡張ライブラリです。
15
+
16
+ この機能は ``IO#readat`` / ``IO#writeat`` によって提供されます。
17
+
18
+ これらのメソッドは、元々ある ``IO#read`` / ``IO#write`` によって更新されるファイル位置ポインタに影響されません。
19
+
20
+ マルチスレッド動作中に同じファイルインスタンスの別の領域を読み書きしても、想定通りの動作が期待できます。
21
+
22
+ 追加の機能として、``StringIO#readat`` / ``StringIO#writeat`` も実装してあります (実際に利用する場合は、``require "ioposrw/stringio"`` を行って下さい)。
23
+
24
+
25
+ ## どう使うのか?
26
+
27
+ ライブラリの読み込み:
28
+
29
+ ```ruby:ruby
30
+ require "ioposrw"
31
+ ```
32
+
33
+ ファイルの読み込み:
34
+
35
+ ```ruby:ruby
36
+ File.open("sample.txt") do |file|
37
+ buf = ""
38
+ file.readat(200, 100, buf) # sample.txt の先頭 200 バイト位置から
39
+ # 100 バイトを buf に読み込む
40
+ end
41
+ ```
42
+
43
+ ファイルの書き込み:
44
+
45
+ ```ruby:ruby
46
+ File.open("sample.txt", "w") do |file|
47
+ buf = "ごにょごにょ"
48
+ file.writeat(200, buf) # sample.txt の先頭 200 バイト位置に buf を書き込む
49
+ end
50
+ ```
51
+
52
+ ## 注意事項について
53
+
54
+ - Windows 上では、``IO#readat / IO#writeat`` を呼び出すとファイルポインタ (IO#pos) が指定位置の次に変更されます (このことは丁度 ``IO#pos=`` と ``IO#read`` を呼び出した後の状態と考えてください)。
55
+
56
+ これは Windows 自身に伴う仕様となります。
57
+
58
+
59
+ ## 内部の実装方法
60
+
61
+ POSIX システムコールの ``pread(2)`` / ``pwrite(2)`` を中心に実装しました。
62
+
63
+ Windows 環境では ``OVERRAPPED`` 構造体を与えた ``ReadFile`` / ``WriteFile`` をそれぞれ用いて ``pread`` / ``pwrite`` 関数を構築し、あとは同じです。
64
+
65
+
66
+ ## ライセンスについて
67
+
68
+ 二条項BSDライセンスの下で取り扱うことができます。
@@ -0,0 +1,212 @@
1
+
2
+ require "pathname"
3
+ require "rake/clean"
4
+
5
+ docnames = "{README,LICENSE,CHANGELOG,Changelog,HISTORY}"
6
+ doctypes = "{,.txt,.rd,.rdoc,.md,.markdown}"
7
+ cexttypes = "{c,C,cc,cxx,cpp,h,H,hh}"
8
+
9
+ DOC = FileList["#{docnames}{,.ja}#{doctypes}"] +
10
+ FileList["{contrib,ext}/**/#{docnames}{,.ja}#{doctypes}"] +
11
+ FileList["ext/**/*.#{cexttypes}"]
12
+ EXT = FileList["ext/**/*"]
13
+ BIN = FileList["bin/*"]
14
+ LIB = FileList["lib/**/*.rb"]
15
+ SPEC = FileList["spec/**/*"]
16
+ TEST = FileList["test/**/*"]
17
+ EXAMPLE = FileList["examples/**/*"]
18
+ GEMSTUB_SRC = "gemstub.rb"
19
+ RAKEFILE = [File.basename(__FILE__), GEMSTUB_SRC]
20
+ EXTRA = []
21
+ EXTCONF = FileList["ext/**/extconf.rb"]
22
+ EXTCONF.reject! { |n| !File.file?(n) }
23
+ EXTMAP = {}
24
+
25
+ load GEMSTUB_SRC
26
+
27
+ EXTMAP.dup.each_pair do |dir, name|
28
+ EXTMAP[Pathname.new(dir).cleanpath.to_s] = Pathname.new(name).cleanpath.to_s
29
+ end
30
+
31
+ GEMSTUB.extensions += EXTCONF
32
+ GEMSTUB.executables += FileList["bin/*"].map { |n| File.basename n }
33
+ GEMSTUB.executables.sort!
34
+
35
+ PACKAGENAME = "#{GEMSTUB.name}-#{GEMSTUB.version}"
36
+ GEMFILE = "#{PACKAGENAME}.gem"
37
+ GEMSPEC = "#{PACKAGENAME}.gemspec"
38
+
39
+ GEMSTUB.files += DOC + EXT + EXTCONF + BIN + LIB + SPEC + TEST + EXAMPLE + RAKEFILE + EXTRA
40
+ GEMSTUB.files.sort!
41
+ if GEMSTUB.rdoc_options.nil? || GEMSTUB.rdoc_options.empty?
42
+ readme = %W(.md .markdown .rd .rdoc .txt #{""}).map { |ext| "README#{ext}" }.find { |m| DOC.find { |n| n == m } }
43
+ GEMSTUB.rdoc_options = %w(--charset UTF-8) + (readme ? %W(-m #{readme}) : [])
44
+ end
45
+ GEMSTUB.extra_rdoc_files += DOC + LIB + EXT.reject { |n| n.include?("/externals/") || !%w(.h .hh .c .cc .cpp .cxx).include?(File.extname(n)) }
46
+ GEMSTUB.extra_rdoc_files.sort!
47
+
48
+ GEMSTUB_TRYOUT = GEMSTUB.dup
49
+ GEMSTUB_TRYOUT.version = "#{GEMSTUB.version}#{Time.now.strftime(".TRYOUT.%Y%m%d.%H%M%S")}"
50
+ PACKAGENAME_TRYOUT = "#{GEMSTUB.name}-#{GEMSTUB_TRYOUT.version}"
51
+ GEMFILE_TRYOUT = "#{PACKAGENAME_TRYOUT}.gem"
52
+ GEMSPEC_TRYOUT = "#{PACKAGENAME_TRYOUT}.gemspec"
53
+
54
+ CLEAN << GEMSPEC << GEMSPEC_TRYOUT
55
+ CLOBBER << GEMFILE
56
+
57
+ task :default => :tryout do
58
+ $stderr.puts <<-EOS
59
+ #{__FILE__}:#{__LINE__}:
60
+ \ttype ``rake release'' to build release package.
61
+ EOS
62
+ end
63
+
64
+ desc "build tryout package"
65
+ task :tryout
66
+
67
+ desc "build release package"
68
+ task :release => :all
69
+
70
+ unless EXTCONF.empty?
71
+ RUBYSET ||= (ENV["RUBYSET"] || "").split(",")
72
+
73
+ if RUBYSET.nil? || RUBYSET.empty?
74
+ $stderr.puts <<-EOS
75
+ #{__FILE__}:
76
+ |
77
+ | If you want binary gem package, launch rake with ``RUBYSET`` enviroment
78
+ | variable for set ruby interpreters by comma separated.
79
+ |
80
+ | e.g.) $ rake RUBYSET=ruby
81
+ | or) $ rake RUBYSET=ruby21,ruby22,ruby23
82
+ |
83
+ EOS
84
+ else
85
+ platforms = RUBYSET.map { |ruby| `#{ruby} --disable-gems -e "puts RUBY_PLATFORM"`.chomp }
86
+ platforms1 = platforms.uniq
87
+ unless platforms1.size == 1 && !platforms1[0].empty?
88
+ abort <<-EOS
89
+ #{__FILE__}:#{__LINE__}: different platforms:
90
+ #{RUBYSET.zip(platforms).map { |ruby, platform| "%24s => %s" % [ruby, platform] }.join("\n")}
91
+ ABORTED.
92
+ EOS
93
+ end
94
+ PLATFORM = platforms1[0]
95
+
96
+ RUBY_VERSIONS = RUBYSET.map do |ruby|
97
+ ver = `#{ruby} --disable-gems -e "puts RUBY_VERSION"`.slice(/\d+\.\d+/)
98
+ raise "failed ruby checking - ``#{ruby}''" unless $?.success?
99
+ [ver, ruby]
100
+ end
101
+
102
+ SOFILES_SET = RUBY_VERSIONS.map { |(ver, ruby)|
103
+ EXTCONF.map { |extconf|
104
+ extdir = Pathname.new(extconf).cleanpath.dirname.to_s
105
+ case
106
+ when soname = EXTMAP[extdir.sub(/^ext\//i, "")]
107
+ soname = soname.sub(/\.so$/i, "")
108
+ when extdir == "ext" || extdir == "."
109
+ soname = GEMSTUB.name
110
+ else
111
+ soname = File.basename(extdir)
112
+ end
113
+
114
+ [ruby, File.join("lib", "#{soname.sub(/(?<=\/)|^(?!.*\/)/, "#{ver}/")}.so"), extconf]
115
+ }
116
+ }.flatten(1)
117
+ SOFILES = SOFILES_SET.map { |(ruby, sopath, extconf)| sopath }
118
+
119
+ GEMSTUB_NATIVE = GEMSTUB.dup
120
+ GEMSTUB_NATIVE.files += SOFILES
121
+ GEMSTUB_NATIVE.platform = Gem::Platform.new(PLATFORM).to_s
122
+ GEMSTUB_NATIVE.extensions.clear
123
+ GEMFILE_NATIVE = "#{GEMSTUB_NATIVE.name}-#{GEMSTUB_NATIVE.version}-#{GEMSTUB_NATIVE.platform}.gem"
124
+ GEMSPEC_NATIVE = "#{GEMSTUB_NATIVE.name}-#{GEMSTUB_NATIVE.platform}.gemspec"
125
+
126
+ task :all => ["native-gem", GEMFILE]
127
+
128
+ desc "build binary gem package"
129
+ task "native-gem" => GEMFILE_NATIVE
130
+
131
+ desc "generate binary gemspec"
132
+ task "native-gemspec" => GEMSPEC_NATIVE
133
+
134
+ file GEMFILE_NATIVE => DOC + EXT + EXTCONF + BIN + LIB + SPEC + TEST + EXAMPLE + SOFILES + RAKEFILE + [GEMSPEC_NATIVE] do
135
+ sh "gem build #{GEMSPEC_NATIVE}"
136
+ end
137
+
138
+ file GEMSPEC_NATIVE => RAKEFILE do
139
+ File.write(GEMSPEC_NATIVE, GEMSTUB_NATIVE.to_ruby, mode: "wb")
140
+ end
141
+
142
+ desc "build c-extension libraries"
143
+ task "sofiles" => SOFILES
144
+
145
+ SOFILES_SET.each do |(ruby, soname, extconf)|
146
+ sodir = File.dirname(soname)
147
+ makefile = File.join(sodir, "Makefile")
148
+
149
+ CLEAN << GEMSPEC_NATIVE << sodir
150
+ CLOBBER << GEMFILE_NATIVE
151
+
152
+ directory sodir
153
+
154
+ desc "generate Makefile for binary extension library"
155
+ file makefile => [sodir, extconf] do
156
+ rel_extconf = Pathname.new(extconf).relative_path_from(Pathname.new(sodir)).to_s
157
+ cd sodir do
158
+ sh *%W"#{ruby} #{rel_extconf} --ruby=#{ruby} #{ENV["EXTCONF"]}"
159
+ end
160
+ end
161
+
162
+ desc "build binary extension library"
163
+ file soname => [makefile] + EXT do
164
+ cd sodir do
165
+ sh "make"
166
+ end
167
+ end
168
+ end
169
+ end
170
+ end
171
+
172
+
173
+ task :all => GEMFILE
174
+ task :tryout => GEMFILE_TRYOUT
175
+
176
+ desc "generate local rdoc"
177
+ task :rdoc => DOC + LIB do
178
+ sh *(%w(rdoc) + GEMSTUB.rdoc_options + DOC + LIB)
179
+ end
180
+
181
+ desc "launch rspec"
182
+ task rspec: :all do
183
+ sh "rspec"
184
+ end
185
+
186
+ desc "build gem package"
187
+ task gem: GEMFILE
188
+
189
+ desc "generate gemspec"
190
+ task gemspec: GEMSPEC
191
+
192
+ desc "print package name"
193
+ task "package-name" do
194
+ puts PACKAGENAME
195
+ end
196
+
197
+ file GEMFILE => DOC + EXT + EXTCONF + BIN + LIB + SPEC + TEST + EXAMPLE + RAKEFILE + [GEMSPEC] do
198
+ sh "gem build #{GEMSPEC}"
199
+ end
200
+
201
+ file GEMFILE_TRYOUT => DOC + EXT + EXTCONF + BIN + LIB + SPEC + TEST + EXAMPLE + RAKEFILE + [GEMSPEC_TRYOUT] do
202
+ #file GEMFILE_TRYOUT do
203
+ sh "gem build #{GEMSPEC_TRYOUT}"
204
+ end
205
+
206
+ file GEMSPEC => RAKEFILE do
207
+ File.write(GEMSPEC, GEMSTUB.to_ruby, mode: "wb")
208
+ end
209
+
210
+ file GEMSPEC_TRYOUT => RAKEFILE do
211
+ File.write(GEMSPEC_TRYOUT, GEMSTUB_TRYOUT.to_ruby, mode: "wb")
212
+ end
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ #vim: set fileencoding:utf-8
3
+
4
+ require "mkmf"
5
+
6
+ create_makefile "ioposrw" or exit 1
@@ -0,0 +1,582 @@
1
+ /* encoding:utf-8 */
2
+
3
+ /*
4
+ * ioposrw.c -
5
+ *
6
+ * - Author: dearblue <dearblue@users.sourceforge.jp>
7
+ * - License: 2-clause BSD License
8
+ * - Project Page: http://sourceforge.jp/projects/rutsubo/
9
+ */
10
+
11
+ #include <ruby.h>
12
+ #include <ruby/io.h>
13
+ #include <ruby/intern.h>
14
+ #include <ruby/thread.h>
15
+
16
+ // rdoc 用に認識させるために使う
17
+ #define RDOC(...)
18
+
19
+ #define NOGVL_FUNC(FUNC) ((void *(*)(void *))(FUNC))
20
+
21
+ #define FUNCALL(RECV, METHOD, ...) \
22
+ ({ \
23
+ VALUE _params[] = { __VA_ARGS__ }; \
24
+ rb_funcall3((RECV), (METHOD), \
25
+ sizeof(_params) / sizeof(_params[0]), _params); \
26
+ }) \
27
+
28
+
29
+ #ifndef RUBY_WIN32_H
30
+ # include <unistd.h>
31
+ # include <sys/types.h>
32
+ # include <sys/stat.h>
33
+ #else
34
+
35
+ #include <windows.h>
36
+ #include <io.h>
37
+
38
+ static ssize_t
39
+ pread(int d, void *buf, size_t nbytes, off_t offset)
40
+ {
41
+ HANDLE file = (HANDLE)_get_osfhandle(d);
42
+ OVERLAPPED ol;
43
+ memset(&ol, 0, sizeof(ol));
44
+ ol.Offset = offset;
45
+ ol.OffsetHigh = offset >> 32;
46
+ DWORD read;
47
+ DWORD status = ReadFile(file, buf, nbytes, &read, &ol);
48
+
49
+ if (status == 0) {
50
+ DWORD err = GetLastError();
51
+ if (err == ERROR_HANDLE_EOF) { return 0; }
52
+ errno = rb_w32_map_errno(err);
53
+ return -1;
54
+ }
55
+
56
+ return read;
57
+ }
58
+
59
+ static ssize_t
60
+ pwrite(int d, const void *buf, size_t nbytes, off_t offset)
61
+ {
62
+ HANDLE file = (HANDLE)_get_osfhandle(d);
63
+ OVERLAPPED ol;
64
+ memset(&ol, 0, sizeof(ol));
65
+ ol.Offset = offset;
66
+ ol.OffsetHigh = offset >> 32;
67
+ DWORD wrote;
68
+ DWORD status = WriteFile(file, buf, nbytes, &wrote, &ol);
69
+ if (status == 0) {
70
+ errno = rb_w32_map_errno(GetLastError());
71
+ return -1;
72
+ }
73
+
74
+ return wrote;
75
+ }
76
+
77
+ #endif /* ! RUBY_WIN32_H */
78
+
79
+
80
+ /* ---- */
81
+
82
+ static VALUE mIOPosRW, mExtIO, mExtStrIO;
83
+
84
+
85
+ static void
86
+ ext_getoffset(VALUE io, int d, VALUE offset, off_t *offp)
87
+ {
88
+ struct stat st;
89
+ if (NIL_P(offset)) {
90
+ if (fstat(d, &st) != 0) {
91
+ VALUE p = rb_inspect(io);
92
+ rb_raise(rb_eArgError,
93
+ "unable to give a nil to `offset' for this IO object - %s",
94
+ StringValueCStr(p));
95
+ }
96
+ *offp = st.st_size;
97
+ } else {
98
+ *offp = NUM2OFFT(offset);
99
+ if (*offp < 0) {
100
+ if (fstat(d, &st) != 0) {
101
+ VALUE p = rb_inspect(io);
102
+ rb_raise(rb_eArgError,
103
+ "unable to give a negative to `offset' for this IO object - %s",
104
+ StringValueCStr(p));
105
+ }
106
+ *offp += st.st_size;
107
+ }
108
+ if (*offp < 0) {
109
+ VALUE p = rb_inspect(io);
110
+ rb_raise(rb_eArgError,
111
+ "file offset too small at %"PRId64" - %s",
112
+ (int64_t)NUM2OFFT(offset),
113
+ StringValueCStr(p));
114
+ }
115
+ }
116
+ }
117
+
118
+ struct readat_args
119
+ {
120
+ rb_io_t *fptr;
121
+ void *buf;
122
+ size_t nbytes;
123
+ off_t offset;
124
+ };
125
+
126
+ static ssize_t
127
+ io_readat_pread(struct readat_args *args)
128
+ {
129
+ return pread(args->fptr->fd, args->buf, args->nbytes, args->offset);
130
+ }
131
+
132
+ static ssize_t
133
+ io_readat_try(struct readat_args *args)
134
+ {
135
+ return (ssize_t)rb_thread_call_without_gvl(NOGVL_FUNC(io_readat_pread),
136
+ args, RUBY_UBF_IO, 0);
137
+ }
138
+
139
+ enum {
140
+ READ_BLOCK_SIZE = 1024 * 1024,
141
+ };
142
+
143
+ static inline VALUE
144
+ io_readat_all(struct readat_args *args, VALUE buffer)
145
+ {
146
+ VALUE tmp = rb_str_buf_new(READ_BLOCK_SIZE);
147
+ rb_str_set_len(tmp, 0);
148
+
149
+ if (NIL_P(buffer)) {
150
+ buffer = rb_str_buf_new(0);
151
+ } else {
152
+ StringValue(buffer);
153
+ }
154
+ rb_str_set_len(buffer, 0);
155
+
156
+ rb_str_locktmp(buffer);
157
+ args->buf = RSTRING_PTR(tmp);
158
+
159
+ for (;;) {
160
+ rb_str_resize(tmp, READ_BLOCK_SIZE);
161
+ args->nbytes = RSTRING_LEN(tmp);
162
+ ssize_t n = (ssize_t)rb_ensure(RUBY_METHOD_FUNC(io_readat_try), (VALUE)args, rb_str_unlocktmp, buffer);
163
+
164
+ if (n == 0) {
165
+ break;
166
+ } else if (n < 0) {
167
+ rb_str_resize(tmp, 0);
168
+ rb_str_set_len(tmp, 0);
169
+ rb_sys_fail(NIL_P(args->fptr->pathv) ? NULL : RSTRING_PTR(args->fptr->pathv));
170
+ } else {
171
+ args->offset += n;
172
+ rb_str_set_len(tmp, n);
173
+ rb_str_concat(buffer, tmp);
174
+ rb_str_locktmp(buffer);
175
+ }
176
+ }
177
+
178
+ rb_str_resize(tmp, 0);
179
+ rb_str_set_len(tmp, 0);
180
+
181
+ if (RSTRING_LEN(buffer) == 0) {
182
+ return Qnil;
183
+ } else {
184
+ return buffer;
185
+ }
186
+ }
187
+
188
+ static inline VALUE
189
+ io_readat_partial(struct readat_args *args, VALUE buffer)
190
+ {
191
+ if (NIL_P(buffer)) {
192
+ buffer = rb_str_buf_new(args->nbytes);
193
+ } else {
194
+ StringValue(buffer);
195
+ }
196
+ rb_str_resize(buffer, args->nbytes);
197
+
198
+ rb_str_locktmp(buffer);
199
+ args->buf = RSTRING_PTR(buffer);
200
+ ssize_t n = (ssize_t)rb_ensure(RUBY_METHOD_FUNC(io_readat_try), (VALUE)args, rb_str_unlocktmp, buffer);
201
+
202
+ if (n == 0) {
203
+ return Qnil;
204
+ } else if (n < 0) {
205
+ rb_sys_fail(NIL_P(args->fptr->pathv) ? NULL : RSTRING_PTR(args->fptr->pathv));
206
+ }
207
+
208
+ rb_str_set_len(buffer, n);
209
+ rb_str_resize(buffer, n);
210
+
211
+ return buffer;
212
+ }
213
+
214
+ static void
215
+ io_readat_security(VALUE io, VALUE offset, VALUE length, VALUE buffer)
216
+ {
217
+ if (rb_safe_level() < 3) {
218
+ return;
219
+ }
220
+
221
+ if (!OBJ_TAINTED(io)) {
222
+ rb_secure(4);
223
+ if (!OBJ_TAINTED(offset) && !OBJ_TAINTED(length) && !OBJ_TAINTED(buffer)) {
224
+ return;
225
+ }
226
+ } else {
227
+ if (OBJ_TAINTED(offset) &&
228
+ (NIL_P(length) || OBJ_TAINTED(length)) &&
229
+ (NIL_P(buffer) || OBJ_TAINTED(buffer))) {
230
+
231
+ return;
232
+ }
233
+ }
234
+
235
+ rb_secure(3);
236
+ }
237
+
238
+ /*
239
+ * call-seq:
240
+ * readat(offset) -> string or nil
241
+ * readat(offset, length) -> string or nil
242
+ * readat(offset, length, buffer) -> buffer or nil
243
+ *
244
+ * 指定位置から読み込むことが出来ます。
245
+ *
246
+ * IO#read との違いは、IO#pos= と IO#read との間でスレッドスイッチが発生することによる、
247
+ * 意図したストリーム位置から #read できないという問題がないことです。
248
+ *
249
+ * [RETURN]
250
+ * 正常に読み込んだ場合、データが格納された buffer、もしくは String インスタンスを返します。
251
+ *
252
+ * ファイルサイズを越えた位置を offset に与えた場合、nil を返します。
253
+ *
254
+ * [offset]
255
+ * 読み込み位置をバイト値で指定します。
256
+ *
257
+ * これはストリーム先端からの絶対位置となります。
258
+ *
259
+ * 負の整数を指定した場合、ファイル終端からの相対位置になります。
260
+ * File#seek に SEEK_END で負の値を指定した場合と同じです。
261
+ *
262
+ * nil を指定した場合、<tt>seek(0, SEEK_END)</tt> と同等となります。
263
+ *
264
+ * [length (省略可能)]
265
+ * 読み込むデータ長をバイト値で指定します。
266
+ *
267
+ * 省略もしくは nil を指定すると、ファイルの最後まで読み込みます。
268
+ *
269
+ * [buffer (省略可能)]
270
+ * 読み込み先バッファとして指定します。
271
+ *
272
+ * 省略もしくは nil を指定すると、IO#readat 内部で勝手に用意します。
273
+ *
274
+ * buffer は変更可能 (frozen ではない String) なインスタンスである必要があります。
275
+ *
276
+ * [EXCEPTIONS]
277
+ * いろいろ。
278
+ *
279
+ * Errno::EXXX だったり、SEGV だったり、これらにとどまりません。
280
+ *
281
+ * 処理中は呼び出しスレッドのみが停止します (GVL を開放します)。
282
+ * その間別スレッドから buffer オブジェクトの変更はできなくなります
283
+ * (厳密に言うと、内部バッファの伸縮を伴う操作が出来なくなるだけです)。
284
+ *
285
+ *
286
+ * ==== 注意点
287
+ *
288
+ * Windows においては、#readat 後に IO 位置が更新されます (IO#pos= と IO#read した後と同じ位置)。
289
+ * これは Windows 上の制限となります。
290
+ */
291
+ static VALUE
292
+ io_readat(int argc, VALUE argv[], VALUE io)
293
+ {
294
+ VALUE offset, length = Qnil, buffer = Qnil;
295
+ struct readat_args args;
296
+
297
+ rb_scan_args(argc, argv, "12", &offset, &length, &buffer);
298
+ io_readat_security(io, offset, length, buffer);
299
+ rb_check_type(io, RUBY_T_FILE);
300
+
301
+ GetOpenFile(io, args.fptr);
302
+ rb_io_check_char_readable(args.fptr);
303
+ ext_getoffset(io, args.fptr->fd, offset, &args.offset);
304
+
305
+ if (NIL_P(length)) {
306
+ buffer = io_readat_all(&args, buffer);
307
+ } else {
308
+ args.nbytes = NUM2SIZET(length);
309
+ if (args.nbytes > 0) {
310
+ buffer = io_readat_partial(&args, buffer);
311
+ } else {
312
+ if (NIL_P(buffer)) {
313
+ buffer = rb_str_new(0, 0);
314
+ } else {
315
+ rb_str_set_len(buffer, 0);
316
+ }
317
+ }
318
+ }
319
+
320
+ if (!NIL_P(buffer)) {
321
+ OBJ_TAINT(buffer);
322
+ }
323
+
324
+ return buffer;
325
+ }
326
+
327
+
328
+ struct writeat_args
329
+ {
330
+ rb_io_t *fptr;
331
+ const void *buf;
332
+ size_t nbytes;
333
+ off_t offset;
334
+ };
335
+
336
+ static ssize_t
337
+ io_writeat_pwrite(struct writeat_args *args)
338
+ {
339
+ return pwrite(args->fptr->fd, args->buf, args->nbytes, args->offset);
340
+ }
341
+
342
+ static ssize_t
343
+ io_writeat_try(struct writeat_args *args)
344
+ {
345
+ return (ssize_t)rb_thread_call_without_gvl(NOGVL_FUNC(io_writeat_pwrite), args, RUBY_UBF_IO, 0);
346
+ }
347
+
348
+ /*
349
+ * call-seq:
350
+ * writeat(offset, buffer) -> wrote size
351
+ *
352
+ * 指定位置への書き込みを行います。
353
+ *
354
+ * [RETURN]
355
+ * 書き込んだデータ量がバイト値で返ります。
356
+ *
357
+ * [offset]
358
+ * 書き込み位置をバイト値で指定します。これはストリーム先端からの絶対位置となります。
359
+ *
360
+ * [buffer]
361
+ * 書き込みたいデータが格納されたStringインスタンスを指定します。
362
+ *
363
+ * [EXCEPTIONS]
364
+ * Errno::EXXX や、SEGV など。
365
+ *
366
+ * 処理中は呼び出しスレッドのみが停止します (GVL を開放します)。
367
+ * また、別スレッドから buffer オブジェクトの変更はできなくなります
368
+ * (厳密に言うと、内部バッファの伸縮を伴う操作が出来なくなるだけです)。
369
+ */
370
+ static VALUE
371
+ io_writeat(VALUE io, VALUE offset, VALUE buffer)
372
+ {
373
+ rb_secure(4);
374
+ rb_check_type(io, RUBY_T_FILE);
375
+
376
+ struct writeat_args args;
377
+
378
+ StringValue(buffer);
379
+ args.nbytes = RSTRING_LEN(buffer);
380
+ GetOpenFile(io, args.fptr);
381
+ rb_io_check_closed(args.fptr);
382
+ ext_getoffset(io, args.fptr->fd, offset, &args.offset);
383
+
384
+ rb_str_locktmp(buffer);
385
+ args.buf = RSTRING_PTR(buffer);
386
+ ssize_t n = (ssize_t)rb_ensure(RUBY_METHOD_FUNC(io_writeat_try), (VALUE)&args, rb_str_unlocktmp, buffer);
387
+
388
+ if (n < 0) {
389
+ rb_sys_fail(NIL_P(args.fptr->pathv) ? NULL : RSTRING_PTR(args.fptr->pathv));
390
+ }
391
+
392
+ return SIZET2NUM(n);
393
+ }
394
+
395
+
396
+ /* ---- */
397
+
398
+
399
+ static VALUE cStringIO;
400
+ static ID IDstring, IDis_closed_read, IDis_closed_write;
401
+
402
+
403
+ static inline VALUE
404
+ strio_getstr(VALUE io)
405
+ {
406
+ VALUE str = FUNCALL(io, IDstring);
407
+ rb_check_type(str, RUBY_T_STRING);
408
+ return str;
409
+ }
410
+
411
+ static inline VALUE
412
+ strio_getread(VALUE io)
413
+ {
414
+ if (RTEST(FUNCALL(io, IDis_closed_read))) {
415
+ rb_raise(rb_eIOError, "not opened for reading");
416
+ }
417
+
418
+ return strio_getstr(io);
419
+ }
420
+
421
+ static inline VALUE
422
+ strio_getwrite(VALUE io)
423
+ {
424
+ if (RTEST(FUNCALL(io, IDis_closed_write))) {
425
+ rb_raise(rb_eIOError, "not opened for writing");
426
+ }
427
+
428
+ return strio_getstr(io);
429
+ }
430
+
431
+
432
+ /*
433
+ * call-seq:
434
+ * readat(offset) -> nil or string
435
+ * readat(offset, length) -> nil or string
436
+ * readat(offset, length, buffer) -> nil or buffer
437
+ *
438
+ * IO#readat と同じ。
439
+ *
440
+ * ただし、例外の搬出は挙動が異なります。このあたりは将来的に (200年後くらいには) 改善される見込みです。
441
+ *
442
+ * 処理中は Ruby のすべてのスレッドが停止します (GVL を開放しないため)。
443
+ */
444
+ static VALUE
445
+ strio_readat(int argc, VALUE argv[], VALUE io)
446
+ {
447
+ VALUE offset, length = Qnil, buffer = Qnil;
448
+
449
+ rb_scan_args(argc, argv, "12", &offset, &length, &buffer);
450
+
451
+ VALUE str = strio_getread(io);
452
+
453
+ VALUE tmp = Qnil;
454
+ if (NIL_P(buffer)) {
455
+ tmp = buffer = rb_str_buf_new(0);
456
+ }
457
+
458
+ ssize_t lenmax = RSTRING_LEN(str);
459
+ ssize_t len = NIL_P(length) ? lenmax : NUM2SSIZET(length);
460
+
461
+ ssize_t off = NUM2SSIZET(offset);
462
+ if (off < 0) {
463
+ off += lenmax;
464
+ }
465
+
466
+ if (len == 0) {
467
+ if (NIL_P(buffer)) {
468
+ buffer = rb_str_new(0, 0);
469
+ } else {
470
+ rb_str_modify(buffer);
471
+ rb_str_set_len(buffer, 0);
472
+ }
473
+ OBJ_INFECT(buffer, io);
474
+ return buffer;
475
+ }
476
+
477
+ if (off >= lenmax) { return Qnil; }
478
+
479
+ if (off + len < 0) {
480
+ rb_raise(rb_eRangeError, "offset and length are too small or too large");
481
+ }
482
+
483
+ if (off + len > lenmax) { len = lenmax - off; }
484
+
485
+ StringValue(buffer);
486
+ rb_str_modify(buffer);
487
+ rb_str_resize(buffer, len);
488
+ rb_str_set_len(buffer, len);
489
+
490
+ memcpy(RSTRING_PTR(buffer), RSTRING_PTR(str) + off, len);
491
+
492
+ OBJ_INFECT(buffer, str);
493
+
494
+ return buffer;
495
+ }
496
+
497
+ /*
498
+ * call-seq:
499
+ * writeat(offset, buffer) -> integer
500
+ *
501
+ * IO#writeat と同じ。
502
+ *
503
+ * ただし、例外の搬出は挙動が異なります。このあたりは将来的に (200年後くらいには) 改善される見込みです。
504
+ *
505
+ * 処理中は Ruby のすべてのスレッドが停止します (GVL を開放しないため)。
506
+ */
507
+ static VALUE
508
+ strio_writeat(VALUE io, VALUE offset, VALUE buffer)
509
+ {
510
+ rb_secure(4);
511
+
512
+ VALUE str = strio_getwrite(io);
513
+
514
+ ssize_t off = NUM2SSIZET(offset);
515
+ if (off < 0) { NUM2SSIZET(SIZET2NUM(off)); } // EXCEPTION!
516
+ StringValue(buffer);
517
+ ssize_t len = RSTRING_LEN(buffer);
518
+ if (len < 0) { NUM2SSIZET(SIZET2NUM(len)); } // EXCEPTION!
519
+ if (off + len < 0) {
520
+ rb_raise(rb_eRangeError,
521
+ "offset and buffer size are too large");
522
+ }
523
+
524
+ if (off + len > RSTRING_LEN(str)) {
525
+ ssize_t p = RSTRING_LEN(str);
526
+ rb_str_modify(buffer);
527
+ rb_str_resize(str, off + len);
528
+ rb_str_set_len(str, off + len);
529
+ if (off > p) {
530
+ memset(RSTRING_PTR(str) + p, 0, off - p);
531
+ }
532
+ } else {
533
+ rb_str_modify(str);
534
+ }
535
+
536
+ memcpy(RSTRING_PTR(str) + off, RSTRING_PTR(buffer), len);
537
+
538
+ return SSIZET2NUM(len);
539
+ }
540
+
541
+
542
+ /*
543
+ * Document-class: IO
544
+ *
545
+ * <b>現段階の実装状況においてセーフレベルをまともに扱っていないため、セキュリティリスクがあります。</b>
546
+ *
547
+ * この拡張ライブラリは IO#readat / IO#writeat を提供します。
548
+ *
549
+ * <tt>require "ioposrw"</tt> によって利用できるようになります。
550
+ */
551
+
552
+ /*
553
+ * Document-class: StringIO
554
+ *
555
+ * <b>現段階の実装状況においてセーフレベルをまともに扱っていないため、セキュリティリスクがあります。</b>
556
+ *
557
+ * StringIO#readat / StringIO#writeat を提供します。
558
+ *
559
+ * StringIO#readat / StringIO#writeat は <tt>require "ioposrw"</tt> しただけでは有効になりません。
560
+ *
561
+ * <tt>require "ioposrw/stringio"</tt> をして下さい。
562
+ *
563
+ * まだ <tt>require "stringio"</tt> されていなければ勝手に読み込みます。
564
+ */
565
+
566
+ void
567
+ Init_ioposrw(void)
568
+ {
569
+ IDstring = rb_intern_const("string");
570
+ IDis_closed_read = rb_intern_const("closed_read?");
571
+ IDis_closed_write = rb_intern_const("closed_write?");
572
+
573
+ mIOPosRW = rb_define_module("IOPositioningReadWrite");
574
+ mExtIO = rb_define_module_under(mIOPosRW, "IO");
575
+ mExtStrIO = rb_define_module_under(mIOPosRW, "StringIO");
576
+
577
+ rb_define_method(mExtIO, "readat", RUBY_METHOD_FUNC(io_readat), -1);
578
+ rb_define_method(mExtIO, "writeat", RUBY_METHOD_FUNC(io_writeat), 2);
579
+
580
+ rb_define_method(mExtStrIO, "readat", RUBY_METHOD_FUNC(strio_readat), -1);
581
+ rb_define_method(mExtStrIO, "writeat", RUBY_METHOD_FUNC(strio_writeat), 2);
582
+ }
@@ -0,0 +1,35 @@
1
+ unless File.read("README.md", 4096) =~ /^\s*\*\s*version:{1,2}\s*(.+)/i
2
+ raise "バージョン情報が README.md に見つかりません"
3
+ end
4
+
5
+ ver = $1
6
+ verfile = "lib/ioposrw/version.rb"
7
+ LIB << verfile
8
+
9
+ file verfile => "README.md" do |*args|
10
+ File.binwrite args[0].name, <<-VERSION_FILE
11
+ module IOPositioningReadWrite
12
+ VERSION = "#{ver}"
13
+ end
14
+ VERSION_FILE
15
+ end
16
+
17
+
18
+ GEMSTUB = Gem::Specification.new do |s|
19
+ s.name = "ioposrw"
20
+ s.version = ver
21
+ s.summary = "Append IO#readat/writeat methods. These provide similar functionality to the POSIX pread/pwrite."
22
+ s.description = <<EOS
23
+ Append IO#readat/writeat methods. These provide similar functionality to the POSIX pread/pwrite.
24
+ This library works on windows and unix. But tested on windows and FreeBSD only.
25
+ EOS
26
+ s.license = "BSD-2-Clause License"
27
+ s.author = "dearblue"
28
+ s.email = "dearblue@users.noreply.github.com"
29
+ s.homepage = "https://github.com/dearblue/ruby-ioposrw"
30
+
31
+ s.add_development_dependency "rake"
32
+ s.add_development_dependency "test-unit"
33
+ end
34
+
35
+ DOC << "QUICKREF.md"
@@ -0,0 +1,35 @@
1
+ ver = RUBY_VERSION.slice(/\d+\.\d+/)
2
+ soname = File.basename(__FILE__, ".rb") << ".so"
3
+ lib = File.join(File.dirname(__FILE__), ver, soname)
4
+ if File.file?(lib)
5
+ require_relative File.join(ver, soname)
6
+ else
7
+ require_relative soname
8
+ end
9
+
10
+ require_relative "ioposrw/version"
11
+
12
+ module IOPositioningReadWrite
13
+ module IOClass
14
+ #
15
+ # call-seq:
16
+ # IO.ioposrw_enable_stringio_extend -> nil
17
+ #
18
+ # This is a feature that is FUTURE OBSOLUTE.
19
+ #
20
+ # Please use <tt>require "ioposrw/stringio"</tt> insted.
21
+ #
22
+ def ioposrw_enable_stringio_extend
23
+ warn <<-EOM
24
+ (warning) #{caller(1, 1)[0]}: IO.ioposrw_enable_stringio_extend is FUTURE OBSOLUTE. please use ``require \"ioposrw/stringio\"'' insted.
25
+ EOM
26
+ require_relative "ioposrw/stringio"
27
+ nil
28
+ end
29
+ end
30
+ end
31
+
32
+ class IO
33
+ extend IOPositioningReadWrite::IOClass
34
+ include IOPositioningReadWrite::IO
35
+ end
@@ -0,0 +1,6 @@
1
+ require_relative "../ioposrw"
2
+ require "stringio"
3
+
4
+ class StringIO
5
+ include IOPositioningReadWrite::StringIO
6
+ end
@@ -0,0 +1,3 @@
1
+ module IOPositioningReadWrite
2
+ VERSION = "0.4"
3
+ end
@@ -0,0 +1,125 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "test/unit"
4
+ require "ioposrw"
5
+ require "tempfile"
6
+
7
+ class TC_ioposrw < Test::Unit::TestCase
8
+ attr_reader :file
9
+
10
+ SOURCE = ((0...256).to_a * 257).shuffle!.pack("C*")
11
+
12
+ def setup
13
+ @file = Tempfile.open("")
14
+ file.binmode
15
+ file << SOURCE
16
+ file.flush
17
+ end
18
+
19
+ def try_readat(file)
20
+ [
21
+ [0, 0, false, "", "空文字列になるべき"],
22
+ [SOURCE.length, 0, false, "", "空文字列になるべき"],
23
+ [SOURCE.length, nil, false, nil, "不適切な EOF"],
24
+ [file.size, nil, false, nil, "不適切な EOF"],
25
+ [file.size, 99999999, false, nil, "不適切な EOF"],
26
+ [0, nil, false, SOURCE, "先頭からの全読み込みが不一致"],
27
+ [0, SOURCE.length, false, SOURCE, "先頭からの全読み込みが不一致"],
28
+ [SOURCE.length / 2, SOURCE.length / 2, false,
29
+ SOURCE.slice(SOURCE.length / 2, SOURCE.length / 2),
30
+ "途中位置からの固定長分の読み込みが不一致"],
31
+
32
+ [0, 0, true, "", "空文字列になるべき"],
33
+ [SOURCE.length, 0, true, "", "空文字列になるべき"],
34
+ [SOURCE.length, nil, true, nil, "不適切な EOF"],
35
+ [file.size, nil, true, nil, "不適切な EOF"],
36
+ [file.size, 99999999, true, nil, "不適切な EOF"],
37
+ [0, nil, true, SOURCE, "先頭からの全読み込みが不一致"],
38
+ [0, SOURCE.length, true, SOURCE, "先頭からの全読み込みが不一致"],
39
+ [SOURCE.length / 2, SOURCE.length / 2, true,
40
+ SOURCE.slice(SOURCE.length / 2, SOURCE.length / 2),
41
+ "途中位置からの固定長分の読み込みが不一致"],
42
+ ].each do |pos, size, buf, ref, mesg|
43
+ if buf
44
+ buf = 200.times.reduce("") { |a, *| a << rand(256).chr(Encoding::BINARY) }
45
+ else
46
+ buf = nil
47
+ end
48
+ assert(file.readat(pos, size, buf) == ref,
49
+ "%s (%s)" % [mesg, pos: pos, size: size, buf_len: (buf ? buf.length : nil)])
50
+ end
51
+
52
+ 256.times do
53
+ size = rand(file.size * 2)
54
+ pos = file.pos = rand(file.size)
55
+ assert(file.read(size) == (buf = file.readat(pos, size)),
56
+ "IO#pos + IO#read との不一致 (pos=#{pos}, size=#{size})")
57
+ buf1 = "".force_encoding(Encoding::BINARY)
58
+ file.pos = pos
59
+ assert(file.read(size, buf1) == (buf = file.readat(pos, size, buf1)),
60
+ "IO#pos + IO#read with buffer との不一致 (pos=#{pos}, size=#{size})")
61
+ assert(buf == SOURCE.byteslice(pos, size),
62
+ "SOURCE.byteslice との不一致 (pos=#{pos}, size=#{size})")
63
+ end
64
+ end
65
+
66
+ def try_writeat(file)
67
+ test = proc do |pos, buf|
68
+ assert(file.writeat(pos, buf) == buf.bytesize, "書き込み量の不一致")
69
+ if SOURCE.length < pos
70
+ SOURCE << "\0" * (pos - SOURCE.length)
71
+ end
72
+ SOURCE[pos, buf.bytesize] = buf
73
+ assert(file.size == SOURCE.length, "ファイルサイズが不正 (file.size=#{file.size})")
74
+ buf = file.readat(0)
75
+ assert(buf.length == SOURCE.length, "ファイルデータ量の不一致")
76
+ assert(buf == SOURCE, "書き込みデータの不一致")
77
+ end
78
+ [
79
+ [0, "HERGFDGFHMFDNGF"],
80
+ [10, "fsdghjljlkjhtgrfegvhjmg"],
81
+ [SOURCE.length + 100, "guiuydrtfv nm,j\;lkjchxdgxehtrjyukli,mnbvgdshbjt\n\r\r\n\0liu;79pfy7kt7lofvol"],
82
+ ].each(&test)
83
+
84
+ 256.times do
85
+ pos = rand(SOURCE.length + 16384)
86
+ buf = (0...256).to_a.shuffle!.pack("C*")
87
+ test.(pos, buf)
88
+ end
89
+ end
90
+
91
+ def test_io_readat
92
+ try_readat(file)
93
+ end
94
+
95
+ def test_io_writeat
96
+ try_writeat(file)
97
+ end
98
+
99
+ def test_stringio_enable
100
+ if RUBY_VERSION < "2.2"
101
+ th = Thread.fork do
102
+ $SAFE = 2
103
+ IO.ioposrw_enable_stringio_extend
104
+ end
105
+ assert_raise(SecurityError) { th.join }
106
+ end
107
+
108
+ require "ioposrw/stringio"
109
+ assert($".select{|x| x=~/stringio\.so$/}.length == 1, 'require "stringio" されない')
110
+ assert(StringIO.instance_methods.select{|x| x==:readat}.length == 1, "StringIO#readat が未定義")
111
+ assert(StringIO.instance_methods.select{|x| x==:writeat}.length == 1, "StringIO#writeat が未定義")
112
+ @@str = StringIO.new(SOURCE.dup)
113
+ assert_not_nil(@@str)
114
+ end
115
+
116
+ def test_stringio_readat
117
+ assert(@@str)
118
+ try_readat(@@str)
119
+ end
120
+
121
+ def test_stringio_writeat
122
+ assert(@@str)
123
+ try_writeat(@@str)
124
+ end
125
+ end
metadata ADDED
@@ -0,0 +1,97 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ioposrw
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.4'
5
+ platform: ruby
6
+ authors:
7
+ - dearblue
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-12-26 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: test-unit
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: |
42
+ Append IO#readat/writeat methods. These provide similar functionality to the POSIX pread/pwrite.
43
+ This library works on windows and unix. But tested on windows and FreeBSD only.
44
+ email: dearblue@users.noreply.github.com
45
+ executables: []
46
+ extensions:
47
+ - ext/extconf.rb
48
+ extra_rdoc_files:
49
+ - HISTORY.ja.md
50
+ - QUICKREF.md
51
+ - README.md
52
+ - ext/ioposrw.c
53
+ - lib/ioposrw.rb
54
+ - lib/ioposrw/stringio.rb
55
+ - lib/ioposrw/version.rb
56
+ files:
57
+ - HISTORY.ja.md
58
+ - QUICKREF.md
59
+ - README.md
60
+ - Rakefile
61
+ - ext/extconf.rb
62
+ - ext/ioposrw.c
63
+ - gemstub.rb
64
+ - lib/ioposrw.rb
65
+ - lib/ioposrw/stringio.rb
66
+ - lib/ioposrw/version.rb
67
+ - test/test_ioposrw.rb
68
+ homepage: https://github.com/dearblue/ruby-ioposrw
69
+ licenses:
70
+ - BSD-2-Clause License
71
+ metadata: {}
72
+ post_install_message:
73
+ rdoc_options:
74
+ - "--charset"
75
+ - UTF-8
76
+ - "-m"
77
+ - README.md
78
+ require_paths:
79
+ - lib
80
+ required_ruby_version: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: '0'
85
+ required_rubygems_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ requirements: []
91
+ rubyforge_project:
92
+ rubygems_version: 2.6.14
93
+ signing_key:
94
+ specification_version: 4
95
+ summary: Append IO#readat/writeat methods. These provide similar functionality to
96
+ the POSIX pread/pwrite.
97
+ test_files: []