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.
- checksums.yaml +7 -0
- data/HISTORY.ja.md +21 -0
- data/QUICKREF.md +13 -0
- data/README.md +68 -0
- data/Rakefile +212 -0
- data/ext/extconf.rb +6 -0
- data/ext/ioposrw.c +582 -0
- data/gemstub.rb +35 -0
- data/lib/ioposrw.rb +35 -0
- data/lib/ioposrw/stringio.rb +6 -0
- data/lib/ioposrw/version.rb +3 -0
- data/test/test_ioposrw.rb +125 -0
- metadata +97 -0
checksums.yaml
ADDED
@@ -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
|
data/HISTORY.ja.md
ADDED
@@ -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"`` を使って下さい。
|
data/QUICKREF.md
ADDED
@@ -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``.
|
data/README.md
ADDED
@@ -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ライセンスの下で取り扱うことができます。
|
data/Rakefile
ADDED
@@ -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
|
data/ext/extconf.rb
ADDED
data/ext/ioposrw.c
ADDED
@@ -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
|
+
}
|
data/gemstub.rb
ADDED
@@ -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"
|
data/lib/ioposrw.rb
ADDED
@@ -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,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: []
|