├── .gitignore ├── .travis.yml ├── README.md ├── include └── mruby │ └── ext │ └── io.h ├── mrbgem.rake ├── mrblib ├── file.rb ├── file_constants.rb ├── io.rb └── kernel.rb ├── run_test.rb ├── src ├── file.c ├── file_test.c ├── io.c └── mruby_io_gem.c └── test ├── file.rb ├── file_test.rb ├── gc_filedes.sh ├── io.rb └── mruby_io_test.c /.gitignore: -------------------------------------------------------------------------------- 1 | /tmp 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | script: 2 | - "ruby run_test.rb all test" 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | mruby-io 2 | ======== 3 | [![Build Status](https://travis-ci.org/iij/mruby-io.svg?branch=master)](https://travis-ci.org/iij/mruby-io) 4 | 5 | `IO` and `File` classes for mruby 6 | 7 | **Note: mruby-io is incorporated in mruby now. If you use mruby 1.4.0 or later, build it with bundled mruby-io. 8 | This repository supports mruby <= 1.3.x only.** 9 | 10 | ## Installation 11 | Add the line below to your `build_config.rb`: 12 | 13 | ``` 14 | conf.gem :github => 'iij/mruby-io' 15 | ``` 16 | 17 | ## Implemented methods 18 | 19 | ### IO 20 | - http://doc.ruby-lang.org/ja/1.9.3/class/IO.html 21 | 22 | | method | mruby-io | memo | 23 | | ------------------------- | -------- | ---- | 24 | | IO.binread | | | 25 | | IO.binwrite | | | 26 | | IO.copy_stream | | | 27 | | IO.new, IO.for_fd, IO.open | o | | 28 | | IO.foreach | | | 29 | | IO.pipe | o | | 30 | | IO.popen | o | | 31 | | IO.read | o | | 32 | | IO.readlines | | | 33 | | IO.select | o | | 34 | | IO.sysopen | o | | 35 | | IO.try_convert | | | 36 | | IO.write | | | 37 | | IO#<< | | | 38 | | IO#advise | | | 39 | | IO#autoclose= | | | 40 | | IO#autoclose? | | | 41 | | IO#binmode | | | 42 | | IO#binmode? | | | 43 | | IO#bytes | | obsolete | 44 | | IO#chars | | obsolete | 45 | | IO#clone, IO#dup | | | 46 | | IO#close | o | | 47 | | IO#close_on_exec= | o | | 48 | | IO#close_on_exec? | o | | 49 | | IO#close_read | | | 50 | | IO#close_write | | | 51 | | IO#closed? | o | | 52 | | IO#codepoints | | obsolete | 53 | | IO#each_byte | o | | 54 | | IO#each_char | o | | 55 | | IO#each_codepoint | | | 56 | | IO#each_line | o | | 57 | | IO#eof, IO#eof? | o | | 58 | | IO#external_encoding | | | 59 | | IO#fcntl | | | 60 | | IO#fdatasync | | | 61 | | IO#fileno, IO#to_i | o | | 62 | | IO#flush | o | | 63 | | IO#fsync | | | 64 | | IO#getbyte | | | 65 | | IO#getc | o | | 66 | | IO#gets | o | | 67 | | IO#internal_encoding | | | 68 | | IO#ioctl | | | 69 | | IO#isatty, IO#tty? | o | | 70 | | IO#lineno | | | 71 | | IO#lineno= | | | 72 | | IO#lines | | obsolete | 73 | | IO#pid | o | | 74 | | IO#pos, IO#tell | o | | 75 | | IO#pos= | o | | 76 | | IO#print | o | | 77 | | IO#printf | o | | 78 | | IO#putc | | | 79 | | IO#puts | o | | 80 | | IO#read | o | | 81 | | IO#read_nonblock | | | 82 | | IO#readbyte | | | 83 | | IO#readchar | o | | 84 | | IO#readline | o | | 85 | | IO#readlines | o | | 86 | | IO#readpartial | | | 87 | | IO#reopen | | | 88 | | IO#rewind | | | 89 | | IO#seek | o | | 90 | | IO#set_encoding | | | 91 | | IO#stat | | | 92 | | IO#sync | o | | 93 | | IO#sync= | o | | 94 | | IO#sysread | o | | 95 | | IO#sysseek | o | | 96 | | IO#syswrite | o | | 97 | | IO#to_io | | | 98 | | IO#ungetbyte | | | 99 | | IO#ungetc | o | | 100 | | IO#write | o | | 101 | | IO#write_nonblock | | | 102 | 103 | ### File 104 | - http://doc.ruby-lang.org/ja/1.9.3/class/File.html 105 | 106 | | method | mruby-io | memo | 107 | | --------------------------- | -------- | ---- | 108 | | File.absolute_path | | | 109 | | File.atime | | | 110 | | File.basename | o | | 111 | | File.blockdev? | | FileTest | 112 | | File.chardev? | | FileTest | 113 | | File.chmod | o | | 114 | | File.chown | | | 115 | | File.ctime | | | 116 | | File.delete, File.unlink | o | | 117 | | File.directory? | o | FileTest | 118 | | File.dirname | o | | 119 | | File.executable? | | FileTest | 120 | | File.executable_real? | | FileTest | 121 | | File.exist?, exists? | o | FileTest | 122 | | File.expand_path | o | | 123 | | File.extname | o | | 124 | | File.file? | o | FileTest | 125 | | File.fnmatch, File.fnmatch? | | | 126 | | File.ftype | | | 127 | | File.grpowned? | | FileTest | 128 | | File.identical? | | FileTest | 129 | | File.join | o | | 130 | | File.lchmod | | | 131 | | File.lchown | | | 132 | | File.link | | | 133 | | File.lstat | | | 134 | | File.mtime | | | 135 | | File.new, File.open | o | | 136 | | File.owned? | | FileTest | 137 | | File.path | | | 138 | | File.pipe? | o | FileTest | 139 | | File.readable? | | FileTest | 140 | | File.readable_real? | | FileTest | 141 | | File.readlink | o | | 142 | | File.realdirpath | | | 143 | | File.realpath | o | | 144 | | File.rename | o | | 145 | | File.setgid? | | FileTest | 146 | | File.setuid? | | FileTest | 147 | | File.size | o | | 148 | | File.size? | o | FileTest | 149 | | File.socket? | o | FileTest | 150 | | File.split | | | 151 | | File.stat | | | 152 | | File.sticky? | | FileTest | 153 | | File.symlink | | | 154 | | File.symlink? | o | FileTest | 155 | | File.truncate | | | 156 | | File.umask | o | | 157 | | File.utime | | | 158 | | File.world_readable? | | | 159 | | File.world_writable? | | | 160 | | File.writable? | | FileTest | 161 | | File.writable_real? | | FileTest | 162 | | File.zero? | o | FileTest | 163 | | File#atime | | | 164 | | File#chmod | | | 165 | | File#chown | | | 166 | | File#ctime | | | 167 | | File#flock | o | | 168 | | File#lstat | | | 169 | | File#mtime | | | 170 | | File#path, File#to_path | o | | 171 | | File#size | | | 172 | | File#truncate | | | 173 | 174 | 175 | ## License 176 | 177 | Copyright (c) 2013 Internet Initiative Japan Inc. 178 | 179 | Permission is hereby granted, free of charge, to any person obtaining a 180 | copy of this software and associated documentation files (the "Software"), 181 | to deal in the Software without restriction, including without limitation 182 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 183 | and/or sell copies of the Software, and to permit persons to whom the 184 | Software is furnished to do so, subject to the following conditions: 185 | 186 | The above copyright notice and this permission notice shall be included in 187 | all copies or substantial portions of the Software. 188 | 189 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 190 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 191 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 192 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 193 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 194 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 195 | DEALINGS IN THE SOFTWARE. 196 | -------------------------------------------------------------------------------- /include/mruby/ext/io.h: -------------------------------------------------------------------------------- 1 | /* 2 | ** io.h - IO class 3 | */ 4 | 5 | #ifndef MRUBY_IO_H 6 | #define MRUBY_IO_H 7 | 8 | #if defined(__cplusplus) 9 | extern "C" { 10 | #endif 11 | 12 | struct mrb_io { 13 | int fd; /* file descriptor, or -1 */ 14 | int fd2; /* file descriptor to write if it's different from fd, or -1 */ 15 | int pid; /* child's pid (for pipes) */ 16 | unsigned int readable:1, 17 | writable:1, 18 | sync:1; 19 | }; 20 | 21 | #define FMODE_READABLE 0x00000001 22 | #define FMODE_WRITABLE 0x00000002 23 | #define FMODE_READWRITE (FMODE_READABLE|FMODE_WRITABLE) 24 | #define FMODE_BINMODE 0x00000004 25 | #define FMODE_APPEND 0x00000040 26 | #define FMODE_CREATE 0x00000080 27 | #define FMODE_TRUNC 0x00000800 28 | 29 | #define E_IO_ERROR (mrb_class_get(mrb, "IOError")) 30 | #define E_EOF_ERROR (mrb_class_get(mrb, "EOFError")) 31 | 32 | mrb_value mrb_io_fileno(mrb_state *mrb, mrb_value io); 33 | 34 | #if defined(__cplusplus) 35 | } /* extern "C" { */ 36 | #endif 37 | #endif /* MRUBY_IO_H */ 38 | -------------------------------------------------------------------------------- /mrbgem.rake: -------------------------------------------------------------------------------- 1 | MRuby::Gem::Specification.new('mruby-io') do |spec| 2 | spec.license = 'MIT' 3 | spec.authors = 'Internet Initiative Japan Inc.' 4 | 5 | spec.cc.include_paths << "#{build.root}/src" 6 | 7 | case RUBY_PLATFORM 8 | when /mingw|mswin/ 9 | spec.linker.libraries += ['Ws2_32'] 10 | #spec.cc.include_paths += ["C:/Windows/system/include"] 11 | spec.linker.library_paths += ["C:/Windows/system"] 12 | end 13 | if build.kind_of?(MRuby::CrossBuild) && %w(x86_64-w64-mingw32 i686-w64-mingw32).include?(build.host_target) 14 | spec.linker.libraries += ['ws2_32'] 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /mrblib/file.rb: -------------------------------------------------------------------------------- 1 | class File < IO 2 | class FileError < Exception; end 3 | class NoFileError < FileError; end 4 | class UnableToStat < FileError; end 5 | class PermissionError < FileError; end 6 | 7 | attr_accessor :path 8 | 9 | def initialize(fd_or_path, mode = "r", perm = 0666) 10 | if fd_or_path.kind_of? Fixnum 11 | super(fd_or_path, mode) 12 | else 13 | @path = fd_or_path 14 | fd = IO.sysopen(@path, mode, perm) 15 | super(fd, mode) 16 | end 17 | end 18 | 19 | def self.join(*names) 20 | return "" if names.empty? 21 | 22 | names.map! do |name| 23 | case name 24 | when String 25 | name 26 | when Array 27 | if names == name 28 | raise ArgumentError, "recursive array" 29 | end 30 | join(*name) 31 | else 32 | raise TypeError, "no implicit conversion of #{name.class} into String" 33 | end 34 | end 35 | 36 | return names[0] if names.size == 1 37 | 38 | if names[0][-1] == File::SEPARATOR 39 | s = names[0][0..-2] 40 | else 41 | s = names[0].dup 42 | end 43 | 44 | (1..names.size-2).each { |i| 45 | t = names[i] 46 | if t[0] == File::SEPARATOR and t[-1] == File::SEPARATOR 47 | t = t[1..-2] 48 | elsif t[0] == File::SEPARATOR 49 | t = t[1..-1] 50 | elsif t[-1] == File::SEPARATOR 51 | t = t[0..-2] 52 | end 53 | s += File::SEPARATOR + t if t != "" 54 | } 55 | if names[-1][0] == File::SEPARATOR 56 | s += File::SEPARATOR + names[-1][1..-1] 57 | else 58 | s += File::SEPARATOR + names[-1] 59 | end 60 | s 61 | end 62 | 63 | def self.expand_path(path, default_dir = '.') 64 | def concat_path(path, base_path) 65 | if path[0] == "/" || path[1] == ':' # Windows root! 66 | expanded_path = path 67 | elsif path[0] == "~" 68 | if (path[1] == "/" || path[1] == nil) 69 | dir = path[1, path.size] 70 | home_dir = _gethome 71 | 72 | unless home_dir 73 | raise ArgumentError, "couldn't find HOME environment -- expanding '~'" 74 | end 75 | 76 | expanded_path = home_dir 77 | expanded_path += dir if dir 78 | expanded_path += "/" 79 | else 80 | splitted_path = path.split("/") 81 | user = splitted_path[0][1, splitted_path[0].size] 82 | dir = "/" + splitted_path[1, splitted_path.size].join("/") 83 | 84 | home_dir = _gethome(user) 85 | 86 | unless home_dir 87 | raise ArgumentError, "user #{user} doesn't exist" 88 | end 89 | 90 | expanded_path = home_dir 91 | expanded_path += dir if dir 92 | expanded_path += "/" 93 | end 94 | else 95 | expanded_path = concat_path(base_path, _getwd) 96 | expanded_path += "/" + path 97 | end 98 | 99 | expanded_path 100 | end 101 | 102 | expanded_path = concat_path(path, default_dir) 103 | drive_prefix = "" 104 | if File::ALT_SEPARATOR && expanded_path.size > 2 && 105 | ("A".."Z").include?(expanded_path[0].upcase) && expanded_path[1] == ":" 106 | drive_prefix = expanded_path[0, 2] 107 | expanded_path = expanded_path[2, expanded_path.size] 108 | end 109 | expand_path_array = [] 110 | if File::ALT_SEPARATOR && expanded_path.include?(File::ALT_SEPARATOR) 111 | expanded_path.gsub!(File::ALT_SEPARATOR, '/') 112 | end 113 | while expanded_path.include?('//') 114 | expanded_path = expanded_path.gsub('//', '/') 115 | end 116 | 117 | if expanded_path != "/" 118 | expanded_path.split('/').each do |path_token| 119 | if path_token == '..' 120 | if expand_path_array.size > 1 121 | expand_path_array.pop 122 | end 123 | elsif path_token == '.' 124 | # nothing to do. 125 | else 126 | expand_path_array << path_token 127 | end 128 | end 129 | 130 | expanded_path = expand_path_array.join("/") 131 | if expanded_path.empty? 132 | expanded_path = '/' 133 | end 134 | end 135 | if drive_prefix.empty? 136 | expanded_path 137 | else 138 | drive_prefix + expanded_path.gsub("/", File::ALT_SEPARATOR) 139 | end 140 | end 141 | 142 | def self.foreach(file) 143 | if block_given? 144 | self.open(file) do |f| 145 | f.each {|l| yield l} 146 | end 147 | else 148 | return self.new(file) 149 | end 150 | end 151 | 152 | def self.directory?(file) 153 | FileTest.directory?(file) 154 | end 155 | 156 | def self.exist?(file) 157 | FileTest.exist?(file) 158 | end 159 | 160 | def self.exists?(file) 161 | FileTest.exists?(file) 162 | end 163 | 164 | def self.file?(file) 165 | FileTest.file?(file) 166 | end 167 | 168 | def self.pipe?(file) 169 | FileTest.pipe?(file) 170 | end 171 | 172 | def self.size(file) 173 | FileTest.size(file) 174 | end 175 | 176 | def self.size?(file) 177 | FileTest.size?(file) 178 | end 179 | 180 | def self.socket?(file) 181 | FileTest.socket?(file) 182 | end 183 | 184 | def self.symlink?(file) 185 | FileTest.symlink?(file) 186 | end 187 | 188 | def self.zero?(file) 189 | FileTest.zero?(file) 190 | end 191 | 192 | def self.extname(filename) 193 | fname = self.basename(filename) 194 | return '' if fname[0] == '.' || fname.index('.').nil? 195 | ext = fname.split('.').last 196 | ext.empty? ? '' : ".#{ext}" 197 | end 198 | 199 | def self.path(filename) 200 | if filename.kind_of?(String) 201 | filename 202 | elsif filename.respond_to?(:to_path) 203 | filename.to_path 204 | else 205 | raise TypeError, "no implicit conversion of #{filename.class} into String" 206 | end 207 | end 208 | end 209 | -------------------------------------------------------------------------------- /mrblib/file_constants.rb: -------------------------------------------------------------------------------- 1 | class File 2 | module Constants 3 | RDONLY = 0 4 | WRONLY = 1 5 | RDWR = 2 6 | NONBLOCK = 4 7 | APPEND = 8 8 | 9 | BINARY = 0 10 | SYNC = 128 11 | NOFOLLOW = 256 12 | CREAT = 512 13 | TRUNC = 1024 14 | EXCL = 2048 15 | 16 | NOCTTY = 131072 17 | DSYNC = 4194304 18 | 19 | FNM_SYSCASE = 0 20 | FNM_NOESCAPE = 1 21 | FNM_PATHNAME = 2 22 | FNM_DOTMATCH = 4 23 | FNM_CASEFOLD = 8 24 | end 25 | end 26 | 27 | class File 28 | include File::Constants 29 | end 30 | -------------------------------------------------------------------------------- /mrblib/io.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # IO 3 | 4 | class IOError < StandardError; end 5 | class EOFError < IOError; end 6 | 7 | class IO 8 | SEEK_SET = 0 9 | SEEK_CUR = 1 10 | SEEK_END = 2 11 | 12 | BUF_SIZE = 4096 13 | 14 | def self.open(*args, &block) 15 | io = self.new(*args) 16 | 17 | return io unless block 18 | 19 | begin 20 | yield io 21 | ensure 22 | begin 23 | io.close unless io.closed? 24 | rescue StandardError 25 | end 26 | end 27 | end 28 | 29 | def self.popen(command, mode = 'r', opts={}, &block) 30 | if !self.respond_to?(:_popen) 31 | raise NotImplementedError, "popen is not supported on this platform" 32 | end 33 | io = self._popen(command, mode, opts) 34 | return io unless block 35 | 36 | begin 37 | yield io 38 | ensure 39 | begin 40 | io.close unless io.closed? 41 | rescue IOError 42 | # nothing 43 | end 44 | end 45 | end 46 | 47 | def self.pipe(&block) 48 | if !self.respond_to?(:_pipe) 49 | raise NotImplementedError, "pipe is not supported on this platform" 50 | end 51 | if block 52 | begin 53 | r, w = IO._pipe 54 | yield r, w 55 | ensure 56 | r.close unless r.closed? 57 | w.close unless w.closed? 58 | end 59 | else 60 | IO._pipe 61 | end 62 | end 63 | 64 | def self.read(path, length=nil, offset=nil, opt=nil) 65 | if not opt.nil? # 4 arguments 66 | offset ||= 0 67 | elsif not offset.nil? # 3 arguments 68 | if offset.is_a? Hash 69 | opt = offset 70 | offset = 0 71 | else 72 | opt = {} 73 | end 74 | elsif not length.nil? # 2 arguments 75 | if length.is_a? Hash 76 | opt = length 77 | offset = 0 78 | length = nil 79 | else 80 | offset = 0 81 | opt = {} 82 | end 83 | else # only 1 argument 84 | opt = {} 85 | offset = 0 86 | length = nil 87 | end 88 | 89 | str = "" 90 | fd = -1 91 | io = nil 92 | begin 93 | if path[0] == "|" 94 | io = IO.popen(path[1..-1], (opt[:mode] || "r")) 95 | else 96 | fd = IO.sysopen(path) 97 | io = IO.open(fd, opt[:mode] || "r") 98 | end 99 | io.seek(offset) if offset > 0 100 | str = io.read(length) 101 | ensure 102 | if io 103 | io.close 104 | elsif fd != -1 105 | IO._sysclose(fd) 106 | end 107 | end 108 | str 109 | end 110 | 111 | def flush 112 | # mruby-io always writes immediately (no output buffer). 113 | raise IOError, "closed stream" if self.closed? 114 | self 115 | end 116 | 117 | def hash 118 | # We must define IO#hash here because IO includes Enumerable and 119 | # Enumerable#hash will call IO#read... 120 | self.__id__ 121 | end 122 | 123 | def write(string) 124 | str = string.is_a?(String) ? string : string.to_s 125 | return str.size unless str.size > 0 126 | if 0 < @buf.length 127 | # reset real pos ignore buf 128 | seek(pos, SEEK_SET) 129 | end 130 | len = syswrite(str) 131 | len 132 | end 133 | 134 | def <<(str) 135 | write(str) 136 | self 137 | end 138 | 139 | def eof? 140 | _check_readable 141 | begin 142 | buf = _read_buf 143 | return buf.size == 0 144 | rescue EOFError 145 | return true 146 | end 147 | end 148 | alias_method :eof, :eof? 149 | 150 | def pos 151 | raise IOError if closed? 152 | sysseek(0, SEEK_CUR) - @buf.length 153 | end 154 | alias_method :tell, :pos 155 | 156 | def pos=(i) 157 | seek(i, SEEK_SET) 158 | end 159 | 160 | def rewind 161 | seek(0, SEEK_SET) 162 | end 163 | 164 | def seek(i, whence = SEEK_SET) 165 | raise IOError if closed? 166 | sysseek(i, whence) 167 | @buf = '' 168 | 0 169 | end 170 | 171 | def _read_buf 172 | return @buf if @buf && @buf.size > 0 173 | @buf = sysread(BUF_SIZE) 174 | end 175 | 176 | def ungetc(substr) 177 | raise TypeError.new "expect String, got #{substr.class}" unless substr.is_a?(String) 178 | if @buf.empty? 179 | @buf = substr.dup 180 | else 181 | @buf = substr + @buf 182 | end 183 | nil 184 | end 185 | 186 | def read(length = nil, outbuf = "") 187 | unless length.nil? 188 | unless length.is_a? Fixnum 189 | raise TypeError.new "can't convert #{length.class} into Integer" 190 | end 191 | if length < 0 192 | raise ArgumentError.new "negative length: #{length} given" 193 | end 194 | if length == 0 195 | return "" # easy case 196 | end 197 | end 198 | 199 | array = [] 200 | while 1 201 | begin 202 | _read_buf 203 | rescue EOFError 204 | array = nil if array.empty? and (not length.nil?) and length != 0 205 | break 206 | end 207 | 208 | if length 209 | consume = (length <= @buf.size) ? length : @buf.size 210 | array.push @buf[0, consume] 211 | @buf = @buf[consume, @buf.size - consume] 212 | length -= consume 213 | break if length == 0 214 | else 215 | array.push @buf 216 | @buf = '' 217 | end 218 | end 219 | 220 | if array.nil? 221 | outbuf.replace("") 222 | nil 223 | else 224 | outbuf.replace(array.join) 225 | end 226 | end 227 | 228 | def readline(arg = $/, limit = nil) 229 | case arg 230 | when String 231 | rs = arg 232 | when Fixnum 233 | rs = $/ 234 | limit = arg 235 | else 236 | raise ArgumentError 237 | end 238 | 239 | if rs.nil? 240 | return read 241 | end 242 | 243 | if rs == "" 244 | rs = $/ + $/ 245 | end 246 | 247 | array = [] 248 | while 1 249 | begin 250 | _read_buf 251 | rescue EOFError 252 | array = nil if array.empty? 253 | break 254 | end 255 | 256 | if limit && limit <= @buf.size 257 | array.push @buf[0, limit] 258 | @buf = @buf[limit, @buf.size - limit] 259 | break 260 | elsif idx = @buf.index(rs) 261 | len = idx + rs.size 262 | array.push @buf[0, len] 263 | @buf = @buf[len, @buf.size - len] 264 | break 265 | else 266 | array.push @buf 267 | @buf = '' 268 | end 269 | end 270 | 271 | raise EOFError.new "end of file reached" if array.nil? 272 | 273 | array.join 274 | end 275 | 276 | def gets(*args) 277 | begin 278 | readline(*args) 279 | rescue EOFError 280 | nil 281 | end 282 | end 283 | 284 | def readchar 285 | _read_buf 286 | c = @buf[0] 287 | @buf = @buf[1, @buf.size] 288 | c 289 | end 290 | 291 | def getc 292 | begin 293 | readchar 294 | rescue EOFError 295 | nil 296 | end 297 | end 298 | 299 | # 15.2.20.5.3 300 | def each(&block) 301 | while line = self.gets 302 | block.call(line) 303 | end 304 | self 305 | end 306 | 307 | # 15.2.20.5.4 308 | def each_byte(&block) 309 | while char = self.getc 310 | block.call(char) 311 | end 312 | self 313 | end 314 | 315 | # 15.2.20.5.5 316 | alias each_line each 317 | 318 | alias each_char each_byte 319 | 320 | def readlines 321 | ary = [] 322 | while (line = gets) 323 | ary << line 324 | end 325 | ary 326 | end 327 | 328 | def puts(*args) 329 | i = 0 330 | len = args.size 331 | while i < len 332 | s = args[i].to_s 333 | write s 334 | write "\n" if (s[-1] != "\n") 335 | i += 1 336 | end 337 | write "\n" if len == 0 338 | nil 339 | end 340 | 341 | def print(*args) 342 | i = 0 343 | len = args.size 344 | while i < len 345 | write args[i].to_s 346 | i += 1 347 | end 348 | end 349 | 350 | def printf(*args) 351 | write sprintf(*args) 352 | nil 353 | end 354 | 355 | alias_method :to_i, :fileno 356 | alias_method :tty?, :isatty 357 | end 358 | 359 | STDIN = IO.open(0, "r") 360 | STDOUT = IO.open(1, "w") 361 | STDERR = IO.open(2, "w") 362 | 363 | $stdin = STDIN 364 | $stdout = STDOUT 365 | $stderr = STDERR 366 | 367 | module Kernel 368 | def print(*args) 369 | $stdout.print(*args) 370 | end 371 | 372 | def puts(*args) 373 | $stdout.puts(*args) 374 | end 375 | 376 | def printf(*args) 377 | $stdout.printf(*args) 378 | end 379 | 380 | def gets(*args) 381 | $stdin.gets(*args) 382 | end 383 | 384 | def getc(*args) 385 | $stdin.getc(*args) 386 | end 387 | end 388 | -------------------------------------------------------------------------------- /mrblib/kernel.rb: -------------------------------------------------------------------------------- 1 | module Kernel 2 | def `(cmd) 3 | IO.popen(cmd) { |io| io.read } 4 | end 5 | 6 | def open(file, *rest, &block) 7 | raise ArgumentError unless file.is_a?(String) 8 | 9 | if file[0] == "|" 10 | IO.popen(file[1..-1], *rest, &block) 11 | else 12 | File.open(file, *rest, &block) 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /run_test.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # 3 | # mrbgems test runner 4 | # 5 | 6 | if __FILE__ == $0 7 | repository, dir = 'https://github.com/mruby/mruby.git', 'tmp/mruby' 8 | build_args = ARGV 9 | 10 | Dir.mkdir 'tmp' unless File.exist?('tmp') 11 | unless File.exist?(dir) 12 | system "git clone #{repository} #{dir}" 13 | end 14 | 15 | exit system(%Q[cd #{dir}; MRUBY_CONFIG=#{File.expand_path __FILE__} ruby minirake #{build_args.join(' ')}]) 16 | end 17 | 18 | MRuby::Build.new do |conf| 19 | toolchain :gcc 20 | conf.gembox 'default' 21 | 22 | conf.gem :git => 'https://github.com/iij/mruby-env.git' 23 | conf.enable_test 24 | 25 | conf.gem File.expand_path(File.dirname(__FILE__)) 26 | end 27 | -------------------------------------------------------------------------------- /src/file.c: -------------------------------------------------------------------------------- 1 | /* 2 | ** file.c - File class 3 | */ 4 | 5 | #include "mruby.h" 6 | #include "mruby/class.h" 7 | #include "mruby/data.h" 8 | #include "mruby/string.h" 9 | #include "mruby/ext/io.h" 10 | 11 | #if MRUBY_RELEASE_NO < 10000 12 | #include "error.h" 13 | #else 14 | #include "mruby/error.h" 15 | #endif 16 | 17 | #include 18 | #include 19 | 20 | #include 21 | #include 22 | 23 | #include 24 | #include 25 | #include 26 | #if defined(_WIN32) || defined(_WIN64) 27 | #define NULL_FILE "NUL" 28 | #define UNLINK _unlink 29 | #define GETCWD _getcwd 30 | #define CHMOD(a, b) 0 31 | #define MAXPATHLEN 1024 32 | #if !defined(PATH_MAX) 33 | #define PATH_MAX _MAX_PATH 34 | #endif 35 | #define realpath(N,R) _fullpath((R),(N),_MAX_PATH) 36 | #include 37 | #else 38 | #define NULL_FILE "/dev/null" 39 | #include 40 | #define UNLINK unlink 41 | #define GETCWD getcwd 42 | #define CHMOD(a, b) chmod(a,b) 43 | #include 44 | #include 45 | #include 46 | #include 47 | #endif 48 | 49 | #define FILE_SEPARATOR "/" 50 | 51 | #if defined(_WIN32) || defined(_WIN64) 52 | #define PATH_SEPARATOR ";" 53 | #define FILE_ALT_SEPARATOR "\\" 54 | #else 55 | #define PATH_SEPARATOR ":" 56 | #endif 57 | 58 | #ifndef LOCK_SH 59 | #define LOCK_SH 1 60 | #endif 61 | #ifndef LOCK_EX 62 | #define LOCK_EX 2 63 | #endif 64 | #ifndef LOCK_NB 65 | #define LOCK_NB 4 66 | #endif 67 | #ifndef LOCK_UN 68 | #define LOCK_UN 8 69 | #endif 70 | 71 | #define STAT(p, s) stat(p, s) 72 | 73 | 74 | mrb_value 75 | mrb_file_s_umask(mrb_state *mrb, mrb_value klass) 76 | { 77 | #if defined(_WIN32) || defined(_WIN64) 78 | /* nothing to do on windows */ 79 | return mrb_fixnum_value(0); 80 | 81 | #else 82 | mrb_int mask, omask; 83 | if (mrb_get_args(mrb, "|i", &mask) == 0) { 84 | omask = umask(0); 85 | umask(omask); 86 | } else { 87 | omask = umask(mask); 88 | } 89 | return mrb_fixnum_value(omask); 90 | #endif 91 | } 92 | 93 | static mrb_value 94 | mrb_file_s_unlink(mrb_state *mrb, mrb_value obj) 95 | { 96 | mrb_value *argv; 97 | mrb_value pathv; 98 | mrb_int argc, i; 99 | const char *path; 100 | 101 | mrb_get_args(mrb, "*", &argv, &argc); 102 | for (i = 0; i < argc; i++) { 103 | pathv = mrb_convert_type(mrb, argv[i], MRB_TT_STRING, "String", "to_str"); 104 | path = mrb_string_value_cstr(mrb, &pathv); 105 | if (UNLINK(path) < 0) { 106 | mrb_sys_fail(mrb, path); 107 | } 108 | } 109 | return mrb_fixnum_value(argc); 110 | } 111 | 112 | static mrb_value 113 | mrb_file_s_rename(mrb_state *mrb, mrb_value obj) 114 | { 115 | mrb_value from, to; 116 | const char *src, *dst; 117 | 118 | mrb_get_args(mrb, "SS", &from, &to); 119 | src = mrb_string_value_cstr(mrb, &from); 120 | dst = mrb_string_value_cstr(mrb, &to); 121 | if (rename(src, dst) < 0) { 122 | #if defined(_WIN32) || defined(_WIN64) 123 | if (CHMOD(dst, 0666) == 0 && UNLINK(dst) == 0 && rename(src, dst) == 0) { 124 | return mrb_fixnum_value(0); 125 | } 126 | #endif 127 | mrb_sys_fail(mrb, mrb_str_to_cstr(mrb, mrb_format(mrb, "(%S, %S)", from, to))); 128 | } 129 | return mrb_fixnum_value(0); 130 | } 131 | 132 | static mrb_value 133 | mrb_file_dirname(mrb_state *mrb, mrb_value klass) 134 | { 135 | #if defined(_WIN32) || defined(_WIN64) 136 | char dname[_MAX_DIR], vname[_MAX_DRIVE]; 137 | char buffer[_MAX_DRIVE + _MAX_DIR]; 138 | char *path; 139 | size_t ridx; 140 | mrb_value s; 141 | mrb_get_args(mrb, "S", &s); 142 | path = mrb_str_to_cstr(mrb, s); 143 | _splitpath((const char*)path, vname, dname, NULL, NULL); 144 | snprintf(buffer, _MAX_DRIVE + _MAX_DIR, "%s%s", vname, dname); 145 | ridx = strlen(buffer); 146 | if (ridx == 0) { 147 | strncpy(buffer, ".", 2); /* null terminated */ 148 | } else if (ridx > 1) { 149 | ridx--; 150 | while (ridx > 0 && (buffer[ridx] == '/' || buffer[ridx] == '\\')) { 151 | buffer[ridx] = '\0'; /* remove last char */ 152 | ridx--; 153 | } 154 | } 155 | return mrb_str_new_cstr(mrb, buffer); 156 | #else 157 | char *dname, *path; 158 | mrb_value s; 159 | mrb_get_args(mrb, "S", &s); 160 | path = mrb_str_to_cstr(mrb, s); 161 | 162 | if ((dname = dirname(path)) == NULL) { 163 | mrb_sys_fail(mrb, "dirname"); 164 | } 165 | #endif 166 | return mrb_str_new_cstr(mrb, dname); 167 | } 168 | 169 | static mrb_value 170 | mrb_file_basename(mrb_state *mrb, mrb_value klass) 171 | { 172 | #if defined(_WIN32) || defined(_WIN64) 173 | char bname[_MAX_DIR]; 174 | char extname[_MAX_EXT]; 175 | char *path; 176 | size_t ridx; 177 | char buffer[_MAX_DIR + _MAX_EXT]; 178 | mrb_value s; 179 | mrb_get_args(mrb, "S", &s); 180 | path = mrb_str_to_cstr(mrb, s); 181 | ridx = strlen(path); 182 | if (ridx > 0) { 183 | ridx--; 184 | while (ridx > 0 && (path[ridx] == '/' || path[ridx] == '\\')) { 185 | path[ridx] = '\0'; 186 | ridx--; 187 | } 188 | if (strncmp(path, "/", 2) == 0) { 189 | return mrb_str_new_cstr(mrb, path); 190 | } 191 | } 192 | _splitpath((const char*)path, NULL, NULL, bname, extname); 193 | snprintf(buffer, _MAX_DIR + _MAX_EXT, "%s%s", bname, extname); 194 | return mrb_str_new_cstr(mrb, buffer); 195 | #else 196 | char *bname, *path; 197 | mrb_value s; 198 | mrb_get_args(mrb, "S", &s); 199 | path = mrb_str_to_cstr(mrb, s); 200 | if ((bname = basename(path)) == NULL) { 201 | mrb_sys_fail(mrb, "basename"); 202 | } 203 | return mrb_str_new_cstr(mrb, bname); 204 | #endif 205 | } 206 | 207 | static mrb_value 208 | mrb_file_realpath(mrb_state *mrb, mrb_value klass) 209 | { 210 | mrb_value pathname, dir_string, s, result; 211 | int argc; 212 | char *cpath; 213 | 214 | argc = mrb_get_args(mrb, "S|S", &pathname, &dir_string); 215 | if (argc == 2) { 216 | s = mrb_str_dup(mrb, dir_string); 217 | s = mrb_str_append(mrb, s, mrb_str_new_cstr(mrb, FILE_SEPARATOR)); 218 | s = mrb_str_append(mrb, s, pathname); 219 | pathname = s; 220 | } 221 | cpath = mrb_str_to_cstr(mrb, pathname); 222 | result = mrb_str_buf_new(mrb, PATH_MAX); 223 | if (realpath(cpath, RSTRING_PTR(result)) == NULL) 224 | mrb_sys_fail(mrb, cpath); 225 | mrb_str_resize(mrb, result, strlen(RSTRING_PTR(result))); 226 | return result; 227 | } 228 | 229 | mrb_value 230 | mrb_file__getwd(mrb_state *mrb, mrb_value klass) 231 | { 232 | mrb_value path; 233 | 234 | path = mrb_str_buf_new(mrb, MAXPATHLEN); 235 | if (GETCWD(RSTRING_PTR(path), MAXPATHLEN) == NULL) { 236 | mrb_sys_fail(mrb, "getcwd(2)"); 237 | } 238 | mrb_str_resize(mrb, path, strlen(RSTRING_PTR(path))); 239 | return path; 240 | } 241 | 242 | static int 243 | mrb_file_is_absolute_path(const char *path) 244 | { 245 | return (path[0] == '/'); 246 | } 247 | 248 | static mrb_value 249 | mrb_file__gethome(mrb_state *mrb, mrb_value klass) 250 | { 251 | #ifndef _WIN32 252 | mrb_value username; 253 | int argc; 254 | char *home; 255 | 256 | argc = mrb_get_args(mrb, "|S", &username); 257 | if (argc == 0) { 258 | home = getenv("HOME"); 259 | if (home == NULL) { 260 | return mrb_nil_value(); 261 | } 262 | if (!mrb_file_is_absolute_path(home)) { 263 | mrb_raise(mrb, E_ARGUMENT_ERROR, "non-absolute home"); 264 | } 265 | } else { 266 | const char *cuser = mrb_str_to_cstr(mrb, username); 267 | struct passwd *pwd = getpwnam(cuser); 268 | if (pwd == NULL) { 269 | return mrb_nil_value(); 270 | } 271 | home = pwd->pw_dir; 272 | if (!mrb_file_is_absolute_path(home)) { 273 | mrb_raisef(mrb, E_ARGUMENT_ERROR, "non-absolute home of ~%S", username); 274 | } 275 | } 276 | return mrb_str_new_cstr(mrb, home); 277 | #else 278 | 279 | return mrb_nil_value(); 280 | #endif 281 | } 282 | 283 | mrb_value 284 | mrb_file_flock(mrb_state *mrb, mrb_value self) 285 | { 286 | #if defined(_WIN32) || defined(_WIN64) || defined(sun) 287 | mrb_raise(mrb, E_NOTIMP_ERROR, "flock is not supported on Illumos/Solaris/Windows"); 288 | #else 289 | mrb_int operation; 290 | int fd; 291 | 292 | mrb_get_args(mrb, "i", &operation); 293 | fd = mrb_fixnum(mrb_io_fileno(mrb, self)); 294 | 295 | while (flock(fd, operation) == -1) { 296 | switch (errno) { 297 | case EINTR: 298 | /* retry */ 299 | break; 300 | case EAGAIN: /* NetBSD */ 301 | #if defined(EWOULDBLOCK) && EWOULDBLOCK != EAGAIN 302 | case EWOULDBLOCK: /* FreeBSD OpenBSD Linux */ 303 | #endif 304 | if (operation & LOCK_NB) { 305 | return mrb_false_value(); 306 | } 307 | /* FALLTHRU - should not happen */ 308 | default: 309 | mrb_sys_fail(mrb, "flock failed"); 310 | break; 311 | } 312 | } 313 | #endif 314 | return mrb_fixnum_value(0); 315 | } 316 | 317 | static mrb_value 318 | mrb_file_s_symlink(mrb_state *mrb, mrb_value klass) 319 | { 320 | #if defined(_WIN32) || defined(_WIN64) 321 | mrb_raise(mrb, E_NOTIMP_ERROR, "symlink is not supported on this platform"); 322 | #else 323 | mrb_value from, to; 324 | const char *src, *dst; 325 | int ai = mrb_gc_arena_save(mrb); 326 | 327 | mrb_get_args(mrb, "SS", &from, &to); 328 | src = mrb_str_to_cstr(mrb, from); 329 | dst = mrb_str_to_cstr(mrb, to); 330 | 331 | if (symlink(src, dst) == -1) { 332 | mrb_sys_fail(mrb, mrb_str_to_cstr(mrb, mrb_format(mrb, "(%S, %S)", from, to))); 333 | } 334 | mrb_gc_arena_restore(mrb, ai); 335 | #endif 336 | return mrb_fixnum_value(0); 337 | } 338 | 339 | static mrb_value 340 | mrb_file_s_chmod(mrb_state *mrb, mrb_value klass) { 341 | mrb_int mode; 342 | mrb_int argc, i; 343 | mrb_value *filenames; 344 | int ai = mrb_gc_arena_save(mrb); 345 | 346 | mrb_get_args(mrb, "i*", &mode, &filenames, &argc); 347 | for (i = 0; i < argc; i++) { 348 | char *path = mrb_str_to_cstr(mrb, filenames[i]); 349 | if (CHMOD(path, mode) == -1) { 350 | mrb_sys_fail(mrb, path); 351 | } 352 | } 353 | 354 | mrb_gc_arena_restore(mrb, ai); 355 | return mrb_fixnum_value(argc); 356 | } 357 | 358 | static mrb_value 359 | mrb_file_s_readlink(mrb_state *mrb, mrb_value klass) { 360 | #if defined(_WIN32) || defined(_WIN64) 361 | mrb_raise(mrb, E_NOTIMP_ERROR, "readlink is not supported on this platform"); 362 | return mrb_nil_value(); // unreachable 363 | #else 364 | char *path, *buf; 365 | size_t bufsize = 100; 366 | ssize_t rc; 367 | mrb_value ret; 368 | int ai = mrb_gc_arena_save(mrb); 369 | 370 | mrb_get_args(mrb, "z", &path); 371 | 372 | buf = (char *)mrb_malloc(mrb, bufsize); 373 | while ((rc = readlink(path, buf, bufsize)) == bufsize && rc != -1) { 374 | bufsize *= 2; 375 | buf = (char *)mrb_realloc(mrb, buf, bufsize); 376 | } 377 | if (rc == -1) { 378 | mrb_free(mrb, buf); 379 | mrb_sys_fail(mrb, path); 380 | } 381 | ret = mrb_str_new(mrb, buf, rc); 382 | mrb_free(mrb, buf); 383 | 384 | mrb_gc_arena_restore(mrb, ai); 385 | return ret; 386 | #endif 387 | } 388 | 389 | void 390 | mrb_init_file(mrb_state *mrb) 391 | { 392 | struct RClass *io, *file, *cnst; 393 | 394 | io = mrb_class_get(mrb, "IO"); 395 | file = mrb_define_class(mrb, "File", io); 396 | MRB_SET_INSTANCE_TT(file, MRB_TT_DATA); 397 | mrb_define_class_method(mrb, file, "umask", mrb_file_s_umask, MRB_ARGS_REQ(1)); 398 | mrb_define_class_method(mrb, file, "delete", mrb_file_s_unlink, MRB_ARGS_ANY()); 399 | mrb_define_class_method(mrb, file, "unlink", mrb_file_s_unlink, MRB_ARGS_ANY()); 400 | mrb_define_class_method(mrb, file, "rename", mrb_file_s_rename, MRB_ARGS_REQ(2)); 401 | mrb_define_class_method(mrb, file, "symlink", mrb_file_s_symlink, MRB_ARGS_REQ(2)); 402 | mrb_define_class_method(mrb, file, "chmod", mrb_file_s_chmod, MRB_ARGS_REQ(1) | MRB_ARGS_REST()); 403 | mrb_define_class_method(mrb, file, "readlink", mrb_file_s_readlink, MRB_ARGS_REQ(1)); 404 | 405 | mrb_define_class_method(mrb, file, "dirname", mrb_file_dirname, MRB_ARGS_REQ(1)); 406 | mrb_define_class_method(mrb, file, "basename", mrb_file_basename, MRB_ARGS_REQ(1)); 407 | mrb_define_class_method(mrb, file, "realpath", mrb_file_realpath, MRB_ARGS_REQ(1)|MRB_ARGS_OPT(1)); 408 | mrb_define_class_method(mrb, file, "_getwd", mrb_file__getwd, MRB_ARGS_NONE()); 409 | mrb_define_class_method(mrb, file, "_gethome", mrb_file__gethome, MRB_ARGS_OPT(1)); 410 | 411 | mrb_define_method(mrb, file, "flock", mrb_file_flock, MRB_ARGS_REQ(1)); 412 | 413 | cnst = mrb_define_module_under(mrb, file, "Constants"); 414 | mrb_define_const(mrb, cnst, "LOCK_SH", mrb_fixnum_value(LOCK_SH)); 415 | mrb_define_const(mrb, cnst, "LOCK_EX", mrb_fixnum_value(LOCK_EX)); 416 | mrb_define_const(mrb, cnst, "LOCK_UN", mrb_fixnum_value(LOCK_UN)); 417 | mrb_define_const(mrb, cnst, "LOCK_NB", mrb_fixnum_value(LOCK_NB)); 418 | mrb_define_const(mrb, cnst, "SEPARATOR", mrb_str_new_cstr(mrb, FILE_SEPARATOR)); 419 | mrb_define_const(mrb, cnst, "PATH_SEPARATOR", mrb_str_new_cstr(mrb, PATH_SEPARATOR)); 420 | #if defined(_WIN32) || defined(_WIN64) 421 | mrb_define_const(mrb, cnst, "ALT_SEPARATOR", mrb_str_new_cstr(mrb, FILE_ALT_SEPARATOR)); 422 | #else 423 | mrb_define_const(mrb, cnst, "ALT_SEPARATOR", mrb_nil_value()); 424 | #endif 425 | mrb_define_const(mrb, cnst, "NULL", mrb_str_new_cstr(mrb, NULL_FILE)); 426 | 427 | } 428 | -------------------------------------------------------------------------------- /src/file_test.c: -------------------------------------------------------------------------------- 1 | /* 2 | ** file.c - File class 3 | */ 4 | 5 | #include "mruby.h" 6 | #include "mruby/class.h" 7 | #include "mruby/data.h" 8 | #include "mruby/string.h" 9 | #include "mruby/ext/io.h" 10 | 11 | #if MRUBY_RELEASE_NO < 10000 12 | #include "error.h" 13 | #else 14 | #include "mruby/error.h" 15 | #endif 16 | 17 | #include 18 | #include 19 | 20 | #if defined(_WIN32) || defined(_WIN64) 21 | #define LSTAT stat 22 | #include 23 | #else 24 | #define LSTAT lstat 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #endif 32 | 33 | #include 34 | 35 | #include 36 | #include 37 | #include 38 | #include 39 | 40 | extern struct mrb_data_type mrb_io_type; 41 | 42 | static int 43 | mrb_stat0(mrb_state *mrb, mrb_value obj, struct stat *st, int do_lstat) 44 | { 45 | mrb_value tmp; 46 | mrb_value io_klass, str_klass; 47 | 48 | io_klass = mrb_obj_value(mrb_class_get(mrb, "IO")); 49 | str_klass = mrb_obj_value(mrb_class_get(mrb, "String")); 50 | 51 | tmp = mrb_funcall(mrb, obj, "is_a?", 1, io_klass); 52 | if (mrb_test(tmp)) { 53 | struct mrb_io *fptr; 54 | fptr = (struct mrb_io *)mrb_get_datatype(mrb, obj, &mrb_io_type); 55 | 56 | if (fptr && fptr->fd >= 0) { 57 | return fstat(fptr->fd, st); 58 | } 59 | 60 | mrb_raise(mrb, E_IO_ERROR, "closed stream"); 61 | return -1; 62 | } 63 | 64 | tmp = mrb_funcall(mrb, obj, "is_a?", 1, str_klass); 65 | if (mrb_test(tmp)) { 66 | if (do_lstat) { 67 | return LSTAT(mrb_str_to_cstr(mrb, obj), st); 68 | } else { 69 | return stat(mrb_str_to_cstr(mrb, obj), st); 70 | } 71 | } 72 | 73 | return -1; 74 | } 75 | 76 | static int 77 | mrb_stat(mrb_state *mrb, mrb_value obj, struct stat *st) 78 | { 79 | return mrb_stat0(mrb, obj, st, 0); 80 | } 81 | 82 | static int 83 | mrb_lstat(mrb_state *mrb, mrb_value obj, struct stat *st) 84 | { 85 | return mrb_stat0(mrb, obj, st, 1); 86 | } 87 | 88 | /* 89 | * Document-method: directory? 90 | * 91 | * call-seq: 92 | * File.directory?(file_name) -> true or false 93 | * 94 | * Returns true if the named file is a directory, 95 | * or a symlink that points at a directory, and false 96 | * otherwise. 97 | * 98 | * File.directory?(".") 99 | */ 100 | 101 | mrb_value 102 | mrb_filetest_s_directory_p(mrb_state *mrb, mrb_value klass) 103 | { 104 | #ifndef S_ISDIR 105 | # define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) 106 | #endif 107 | 108 | struct stat st; 109 | mrb_value obj; 110 | 111 | mrb_get_args(mrb, "o", &obj); 112 | 113 | if (mrb_stat(mrb, obj, &st) < 0) 114 | return mrb_false_value(); 115 | if (S_ISDIR(st.st_mode)) 116 | return mrb_true_value(); 117 | 118 | return mrb_false_value(); 119 | } 120 | 121 | /* 122 | * call-seq: 123 | * File.pipe?(file_name) -> true or false 124 | * 125 | * Returns true if the named file is a pipe. 126 | */ 127 | 128 | mrb_value 129 | mrb_filetest_s_pipe_p(mrb_state *mrb, mrb_value klass) 130 | { 131 | #if defined(_WIN32) || defined(_WIN64) 132 | mrb_raise(mrb, E_NOTIMP_ERROR, "pipe is not supported on this platform"); 133 | #else 134 | #ifdef S_IFIFO 135 | # ifndef S_ISFIFO 136 | # define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO) 137 | # endif 138 | 139 | struct stat st; 140 | mrb_value obj; 141 | 142 | mrb_get_args(mrb, "o", &obj); 143 | 144 | if (mrb_stat(mrb, obj, &st) < 0) 145 | return mrb_false_value(); 146 | if (S_ISFIFO(st.st_mode)) 147 | return mrb_true_value(); 148 | 149 | #endif 150 | return mrb_false_value(); 151 | #endif 152 | } 153 | 154 | /* 155 | * call-seq: 156 | * File.symlink?(file_name) -> true or false 157 | * 158 | * Returns true if the named file is a symbolic link. 159 | */ 160 | 161 | mrb_value 162 | mrb_filetest_s_symlink_p(mrb_state *mrb, mrb_value klass) 163 | { 164 | #if defined(_WIN32) || defined(_WIN64) 165 | mrb_raise(mrb, E_NOTIMP_ERROR, "symlink is not supported on this platform"); 166 | #else 167 | #ifndef S_ISLNK 168 | # ifdef _S_ISLNK 169 | # define S_ISLNK(m) _S_ISLNK(m) 170 | # else 171 | # ifdef _S_IFLNK 172 | # define S_ISLNK(m) (((m) & S_IFMT) == _S_IFLNK) 173 | # else 174 | # ifdef S_IFLNK 175 | # define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK) 176 | # endif 177 | # endif 178 | # endif 179 | #endif 180 | 181 | #ifdef S_ISLNK 182 | struct stat st; 183 | mrb_value obj; 184 | 185 | mrb_get_args(mrb, "o", &obj); 186 | 187 | if (mrb_lstat(mrb, obj, &st) == -1) 188 | return mrb_false_value(); 189 | if (S_ISLNK(st.st_mode)) 190 | return mrb_true_value(); 191 | #endif 192 | 193 | return mrb_false_value(); 194 | #endif 195 | } 196 | 197 | /* 198 | * call-seq: 199 | * File.socket?(file_name) -> true or false 200 | * 201 | * Returns true if the named file is a socket. 202 | */ 203 | 204 | mrb_value 205 | mrb_filetest_s_socket_p(mrb_state *mrb, mrb_value klass) 206 | { 207 | #if defined(_WIN32) || defined(_WIN64) 208 | mrb_raise(mrb, E_NOTIMP_ERROR, "socket is not supported on this platform"); 209 | #else 210 | #ifndef S_ISSOCK 211 | # ifdef _S_ISSOCK 212 | # define S_ISSOCK(m) _S_ISSOCK(m) 213 | # else 214 | # ifdef _S_IFSOCK 215 | # define S_ISSOCK(m) (((m) & S_IFMT) == _S_IFSOCK) 216 | # else 217 | # ifdef S_IFSOCK 218 | # define S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK) 219 | # endif 220 | # endif 221 | # endif 222 | #endif 223 | 224 | #ifdef S_ISSOCK 225 | struct stat st; 226 | mrb_value obj; 227 | 228 | mrb_get_args(mrb, "o", &obj); 229 | 230 | if (mrb_stat(mrb, obj, &st) < 0) 231 | return mrb_false_value(); 232 | if (S_ISSOCK(st.st_mode)) 233 | return mrb_true_value(); 234 | #endif 235 | 236 | return mrb_false_value(); 237 | #endif 238 | } 239 | 240 | /* 241 | * call-seq: 242 | * File.exist?(file_name) -> true or false 243 | * File.exists?(file_name) -> true or false 244 | * 245 | * Return true if the named file exists. 246 | */ 247 | 248 | mrb_value 249 | mrb_filetest_s_exist_p(mrb_state *mrb, mrb_value klass) 250 | { 251 | struct stat st; 252 | mrb_value obj; 253 | 254 | mrb_get_args(mrb, "o", &obj); 255 | if (mrb_stat(mrb, obj, &st) < 0) 256 | return mrb_false_value(); 257 | 258 | return mrb_true_value(); 259 | } 260 | 261 | /* 262 | * call-seq: 263 | * File.file?(file_name) -> true or false 264 | * 265 | * Returns true if the named file exists and is a 266 | * regular file. 267 | */ 268 | 269 | mrb_value 270 | mrb_filetest_s_file_p(mrb_state *mrb, mrb_value klass) 271 | { 272 | #ifndef S_ISREG 273 | # define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) 274 | #endif 275 | 276 | struct stat st; 277 | mrb_value obj; 278 | 279 | mrb_get_args(mrb, "o", &obj); 280 | 281 | if (mrb_stat(mrb, obj, &st) < 0) 282 | return mrb_false_value(); 283 | if (S_ISREG(st.st_mode)) 284 | return mrb_true_value(); 285 | 286 | return mrb_false_value(); 287 | } 288 | 289 | /* 290 | * call-seq: 291 | * File.zero?(file_name) -> true or false 292 | * 293 | * Returns true if the named file exists and has 294 | * a zero size. 295 | */ 296 | 297 | mrb_value 298 | mrb_filetest_s_zero_p(mrb_state *mrb, mrb_value klass) 299 | { 300 | struct stat st; 301 | mrb_value obj; 302 | 303 | mrb_get_args(mrb, "o", &obj); 304 | 305 | if (mrb_stat(mrb, obj, &st) < 0) 306 | return mrb_false_value(); 307 | if (st.st_size == 0) 308 | return mrb_true_value(); 309 | 310 | return mrb_false_value(); 311 | } 312 | 313 | /* 314 | * call-seq: 315 | * File.size(file_name) -> integer 316 | * 317 | * Returns the size of file_name. 318 | * 319 | * _file_name_ can be an IO object. 320 | */ 321 | 322 | mrb_value 323 | mrb_filetest_s_size(mrb_state *mrb, mrb_value klass) 324 | { 325 | struct stat st; 326 | mrb_value obj; 327 | 328 | mrb_get_args(mrb, "o", &obj); 329 | 330 | if (mrb_stat(mrb, obj, &st) < 0) 331 | mrb_sys_fail(mrb, "mrb_stat"); 332 | 333 | return mrb_fixnum_value(st.st_size); 334 | } 335 | 336 | /* 337 | * call-seq: 338 | * File.size?(file_name) -> Integer or nil 339 | * 340 | * Returns +nil+ if +file_name+ doesn't exist or has zero size, the size of the 341 | * file otherwise. 342 | */ 343 | 344 | mrb_value 345 | mrb_filetest_s_size_p(mrb_state *mrb, mrb_value klass) 346 | { 347 | struct stat st; 348 | mrb_value obj; 349 | 350 | mrb_get_args(mrb, "o", &obj); 351 | 352 | if (mrb_stat(mrb, obj, &st) < 0) 353 | return mrb_nil_value(); 354 | if (st.st_size == 0) 355 | return mrb_nil_value(); 356 | 357 | return mrb_fixnum_value(st.st_size); 358 | } 359 | 360 | void 361 | mrb_init_file_test(mrb_state *mrb) 362 | { 363 | struct RClass *f; 364 | 365 | f = mrb_define_class(mrb, "FileTest", mrb->object_class); 366 | 367 | mrb_define_class_method(mrb, f, "directory?", mrb_filetest_s_directory_p, MRB_ARGS_REQ(1)); 368 | mrb_define_class_method(mrb, f, "exist?", mrb_filetest_s_exist_p, MRB_ARGS_REQ(1)); 369 | mrb_define_class_method(mrb, f, "exists?", mrb_filetest_s_exist_p, MRB_ARGS_REQ(1)); 370 | mrb_define_class_method(mrb, f, "file?", mrb_filetest_s_file_p, MRB_ARGS_REQ(1)); 371 | mrb_define_class_method(mrb, f, "pipe?", mrb_filetest_s_pipe_p, MRB_ARGS_REQ(1)); 372 | mrb_define_class_method(mrb, f, "size", mrb_filetest_s_size, MRB_ARGS_REQ(1)); 373 | mrb_define_class_method(mrb, f, "size?", mrb_filetest_s_size_p, MRB_ARGS_REQ(1)); 374 | mrb_define_class_method(mrb, f, "socket?", mrb_filetest_s_socket_p, MRB_ARGS_REQ(1)); 375 | mrb_define_class_method(mrb, f, "symlink?", mrb_filetest_s_symlink_p, MRB_ARGS_REQ(1)); 376 | mrb_define_class_method(mrb, f, "zero?", mrb_filetest_s_zero_p, MRB_ARGS_REQ(1)); 377 | } 378 | -------------------------------------------------------------------------------- /src/io.c: -------------------------------------------------------------------------------- 1 | /* 2 | ** io.c - IO class 3 | */ 4 | 5 | #include "mruby.h" 6 | #include "mruby/array.h" 7 | #include "mruby/class.h" 8 | #include "mruby/data.h" 9 | #include "mruby/hash.h" 10 | #include "mruby/string.h" 11 | #include "mruby/variable.h" 12 | #include "mruby/ext/io.h" 13 | 14 | #if MRUBY_RELEASE_NO < 10000 15 | #include "error.h" 16 | #else 17 | #include "mruby/error.h" 18 | #endif 19 | 20 | #include 21 | #include 22 | 23 | #if defined(_WIN32) || defined(_WIN64) 24 | #include 25 | #include 26 | #define open _open 27 | #define close _close 28 | #define read _read 29 | #define write _write 30 | #define lseek _lseek 31 | #else 32 | #include 33 | #include 34 | #endif 35 | 36 | #include 37 | 38 | #include 39 | #include 40 | #include 41 | 42 | 43 | static void mrb_io_free(mrb_state *mrb, void *ptr); 44 | struct mrb_data_type mrb_io_type = { "IO", mrb_io_free }; 45 | 46 | 47 | static struct mrb_io *io_get_open_fptr(mrb_state *mrb, mrb_value self); 48 | static int mrb_io_modestr_to_flags(mrb_state *mrb, const char *modestr); 49 | static int mrb_io_flags_to_modenum(mrb_state *mrb, int flags); 50 | static void fptr_finalize(mrb_state *mrb, struct mrb_io *fptr, int quiet); 51 | 52 | #if MRUBY_RELEASE_NO < 10000 53 | static struct RClass * 54 | mrb_module_get(mrb_state *mrb, const char *name) 55 | { 56 | return mrb_class_get(mrb, name); 57 | } 58 | #endif 59 | 60 | static struct mrb_io * 61 | io_get_open_fptr(mrb_state *mrb, mrb_value self) 62 | { 63 | struct mrb_io *fptr; 64 | 65 | fptr = (struct mrb_io *)mrb_get_datatype(mrb, self, &mrb_io_type); 66 | if (fptr->fd < 0) { 67 | mrb_raise(mrb, E_IO_ERROR, "closed stream."); 68 | } 69 | return fptr; 70 | } 71 | 72 | #if !defined(_WIN32) && !defined(_WIN64) 73 | static void 74 | io_set_process_status(mrb_state *mrb, pid_t pid, int status) 75 | { 76 | struct RClass *c_process, *c_status; 77 | mrb_value v; 78 | 79 | c_status = NULL; 80 | if (mrb_class_defined(mrb, "Process")) { 81 | c_process = mrb_module_get(mrb, "Process"); 82 | if (mrb_const_defined(mrb, mrb_obj_value(c_process), mrb_intern_cstr(mrb, "Status"))) { 83 | c_status = mrb_class_get_under(mrb, c_process, "Status"); 84 | } 85 | } 86 | if (c_status != NULL) { 87 | v = mrb_funcall(mrb, mrb_obj_value(c_status), "new", 2, mrb_fixnum_value(pid), mrb_fixnum_value(status)); 88 | } else { 89 | v = mrb_fixnum_value(WEXITSTATUS(status)); 90 | } 91 | mrb_gv_set(mrb, mrb_intern_cstr(mrb, "$?"), v); 92 | } 93 | #endif 94 | 95 | static int 96 | mrb_io_modestr_to_flags(mrb_state *mrb, const char *mode) 97 | { 98 | int flags = 0; 99 | const char *m = mode; 100 | 101 | switch (*m++) { 102 | case 'r': 103 | flags |= FMODE_READABLE; 104 | break; 105 | case 'w': 106 | flags |= FMODE_WRITABLE | FMODE_CREATE | FMODE_TRUNC; 107 | break; 108 | case 'a': 109 | flags |= FMODE_WRITABLE | FMODE_APPEND | FMODE_CREATE; 110 | break; 111 | default: 112 | mrb_raisef(mrb, E_ARGUMENT_ERROR, "illegal access mode %S", mrb_str_new_cstr(mrb, mode)); 113 | } 114 | 115 | while (*m) { 116 | switch (*m++) { 117 | case 'b': 118 | flags |= FMODE_BINMODE; 119 | break; 120 | case '+': 121 | flags |= FMODE_READWRITE; 122 | break; 123 | case ':': 124 | /* XXX: PASSTHROUGH*/ 125 | default: 126 | mrb_raisef(mrb, E_ARGUMENT_ERROR, "illegal access mode %S", mrb_str_new_cstr(mrb, mode)); 127 | } 128 | } 129 | 130 | return flags; 131 | } 132 | 133 | static int 134 | mrb_io_flags_to_modenum(mrb_state *mrb, int flags) 135 | { 136 | int modenum = 0; 137 | 138 | switch(flags & (FMODE_READABLE|FMODE_WRITABLE|FMODE_READWRITE)) { 139 | case FMODE_READABLE: 140 | modenum = O_RDONLY; 141 | break; 142 | case FMODE_WRITABLE: 143 | modenum = O_WRONLY; 144 | break; 145 | case FMODE_READWRITE: 146 | modenum = O_RDWR; 147 | break; 148 | } 149 | 150 | if (flags & FMODE_APPEND) { 151 | modenum |= O_APPEND; 152 | } 153 | if (flags & FMODE_TRUNC) { 154 | modenum |= O_TRUNC; 155 | } 156 | if (flags & FMODE_CREATE) { 157 | modenum |= O_CREAT; 158 | } 159 | #ifdef O_BINARY 160 | if (flags & FMODE_BINMODE) { 161 | modenum |= O_BINARY; 162 | } 163 | #endif 164 | 165 | return modenum; 166 | } 167 | 168 | void 169 | mrb_fd_cloexec(mrb_state *mrb, int fd) 170 | { 171 | #if defined(F_GETFD) && defined(F_SETFD) && defined(FD_CLOEXEC) 172 | int flags, flags2; 173 | 174 | flags = fcntl(fd, F_GETFD); 175 | if (flags == -1) { 176 | mrb_sys_fail(mrb, "fcntl"); 177 | } 178 | if (fd <= 2) { 179 | flags2 = flags & ~FD_CLOEXEC; /* Clear CLOEXEC for standard file descriptors: 0, 1, 2. */ 180 | } 181 | else { 182 | flags2 = flags | FD_CLOEXEC; /* Set CLOEXEC for non-standard file descriptors: 3, 4, 5, ... */ 183 | } 184 | if (flags != flags2) { 185 | if (fcntl(fd, F_SETFD, flags2) == -1) { 186 | mrb_sys_fail(mrb, "fcntl"); 187 | } 188 | } 189 | #endif 190 | } 191 | 192 | #ifndef _WIN32 193 | static int 194 | mrb_cloexec_pipe(mrb_state *mrb, int fildes[2]) 195 | { 196 | int ret; 197 | ret = pipe(fildes); 198 | if (ret == -1) 199 | return -1; 200 | mrb_fd_cloexec(mrb, fildes[0]); 201 | mrb_fd_cloexec(mrb, fildes[1]); 202 | return ret; 203 | } 204 | 205 | static int 206 | mrb_pipe(mrb_state *mrb, int pipes[2]) 207 | { 208 | int ret; 209 | ret = mrb_cloexec_pipe(mrb, pipes); 210 | if (ret == -1) { 211 | if (errno == EMFILE || errno == ENFILE) { 212 | mrb_garbage_collect(mrb); 213 | ret = mrb_cloexec_pipe(mrb, pipes); 214 | } 215 | } 216 | return ret; 217 | } 218 | 219 | static int 220 | mrb_proc_exec(const char *pname) 221 | { 222 | const char *s; 223 | s = pname; 224 | 225 | while (*s == ' ' || *s == '\t' || *s == '\n') 226 | s++; 227 | 228 | if (!*s) { 229 | errno = ENOENT; 230 | return -1; 231 | } 232 | 233 | execl("/bin/sh", "sh", "-c", pname, (char *)NULL); 234 | return -1; 235 | } 236 | #endif 237 | 238 | static void 239 | mrb_io_free(mrb_state *mrb, void *ptr) 240 | { 241 | struct mrb_io *io = (struct mrb_io *)ptr; 242 | if (io != NULL) { 243 | fptr_finalize(mrb, io, TRUE); 244 | mrb_free(mrb, io); 245 | } 246 | } 247 | 248 | static struct mrb_io * 249 | mrb_io_alloc(mrb_state *mrb) 250 | { 251 | struct mrb_io *fptr; 252 | 253 | fptr = (struct mrb_io *)mrb_malloc(mrb, sizeof(struct mrb_io)); 254 | fptr->fd = -1; 255 | fptr->fd2 = -1; 256 | fptr->pid = 0; 257 | fptr->readable = 0; 258 | fptr->writable = 0; 259 | fptr->sync = 0; 260 | return fptr; 261 | } 262 | 263 | #ifndef NOFILE 264 | #define NOFILE 64 265 | #endif 266 | 267 | #ifndef _WIN32 268 | static int 269 | option_to_fd(mrb_state *mrb, mrb_value obj, const char *key) 270 | { 271 | mrb_value opt = mrb_funcall(mrb, obj, "[]", 1, mrb_symbol_value(mrb_intern_static(mrb, key, strlen(key)))); 272 | if (mrb_nil_p(opt)) { 273 | return -1; 274 | } 275 | 276 | switch (mrb_type(opt)) { 277 | case MRB_TT_DATA: /* IO */ 278 | return mrb_fixnum(mrb_io_fileno(mrb, opt)); 279 | case MRB_TT_FIXNUM: 280 | return mrb_fixnum(opt); 281 | default: 282 | mrb_raise(mrb, E_ARGUMENT_ERROR, "wrong exec redirect action"); 283 | break; 284 | } 285 | return -1; /* never reached */ 286 | } 287 | 288 | mrb_value 289 | mrb_io_s_popen(mrb_state *mrb, mrb_value klass) 290 | { 291 | mrb_value cmd, io, result; 292 | mrb_value mode = mrb_str_new_cstr(mrb, "r"); 293 | mrb_value opt = mrb_hash_new(mrb); 294 | 295 | struct mrb_io *fptr; 296 | const char *pname; 297 | int pid, flags, fd, write_fd = -1; 298 | int pr[2] = { -1, -1 }; 299 | int pw[2] = { -1, -1 }; 300 | int doexec; 301 | int saved_errno; 302 | int opt_in, opt_out, opt_err; 303 | 304 | mrb_get_args(mrb, "S|SH", &cmd, &mode, &opt); 305 | io = mrb_obj_value(mrb_data_object_alloc(mrb, mrb_class_ptr(klass), NULL, &mrb_io_type)); 306 | 307 | pname = mrb_string_value_cstr(mrb, &cmd); 308 | flags = mrb_io_modestr_to_flags(mrb, mrb_string_value_cstr(mrb, &mode)); 309 | 310 | doexec = (strcmp("-", pname) != 0); 311 | opt_in = option_to_fd(mrb, opt, "in"); 312 | opt_out = option_to_fd(mrb, opt, "out"); 313 | opt_err = option_to_fd(mrb, opt, "err"); 314 | 315 | if (flags & FMODE_READABLE) { 316 | if (pipe(pr) == -1) { 317 | mrb_sys_fail(mrb, "pipe"); 318 | } 319 | mrb_fd_cloexec(mrb, pr[0]); 320 | mrb_fd_cloexec(mrb, pr[1]); 321 | } 322 | 323 | if (flags & FMODE_WRITABLE) { 324 | if (pipe(pw) == -1) { 325 | if (pr[0] != -1) close(pr[0]); 326 | if (pr[1] != -1) close(pr[1]); 327 | mrb_sys_fail(mrb, "pipe"); 328 | } 329 | mrb_fd_cloexec(mrb, pw[0]); 330 | mrb_fd_cloexec(mrb, pw[1]); 331 | } 332 | 333 | if (!doexec) { 334 | // XXX 335 | fflush(stdin); 336 | fflush(stdout); 337 | fflush(stderr); 338 | } 339 | 340 | result = mrb_nil_value(); 341 | switch (pid = fork()) { 342 | case 0: /* child */ 343 | if (opt_in != -1) { 344 | dup2(opt_in, 0); 345 | } 346 | if (opt_out != -1) { 347 | dup2(opt_out, 1); 348 | } 349 | if (opt_err != -1) { 350 | dup2(opt_err, 2); 351 | } 352 | if (flags & FMODE_READABLE) { 353 | close(pr[0]); 354 | if (pr[1] != 1) { 355 | dup2(pr[1], 1); 356 | close(pr[1]); 357 | } 358 | } 359 | if (flags & FMODE_WRITABLE) { 360 | close(pw[1]); 361 | if (pw[0] != 0) { 362 | dup2(pw[0], 0); 363 | close(pw[0]); 364 | } 365 | } 366 | if (doexec) { 367 | for (fd = 3; fd < NOFILE; fd++) { 368 | close(fd); 369 | } 370 | mrb_proc_exec(pname); 371 | mrb_raisef(mrb, E_IO_ERROR, "command not found: %S", cmd); 372 | _exit(127); 373 | } 374 | result = mrb_nil_value(); 375 | break; 376 | 377 | default: /* parent */ 378 | if ((flags & FMODE_READABLE) && (flags & FMODE_WRITABLE)) { 379 | close(pr[1]); 380 | fd = pr[0]; 381 | close(pw[0]); 382 | write_fd = pw[1]; 383 | } else if (flags & FMODE_READABLE) { 384 | close(pr[1]); 385 | fd = pr[0]; 386 | } else { 387 | close(pw[0]); 388 | fd = pw[1]; 389 | } 390 | 391 | mrb_iv_set(mrb, io, mrb_intern_cstr(mrb, "@buf"), mrb_str_new_cstr(mrb, "")); 392 | 393 | fptr = mrb_io_alloc(mrb); 394 | fptr->fd = fd; 395 | fptr->fd2 = write_fd; 396 | fptr->pid = pid; 397 | fptr->readable = ((flags & FMODE_READABLE) != 0); 398 | fptr->writable = ((flags & FMODE_WRITABLE) != 0); 399 | fptr->sync = 0; 400 | 401 | DATA_TYPE(io) = &mrb_io_type; 402 | DATA_PTR(io) = fptr; 403 | result = io; 404 | break; 405 | 406 | case -1: /* error */ 407 | saved_errno = errno; 408 | if (flags & FMODE_READABLE) { 409 | close(pr[0]); 410 | close(pr[1]); 411 | } 412 | if (flags & FMODE_WRITABLE) { 413 | close(pw[0]); 414 | close(pw[1]); 415 | } 416 | errno = saved_errno; 417 | mrb_sys_fail(mrb, "pipe_open failed."); 418 | break; 419 | } 420 | return result; 421 | } 422 | #endif 423 | 424 | mrb_value 425 | mrb_io_initialize(mrb_state *mrb, mrb_value io) 426 | { 427 | struct mrb_io *fptr; 428 | mrb_int fd; 429 | mrb_value mode, opt; 430 | int flags; 431 | 432 | mode = opt = mrb_nil_value(); 433 | 434 | mrb_get_args(mrb, "i|So", &fd, &mode, &opt); 435 | if (mrb_nil_p(mode)) { 436 | mode = mrb_str_new_cstr(mrb, "r"); 437 | } 438 | if (mrb_nil_p(opt)) { 439 | opt = mrb_hash_new(mrb); 440 | } 441 | 442 | flags = mrb_io_modestr_to_flags(mrb, mrb_string_value_cstr(mrb, &mode)); 443 | 444 | mrb_iv_set(mrb, io, mrb_intern_cstr(mrb, "@buf"), mrb_str_new_cstr(mrb, "")); 445 | 446 | fptr = (struct mrb_io *)DATA_PTR(io); 447 | if (fptr != NULL) { 448 | fptr_finalize(mrb, fptr, TRUE); 449 | mrb_free(mrb, fptr); 450 | } 451 | fptr = mrb_io_alloc(mrb); 452 | 453 | DATA_TYPE(io) = &mrb_io_type; 454 | DATA_PTR(io) = fptr; 455 | 456 | fptr->fd = fd; 457 | fptr->readable = ((flags & FMODE_READABLE) != 0); 458 | fptr->writable = ((flags & FMODE_WRITABLE) != 0); 459 | fptr->sync = 0; 460 | return io; 461 | } 462 | 463 | static void 464 | fptr_finalize(mrb_state *mrb, struct mrb_io *fptr, int quiet) 465 | { 466 | int saved_errno = 0; 467 | 468 | if (fptr == NULL) { 469 | return; 470 | } 471 | 472 | if (fptr->fd > 2) { 473 | if (close(fptr->fd) == -1) { 474 | saved_errno = errno; 475 | } 476 | fptr->fd = -1; 477 | } 478 | 479 | if (fptr->fd2 > 2) { 480 | if (close(fptr->fd2) == -1) { 481 | if (saved_errno == 0) { 482 | saved_errno = errno; 483 | } 484 | } 485 | fptr->fd2 = -1; 486 | } 487 | 488 | #if !defined(_WIN32) && !defined(_WIN64) 489 | if (fptr->pid != 0) { 490 | pid_t pid; 491 | int status; 492 | do { 493 | pid = waitpid(fptr->pid, &status, 0); 494 | } while (pid == -1 && errno == EINTR); 495 | if (!quiet && pid == fptr->pid) { 496 | io_set_process_status(mrb, pid, status); 497 | } 498 | fptr->pid = 0; 499 | /* Note: we don't raise an exception when waitpid(3) fails */ 500 | } 501 | #endif 502 | 503 | if (!quiet && saved_errno != 0) { 504 | errno = saved_errno; 505 | mrb_sys_fail(mrb, "fptr_finalize failed."); 506 | } 507 | } 508 | 509 | mrb_value 510 | mrb_io_check_readable(mrb_state *mrb, mrb_value self) 511 | { 512 | struct mrb_io *fptr = io_get_open_fptr(mrb, self); 513 | if (! fptr->readable) { 514 | mrb_raise(mrb, E_IO_ERROR, "not opened for reading"); 515 | } 516 | return mrb_nil_value(); 517 | } 518 | 519 | mrb_value 520 | mrb_io_isatty(mrb_state *mrb, mrb_value self) 521 | { 522 | struct mrb_io *fptr; 523 | 524 | fptr = io_get_open_fptr(mrb, self); 525 | if (isatty(fptr->fd) == 0) 526 | return mrb_false_value(); 527 | return mrb_true_value(); 528 | } 529 | 530 | mrb_value 531 | mrb_io_s_for_fd(mrb_state *mrb, mrb_value klass) 532 | { 533 | struct RClass *c = mrb_class_ptr(klass); 534 | enum mrb_vtype ttype = MRB_INSTANCE_TT(c); 535 | mrb_value obj; 536 | 537 | /* copied from mrb_instance_alloc() */ 538 | if (ttype == 0) ttype = MRB_TT_OBJECT; 539 | obj = mrb_obj_value((struct RObject*)mrb_obj_alloc(mrb, ttype, c)); 540 | return mrb_io_initialize(mrb, obj); 541 | } 542 | 543 | mrb_value 544 | mrb_io_s_sysclose(mrb_state *mrb, mrb_value klass) 545 | { 546 | mrb_int fd; 547 | mrb_get_args(mrb, "i", &fd); 548 | if (close(fd) == -1) { 549 | mrb_sys_fail(mrb, "close"); 550 | } 551 | return mrb_fixnum_value(0); 552 | } 553 | 554 | int 555 | mrb_cloexec_open(mrb_state *mrb, const char *pathname, mrb_int flags, mrb_int mode) 556 | { 557 | mrb_value emsg; 558 | int fd, retry = FALSE; 559 | 560 | #ifdef O_CLOEXEC 561 | /* O_CLOEXEC is available since Linux 2.6.23. Linux 2.6.18 silently ignore it. */ 562 | flags |= O_CLOEXEC; 563 | #elif defined O_NOINHERIT 564 | flags |= O_NOINHERIT; 565 | #endif 566 | reopen: 567 | fd = open(pathname, flags, mode); 568 | if (fd == -1) { 569 | if (!retry) { 570 | switch (errno) { 571 | case ENFILE: 572 | case EMFILE: 573 | mrb_garbage_collect(mrb); 574 | retry = TRUE; 575 | goto reopen; 576 | } 577 | } 578 | 579 | emsg = mrb_format(mrb, "open %S", mrb_str_new_cstr(mrb, pathname)); 580 | mrb_str_modify(mrb, mrb_str_ptr(emsg)); 581 | mrb_sys_fail(mrb, RSTRING_PTR(emsg)); 582 | } 583 | 584 | if (fd <= 2) { 585 | mrb_fd_cloexec(mrb, fd); 586 | } 587 | return fd; 588 | } 589 | 590 | mrb_value 591 | mrb_io_s_sysopen(mrb_state *mrb, mrb_value klass) 592 | { 593 | mrb_value path = mrb_nil_value(); 594 | mrb_value mode = mrb_nil_value(); 595 | mrb_int fd, flags, perm = -1; 596 | const char *pat; 597 | int modenum; 598 | 599 | mrb_get_args(mrb, "S|Si", &path, &mode, &perm); 600 | if (mrb_nil_p(mode)) { 601 | mode = mrb_str_new_cstr(mrb, "r"); 602 | } 603 | if (perm < 0) { 604 | perm = 0666; 605 | } 606 | 607 | pat = mrb_string_value_cstr(mrb, &path); 608 | flags = mrb_io_modestr_to_flags(mrb, mrb_string_value_cstr(mrb, &mode)); 609 | modenum = mrb_io_flags_to_modenum(mrb, flags); 610 | fd = mrb_cloexec_open(mrb, pat, modenum, perm); 611 | return mrb_fixnum_value(fd); 612 | } 613 | 614 | mrb_value 615 | mrb_io_sysread(mrb_state *mrb, mrb_value io) 616 | { 617 | struct mrb_io *fptr; 618 | mrb_value buf = mrb_nil_value(); 619 | mrb_int maxlen; 620 | int ret; 621 | 622 | mrb_get_args(mrb, "i|S", &maxlen, &buf); 623 | if (maxlen < 0) { 624 | mrb_raise(mrb, E_ARGUMENT_ERROR, "negative expanding string size"); 625 | } 626 | else if (maxlen == 0) { 627 | return mrb_str_new(mrb, NULL, maxlen); 628 | } 629 | 630 | if (mrb_nil_p(buf)) { 631 | buf = mrb_str_new(mrb, NULL, maxlen); 632 | } 633 | 634 | if (RSTRING_LEN(buf) != maxlen) { 635 | buf = mrb_str_resize(mrb, buf, maxlen); 636 | } else { 637 | mrb_str_modify(mrb, RSTRING(buf)); 638 | } 639 | 640 | fptr = (struct mrb_io *)io_get_open_fptr(mrb, io); 641 | if (!fptr->readable) { 642 | mrb_raise(mrb, E_IO_ERROR, "not opened for reading"); 643 | } 644 | ret = read(fptr->fd, RSTRING_PTR(buf), maxlen); 645 | switch (ret) { 646 | case 0: /* EOF */ 647 | if (maxlen == 0) { 648 | buf = mrb_str_new_cstr(mrb, ""); 649 | } else { 650 | mrb_raise(mrb, E_EOF_ERROR, "sysread failed: End of File"); 651 | } 652 | break; 653 | case -1: /* Error */ 654 | mrb_sys_fail(mrb, "sysread failed"); 655 | break; 656 | default: 657 | if (RSTRING_LEN(buf) != ret) { 658 | buf = mrb_str_resize(mrb, buf, ret); 659 | } 660 | break; 661 | } 662 | 663 | return buf; 664 | } 665 | 666 | mrb_value 667 | mrb_io_sysseek(mrb_state *mrb, mrb_value io) 668 | { 669 | struct mrb_io *fptr; 670 | off_t pos; 671 | mrb_int offset, whence = -1; 672 | 673 | mrb_get_args(mrb, "i|i", &offset, &whence); 674 | if (whence < 0) { 675 | whence = 0; 676 | } 677 | 678 | fptr = (struct mrb_io *)mrb_get_datatype(mrb, io, &mrb_io_type); 679 | pos = lseek(fptr->fd, offset, whence); 680 | if (pos == -1) { 681 | mrb_sys_fail(mrb, "sysseek"); 682 | } 683 | if (pos > MRB_INT_MAX) { 684 | return mrb_float_value(mrb, (mrb_float)pos); 685 | } else { 686 | return mrb_fixnum_value(pos); 687 | } 688 | } 689 | 690 | mrb_value 691 | mrb_io_syswrite(mrb_state *mrb, mrb_value io) 692 | { 693 | struct mrb_io *fptr; 694 | mrb_value str, buf; 695 | int fd, length; 696 | 697 | fptr = (struct mrb_io *)mrb_get_datatype(mrb, io, &mrb_io_type); 698 | if (! fptr->writable) { 699 | mrb_raise(mrb, E_IO_ERROR, "not opened for writing"); 700 | } 701 | 702 | mrb_get_args(mrb, "S", &str); 703 | if (mrb_type(str) != MRB_TT_STRING) { 704 | buf = mrb_funcall(mrb, str, "to_s", 0); 705 | } else { 706 | buf = str; 707 | } 708 | 709 | if (fptr->fd2 == -1) { 710 | fd = fptr->fd; 711 | } else { 712 | fd = fptr->fd2; 713 | } 714 | length = write(fd, RSTRING_PTR(buf), RSTRING_LEN(buf)); 715 | if (length == -1) { 716 | mrb_sys_fail(mrb, 0); 717 | } 718 | 719 | return mrb_fixnum_value(length); 720 | } 721 | 722 | mrb_value 723 | mrb_io_close(mrb_state *mrb, mrb_value self) 724 | { 725 | struct mrb_io *fptr; 726 | fptr = io_get_open_fptr(mrb, self); 727 | fptr_finalize(mrb, fptr, FALSE); 728 | return mrb_nil_value(); 729 | } 730 | 731 | mrb_value 732 | mrb_io_closed(mrb_state *mrb, mrb_value io) 733 | { 734 | struct mrb_io *fptr; 735 | fptr = (struct mrb_io *)mrb_get_datatype(mrb, io, &mrb_io_type); 736 | if (fptr->fd >= 0) { 737 | return mrb_false_value(); 738 | } 739 | 740 | return mrb_true_value(); 741 | } 742 | 743 | mrb_value 744 | mrb_io_pid(mrb_state *mrb, mrb_value io) 745 | { 746 | struct mrb_io *fptr; 747 | fptr = (struct mrb_io *)mrb_get_datatype(mrb, io, &mrb_io_type); 748 | 749 | if (fptr->pid > 0) { 750 | return mrb_fixnum_value(fptr->pid); 751 | } 752 | 753 | return mrb_nil_value(); 754 | } 755 | 756 | static struct timeval 757 | time2timeval(mrb_state *mrb, mrb_value time) 758 | { 759 | struct timeval t = { 0, 0 }; 760 | 761 | switch (mrb_type(time)) { 762 | case MRB_TT_FIXNUM: 763 | t.tv_sec = mrb_fixnum(time); 764 | t.tv_usec = 0; 765 | break; 766 | 767 | case MRB_TT_FLOAT: 768 | t.tv_sec = mrb_float(time); 769 | t.tv_usec = (mrb_float(time) - t.tv_sec) * 1000000.0; 770 | break; 771 | 772 | default: 773 | mrb_raise(mrb, E_TYPE_ERROR, "wrong argument class"); 774 | } 775 | 776 | return t; 777 | } 778 | 779 | static int 780 | mrb_io_read_data_pending(mrb_state *mrb, mrb_value io) 781 | { 782 | mrb_value buf = mrb_iv_get(mrb, io, mrb_intern_cstr(mrb, "@buf")); 783 | if (mrb_type(buf) == MRB_TT_STRING && RSTRING_LEN(buf) > 0) { 784 | return 1; 785 | } 786 | return 0; 787 | } 788 | 789 | #ifndef _WIN32 790 | static mrb_value 791 | mrb_io_s_pipe(mrb_state *mrb, mrb_value klass) 792 | { 793 | mrb_value r = mrb_nil_value(); 794 | mrb_value w = mrb_nil_value(); 795 | struct mrb_io *fptr_r; 796 | struct mrb_io *fptr_w; 797 | int pipes[2]; 798 | 799 | if (mrb_pipe(mrb, pipes) == -1) { 800 | mrb_sys_fail(mrb, "pipe"); 801 | } 802 | 803 | r = mrb_obj_value(mrb_data_object_alloc(mrb, mrb_class_ptr(klass), NULL, &mrb_io_type)); 804 | mrb_iv_set(mrb, r, mrb_intern_cstr(mrb, "@buf"), mrb_str_new_cstr(mrb, "")); 805 | fptr_r = mrb_io_alloc(mrb); 806 | fptr_r->fd = pipes[0]; 807 | fptr_r->readable = 1; 808 | fptr_r->writable = 0; 809 | fptr_r->sync = 0; 810 | DATA_TYPE(r) = &mrb_io_type; 811 | DATA_PTR(r) = fptr_r; 812 | 813 | w = mrb_obj_value(mrb_data_object_alloc(mrb, mrb_class_ptr(klass), NULL, &mrb_io_type)); 814 | mrb_iv_set(mrb, w, mrb_intern_cstr(mrb, "@buf"), mrb_str_new_cstr(mrb, "")); 815 | fptr_w = mrb_io_alloc(mrb); 816 | fptr_w->fd = pipes[1]; 817 | fptr_w->readable = 0; 818 | fptr_w->writable = 1; 819 | fptr_w->sync = 1; 820 | DATA_TYPE(w) = &mrb_io_type; 821 | DATA_PTR(w) = fptr_w; 822 | 823 | return mrb_assoc_new(mrb, r, w); 824 | } 825 | #endif 826 | 827 | static mrb_value 828 | mrb_io_s_select(mrb_state *mrb, mrb_value klass) 829 | { 830 | mrb_value *argv; 831 | mrb_int argc; 832 | mrb_value read, read_io, write, except, timeout, list; 833 | struct timeval *tp, timerec; 834 | fd_set pset, rset, wset, eset; 835 | fd_set *rp, *wp, *ep; 836 | struct mrb_io *fptr; 837 | int pending = 0; 838 | mrb_value result; 839 | int max = 0; 840 | int interrupt_flag = 0; 841 | int i, n; 842 | 843 | mrb_get_args(mrb, "*", &argv, &argc); 844 | 845 | if (argc < 1 || argc > 4) { 846 | mrb_raisef(mrb, E_ARGUMENT_ERROR, "wrong number of arguments (%S for 1..4)", mrb_fixnum_value(argc)); 847 | } 848 | 849 | timeout = mrb_nil_value(); 850 | except = mrb_nil_value(); 851 | write = mrb_nil_value(); 852 | if (argc > 3) 853 | timeout = argv[3]; 854 | if (argc > 2) 855 | except = argv[2]; 856 | if (argc > 1) 857 | write = argv[1]; 858 | read = argv[0]; 859 | 860 | if (mrb_nil_p(timeout)) { 861 | tp = NULL; 862 | } else { 863 | timerec = time2timeval(mrb, timeout); 864 | tp = &timerec; 865 | } 866 | 867 | FD_ZERO(&pset); 868 | if (!mrb_nil_p(read)) { 869 | mrb_check_type(mrb, read, MRB_TT_ARRAY); 870 | rp = &rset; 871 | FD_ZERO(rp); 872 | for (i = 0; i < RARRAY_LEN(read); i++) { 873 | read_io = RARRAY_PTR(read)[i]; 874 | fptr = (struct mrb_io *)mrb_get_datatype(mrb, read_io, &mrb_io_type); 875 | FD_SET(fptr->fd, rp); 876 | if (mrb_io_read_data_pending(mrb, read_io)) { 877 | pending++; 878 | FD_SET(fptr->fd, &pset); 879 | } 880 | if (max < fptr->fd) 881 | max = fptr->fd; 882 | } 883 | if (pending) { 884 | timerec.tv_sec = timerec.tv_usec = 0; 885 | tp = &timerec; 886 | } 887 | } else { 888 | rp = NULL; 889 | } 890 | 891 | if (!mrb_nil_p(write)) { 892 | mrb_check_type(mrb, write, MRB_TT_ARRAY); 893 | wp = &wset; 894 | FD_ZERO(wp); 895 | for (i = 0; i < RARRAY_LEN(write); i++) { 896 | fptr = (struct mrb_io *)mrb_get_datatype(mrb, RARRAY_PTR(write)[i], &mrb_io_type); 897 | FD_SET(fptr->fd, wp); 898 | if (max < fptr->fd) 899 | max = fptr->fd; 900 | if (fptr->fd2 >= 0) { 901 | FD_SET(fptr->fd2, wp); 902 | if (max < fptr->fd2) 903 | max = fptr->fd2; 904 | } 905 | } 906 | } else { 907 | wp = NULL; 908 | } 909 | 910 | if (!mrb_nil_p(except)) { 911 | mrb_check_type(mrb, except, MRB_TT_ARRAY); 912 | ep = &eset; 913 | FD_ZERO(ep); 914 | for (i = 0; i < RARRAY_LEN(except); i++) { 915 | fptr = (struct mrb_io *)mrb_get_datatype(mrb, RARRAY_PTR(except)[i], &mrb_io_type); 916 | FD_SET(fptr->fd, ep); 917 | if (max < fptr->fd) 918 | max = fptr->fd; 919 | if (fptr->fd2 >= 0) { 920 | FD_SET(fptr->fd2, ep); 921 | if (max < fptr->fd2) 922 | max = fptr->fd2; 923 | } 924 | } 925 | } else { 926 | ep = NULL; 927 | } 928 | 929 | max++; 930 | 931 | retry: 932 | n = select(max, rp, wp, ep, tp); 933 | if (n < 0) { 934 | if (errno != EINTR) 935 | mrb_sys_fail(mrb, "select failed"); 936 | if (tp == NULL) 937 | goto retry; 938 | interrupt_flag = 1; 939 | } 940 | 941 | if (!pending && n == 0) 942 | return mrb_nil_value(); 943 | 944 | result = mrb_ary_new_capa(mrb, 3); 945 | mrb_ary_push(mrb, result, rp? mrb_ary_new(mrb) : mrb_ary_new_capa(mrb, 0)); 946 | mrb_ary_push(mrb, result, wp? mrb_ary_new(mrb) : mrb_ary_new_capa(mrb, 0)); 947 | mrb_ary_push(mrb, result, ep? mrb_ary_new(mrb) : mrb_ary_new_capa(mrb, 0)); 948 | 949 | if (interrupt_flag == 0) { 950 | if (rp) { 951 | list = RARRAY_PTR(result)[0]; 952 | for (i = 0; i < RARRAY_LEN(read); i++) { 953 | fptr = (struct mrb_io *)mrb_get_datatype(mrb, RARRAY_PTR(read)[i], &mrb_io_type); 954 | if (FD_ISSET(fptr->fd, rp) || 955 | FD_ISSET(fptr->fd, &pset)) { 956 | mrb_ary_push(mrb, list, RARRAY_PTR(read)[i]); 957 | } 958 | } 959 | } 960 | 961 | if (wp) { 962 | list = RARRAY_PTR(result)[1]; 963 | for (i = 0; i < RARRAY_LEN(write); i++) { 964 | fptr = (struct mrb_io *)mrb_get_datatype(mrb, RARRAY_PTR(write)[i], &mrb_io_type); 965 | if (FD_ISSET(fptr->fd, wp)) { 966 | mrb_ary_push(mrb, list, RARRAY_PTR(write)[i]); 967 | } else if (fptr->fd2 >= 0 && FD_ISSET(fptr->fd2, wp)) { 968 | mrb_ary_push(mrb, list, RARRAY_PTR(write)[i]); 969 | } 970 | } 971 | } 972 | 973 | if (ep) { 974 | list = RARRAY_PTR(result)[2]; 975 | for (i = 0; i < RARRAY_LEN(except); i++) { 976 | fptr = (struct mrb_io *)mrb_get_datatype(mrb, RARRAY_PTR(except)[i], &mrb_io_type); 977 | if (FD_ISSET(fptr->fd, ep)) { 978 | mrb_ary_push(mrb, list, RARRAY_PTR(except)[i]); 979 | } else if (fptr->fd2 >= 0 && FD_ISSET(fptr->fd2, ep)) { 980 | mrb_ary_push(mrb, list, RARRAY_PTR(except)[i]); 981 | } 982 | } 983 | } 984 | } 985 | 986 | return result; 987 | } 988 | 989 | mrb_value 990 | mrb_io_fileno(mrb_state *mrb, mrb_value io) 991 | { 992 | struct mrb_io *fptr; 993 | fptr = (struct mrb_io *)mrb_get_datatype(mrb, io, &mrb_io_type); 994 | return mrb_fixnum_value(fptr->fd); 995 | } 996 | 997 | mrb_value 998 | mrb_io_close_on_exec_p(mrb_state *mrb, mrb_value self) 999 | { 1000 | #if defined(F_GETFD) && defined(F_SETFD) && defined(FD_CLOEXEC) 1001 | struct mrb_io *fptr; 1002 | int ret; 1003 | 1004 | fptr = io_get_open_fptr(mrb, self); 1005 | 1006 | if (fptr->fd2 >= 0) { 1007 | if ((ret = fcntl(fptr->fd2, F_GETFD)) == -1) mrb_sys_fail(mrb, "F_GETFD failed"); 1008 | if (!(ret & FD_CLOEXEC)) return mrb_false_value(); 1009 | } 1010 | 1011 | if ((ret = fcntl(fptr->fd, F_GETFD)) == -1) mrb_sys_fail(mrb, "F_GETFD failed"); 1012 | if (!(ret & FD_CLOEXEC)) return mrb_false_value(); 1013 | return mrb_true_value(); 1014 | 1015 | #else 1016 | mrb_raise(mrb, E_NOTIMP_ERROR, "IO#close_on_exec? is not supported on the platform"); 1017 | return mrb_false_value(); 1018 | #endif 1019 | } 1020 | 1021 | mrb_value 1022 | mrb_io_set_close_on_exec(mrb_state *mrb, mrb_value self) 1023 | { 1024 | #if defined(F_GETFD) && defined(F_SETFD) && defined(FD_CLOEXEC) 1025 | struct mrb_io *fptr; 1026 | int flag, ret; 1027 | mrb_bool b; 1028 | 1029 | fptr = io_get_open_fptr(mrb, self); 1030 | mrb_get_args(mrb, "b", &b); 1031 | flag = b ? FD_CLOEXEC : 0; 1032 | 1033 | if (fptr->fd2 >= 0) { 1034 | if ((ret = fcntl(fptr->fd2, F_GETFD)) == -1) mrb_sys_fail(mrb, "F_GETFD failed"); 1035 | if ((ret & FD_CLOEXEC) != flag) { 1036 | ret = (ret & ~FD_CLOEXEC) | flag; 1037 | ret = fcntl(fptr->fd2, F_SETFD, ret); 1038 | 1039 | if (ret == -1) mrb_sys_fail(mrb, "F_SETFD failed"); 1040 | } 1041 | } 1042 | 1043 | if ((ret = fcntl(fptr->fd, F_GETFD)) == -1) mrb_sys_fail(mrb, "F_GETFD failed"); 1044 | if ((ret & FD_CLOEXEC) != flag) { 1045 | ret = (ret & ~FD_CLOEXEC) | flag; 1046 | ret = fcntl(fptr->fd, F_SETFD, ret); 1047 | if (ret == -1) mrb_sys_fail(mrb, "F_SETFD failed"); 1048 | } 1049 | 1050 | return mrb_bool_value(b); 1051 | #else 1052 | mrb_raise(mrb, E_NOTIMP_ERROR, "IO#close_on_exec= is not supported on the platform"); 1053 | return mrb_nil_value(); 1054 | #endif 1055 | } 1056 | 1057 | mrb_value 1058 | mrb_io_set_sync(mrb_state *mrb, mrb_value self) 1059 | { 1060 | struct mrb_io *fptr; 1061 | mrb_bool b; 1062 | 1063 | fptr = io_get_open_fptr(mrb, self); 1064 | mrb_get_args(mrb, "b", &b); 1065 | fptr->sync = b; 1066 | return mrb_bool_value(b); 1067 | } 1068 | 1069 | mrb_value 1070 | mrb_io_sync(mrb_state *mrb, mrb_value self) 1071 | { 1072 | struct mrb_io *fptr; 1073 | fptr = io_get_open_fptr(mrb, self); 1074 | return mrb_bool_value(fptr->sync); 1075 | } 1076 | 1077 | void 1078 | mrb_init_io(mrb_state *mrb) 1079 | { 1080 | struct RClass *io; 1081 | 1082 | io = mrb_define_class(mrb, "IO", mrb->object_class); 1083 | MRB_SET_INSTANCE_TT(io, MRB_TT_DATA); 1084 | 1085 | mrb_include_module(mrb, io, mrb_module_get(mrb, "Enumerable")); /* 15.2.20.3 */ 1086 | #ifndef _WIN32 1087 | mrb_define_class_method(mrb, io, "_popen", mrb_io_s_popen, MRB_ARGS_ANY()); 1088 | mrb_define_class_method(mrb, io, "_sysclose", mrb_io_s_sysclose, MRB_ARGS_REQ(1)); 1089 | #endif 1090 | mrb_define_class_method(mrb, io, "for_fd", mrb_io_s_for_fd, MRB_ARGS_ANY()); 1091 | mrb_define_class_method(mrb, io, "select", mrb_io_s_select, MRB_ARGS_ANY()); 1092 | mrb_define_class_method(mrb, io, "sysopen", mrb_io_s_sysopen, MRB_ARGS_ANY()); 1093 | #ifndef _WIN32 1094 | mrb_define_class_method(mrb, io, "_pipe", mrb_io_s_pipe, MRB_ARGS_NONE()); 1095 | #endif 1096 | 1097 | mrb_define_method(mrb, io, "initialize", mrb_io_initialize, MRB_ARGS_ANY()); /* 15.2.20.5.21 (x)*/ 1098 | mrb_define_method(mrb, io, "_check_readable", mrb_io_check_readable, MRB_ARGS_NONE()); 1099 | mrb_define_method(mrb, io, "isatty", mrb_io_isatty, MRB_ARGS_NONE()); 1100 | mrb_define_method(mrb, io, "sync", mrb_io_sync, MRB_ARGS_NONE()); 1101 | mrb_define_method(mrb, io, "sync=", mrb_io_set_sync, MRB_ARGS_REQ(1)); 1102 | mrb_define_method(mrb, io, "sysread", mrb_io_sysread, MRB_ARGS_ANY()); 1103 | mrb_define_method(mrb, io, "sysseek", mrb_io_sysseek, MRB_ARGS_REQ(1)); 1104 | mrb_define_method(mrb, io, "syswrite", mrb_io_syswrite, MRB_ARGS_REQ(1)); 1105 | mrb_define_method(mrb, io, "close", mrb_io_close, MRB_ARGS_NONE()); /* 15.2.20.5.1 */ 1106 | mrb_define_method(mrb, io, "close_on_exec=", mrb_io_set_close_on_exec, MRB_ARGS_REQ(1)); 1107 | mrb_define_method(mrb, io, "close_on_exec?", mrb_io_close_on_exec_p, MRB_ARGS_NONE()); 1108 | mrb_define_method(mrb, io, "closed?", mrb_io_closed, MRB_ARGS_NONE()); /* 15.2.20.5.2 */ 1109 | mrb_define_method(mrb, io, "pid", mrb_io_pid, MRB_ARGS_NONE()); /* 15.2.20.5.2 */ 1110 | mrb_define_method(mrb, io, "fileno", mrb_io_fileno, MRB_ARGS_NONE()); 1111 | 1112 | 1113 | mrb_gv_set(mrb, mrb_intern_cstr(mrb, "$/"), mrb_str_new_cstr(mrb, "\n")); 1114 | } 1115 | -------------------------------------------------------------------------------- /src/mruby_io_gem.c: -------------------------------------------------------------------------------- 1 | #include "mruby.h" 2 | 3 | void mrb_init_io(mrb_state *mrb); 4 | void mrb_init_file(mrb_state *mrb); 5 | void mrb_init_file_test(mrb_state *mrb); 6 | 7 | #define DONE mrb_gc_arena_restore(mrb, 0) 8 | 9 | void 10 | mrb_mruby_io_gem_init(mrb_state* mrb) 11 | { 12 | mrb_init_io(mrb); DONE; 13 | mrb_init_file(mrb); DONE; 14 | mrb_init_file_test(mrb); DONE; 15 | } 16 | 17 | void 18 | mrb_mruby_io_gem_final(mrb_state* mrb) 19 | { 20 | } 21 | -------------------------------------------------------------------------------- /test/file.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # IO Test 3 | 4 | assert('File', '15.2.21') do 5 | File.class == Class 6 | end 7 | 8 | assert('File', '15.2.21.2') do 9 | File.superclass == IO 10 | end 11 | 12 | assert('File TEST SETUP') do 13 | MRubyIOTestUtil.io_test_setup 14 | end 15 | 16 | assert('File#initialize', '15.2.21.4.1') do 17 | io = File.open($mrbtest_io_rfname, "r") 18 | assert_nil io.close 19 | assert_raise IOError do 20 | io.close 21 | end 22 | end 23 | 24 | assert('File#path', '15.2.21.4.2') do 25 | io = File.open($mrbtest_io_rfname, "r") 26 | assert_equal $mrbtest_io_msg, io.read 27 | assert_equal $mrbtest_io_rfname, io.path 28 | io.close 29 | assert_equal $mrbtest_io_rfname, io.path 30 | io.closed? 31 | end 32 | 33 | assert('File.basename') do 34 | assert_equal '/', File.basename('//') 35 | assert_equal 'a', File.basename('/a/') 36 | assert_equal 'b', File.basename('/a/b') 37 | assert_equal 'b', File.basename('../a/b') 38 | end 39 | 40 | assert('File.dirname') do 41 | assert_equal '.', File.dirname('') 42 | assert_equal '.', File.dirname('a') 43 | assert_equal '/', File.dirname('/a') 44 | assert_equal 'a', File.dirname('a/b') 45 | assert_equal '/a', File.dirname('/a/b') 46 | end 47 | 48 | assert('File.extname') do 49 | assert_equal '.txt', File.extname('foo/foo.txt') 50 | assert_equal '.gz', File.extname('foo/foo.tar.gz') 51 | assert_equal '', File.extname('foo/bar') 52 | assert_equal '', File.extname('foo/.bar') 53 | assert_equal '', File.extname('foo.txt/bar') 54 | assert_equal '', File.extname('.foo') 55 | end 56 | 57 | assert('IO#flock') do 58 | f = File.open $mrbtest_io_rfname 59 | begin 60 | assert_equal(f.flock(File::LOCK_SH), 0) 61 | assert_equal(f.flock(File::LOCK_UN), 0) 62 | assert_equal(f.flock(File::LOCK_EX | File::LOCK_NB), 0) 63 | assert_equal(f.flock(File::LOCK_UN), 0) 64 | rescue NotImplementedError => e 65 | skip e.message 66 | ensure 67 | f.close 68 | end 69 | end 70 | 71 | assert('File.join') do 72 | assert_equal "", File.join() 73 | assert_equal "a", File.join("a") 74 | assert_equal "/a", File.join("/a") 75 | assert_equal "a/", File.join("a/") 76 | assert_equal "a/b/c", File.join("a", "b", "c") 77 | assert_equal "/a/b/c", File.join("/a", "b", "c") 78 | assert_equal "a/b/c/", File.join("a", "b", "c/") 79 | assert_equal "a/b/c", File.join("a/", "/b/", "/c") 80 | assert_equal "a/b/c", File.join(["a", "b", "c"]) 81 | assert_equal "a/b/c", File.join("a", ["b", ["c"]]) 82 | end 83 | 84 | assert('File.realpath') do 85 | if File::ALT_SEPARATOR 86 | readme_path = File._getwd + File::ALT_SEPARATOR + "README.md" 87 | assert_equal readme_path, File.realpath("README.md") 88 | else 89 | dir = MRubyIOTestUtil.mkdtemp("mruby-io-test.XXXXXX") 90 | begin 91 | dir1 = File.realpath($mrbtest_io_rfname) 92 | dir2 = File.realpath("./#{dir}//./../#{$mrbtest_io_symlinkname}") 93 | assert_equal dir1, dir2 94 | ensure 95 | MRubyIOTestUtil.rmdir dir 96 | end 97 | end 98 | end 99 | 100 | assert("File.readlink") do 101 | begin 102 | assert_equal $mrbtest_io_rfname, File.readlink($mrbtest_io_symlinkname) 103 | rescue NotImplementedError => e 104 | skip e.message 105 | end 106 | end 107 | 108 | assert("File.readlink fails with non-symlink") do 109 | begin 110 | assert_raise(RuntimeError) { 111 | begin 112 | File.readlink($mrbtest_io_rfname) 113 | rescue => e 114 | if Object.const_defined?(:SystemCallError) and e.kind_of?(SystemCallError) 115 | raise RuntimeError, "SystemCallError converted to RuntimeError" 116 | end 117 | raise e 118 | end 119 | } 120 | rescue NotImplementedError => e 121 | skip e.message 122 | end 123 | end 124 | 125 | assert('File TEST CLEANUP') do 126 | assert_nil MRubyIOTestUtil.io_test_cleanup 127 | end 128 | 129 | assert('File.expand_path') do 130 | assert_equal "/", File.expand_path("..", "/tmp"), "parent path with base_dir (1)" 131 | assert_equal "/tmp", File.expand_path("..", "/tmp/mruby"), "parent path with base_dir (2)" 132 | 133 | assert_equal "/home", File.expand_path("/home"), "absolute" 134 | assert_equal "/home", File.expand_path("/home", "."), "absolute with base_dir" 135 | 136 | assert_equal "/hoge", File.expand_path("/tmp/..//hoge") 137 | assert_equal "/hoge", File.expand_path("////tmp/..///////hoge") 138 | 139 | assert_equal "/", File.expand_path("../../../..", "/") 140 | if File._getwd[1] == ":" 141 | drive_letter = File._getwd[0] 142 | assert_equal drive_letter + ":\\", File.expand_path(([".."] * 100).join("/")) 143 | else 144 | assert_equal "/", File.expand_path(([".."] * 100).join("/")) 145 | end 146 | end 147 | 148 | assert('File.expand_path (with ENV)') do 149 | skip unless Object.const_defined?(:ENV) && ENV['HOME'] 150 | 151 | assert_equal ENV['HOME'], File.expand_path("~/"), "home" 152 | assert_equal ENV['HOME'], File.expand_path("~/", "/"), "home with base_dir" 153 | 154 | assert_equal "#{ENV['HOME']}/user", File.expand_path("user", ENV['HOME']), "relative with base_dir" 155 | end 156 | 157 | assert('File.path') do 158 | assert_equal "", File.path("") 159 | assert_equal "a/b/c", File.path("a/b/c") 160 | assert_equal "a/../b/./c", File.path("a/../b/./c") 161 | assert_raise(TypeError) { File.path(nil) } 162 | assert_raise(TypeError) { File.path(123) } 163 | 164 | end 165 | 166 | assert('File.symlink') do 167 | target_name = "/usr/bin" 168 | symlink_name = "test-bin-dummy" 169 | if !File.exist?(target_name) 170 | skip("target directory of File.symlink is not found") 171 | else 172 | assert_equal 0, File.symlink(target_name, symlink_name) 173 | begin 174 | assert_equal true, File.symlink?(symlink_name) 175 | ensure 176 | File.delete symlink_name 177 | end 178 | end 179 | end 180 | 181 | assert('File.chmod') do 182 | File.open('chmod-test', 'w') {} 183 | begin 184 | assert_equal 1, File.chmod(0400, 'chmod-test') 185 | ensure 186 | File.delete('chmod-test') 187 | end 188 | end 189 | -------------------------------------------------------------------------------- /test/file_test.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # FileTest 3 | 4 | assert('FileTest TEST SETUP') do 5 | MRubyIOTestUtil.io_test_setup 6 | end 7 | 8 | assert("FileTest.directory?") do 9 | dir = MRubyIOTestUtil.mkdtemp("mruby-io-test.XXXXXX") 10 | begin 11 | assert_true FileTest.directory?(dir) 12 | assert_false FileTest.directory?($mrbtest_io_rfname) 13 | ensure 14 | MRubyIOTestUtil.rmdir dir 15 | end 16 | end 17 | 18 | assert("FileTest.exist?") do 19 | assert_equal true, FileTest.exist?($mrbtest_io_rfname), "filename - exist" 20 | assert_equal false, FileTest.exist?($mrbtest_io_rfname + "-"), "filename - not exist" 21 | io = IO.new(IO.sysopen($mrbtest_io_rfname)) 22 | assert_equal true, FileTest.exist?(io), "io obj - exist" 23 | io.close 24 | assert_equal true, io.closed? 25 | assert_raise IOError do 26 | FileTest.exist?(io) 27 | end 28 | end 29 | 30 | assert("FileTest.file?") do 31 | dir = MRubyIOTestUtil.mkdtemp("mruby-io-test.XXXXXX") 32 | begin 33 | assert_true FileTest.file?($mrbtest_io_rfname) 34 | assert_false FileTest.file?(dir) 35 | ensure 36 | MRubyIOTestUtil.rmdir dir 37 | end 38 | end 39 | 40 | assert("FileTest.pipe?") do 41 | begin 42 | assert_equal false, FileTest.pipe?("/tmp") 43 | io = IO.popen("ls") 44 | assert_equal true, FileTest.pipe?(io) 45 | rescue NotImplementedError => e 46 | skip e.message 47 | end 48 | end 49 | 50 | assert('FileTest.size') do 51 | assert_equal FileTest.size($mrbtest_io_rfname), $mrbtest_io_msg.size 52 | assert_equal FileTest.size($mrbtest_io_wfname), 0 53 | end 54 | 55 | assert("FileTest.size?") do 56 | assert_equal $mrbtest_io_msg.size, FileTest.size?($mrbtest_io_rfname) 57 | assert_equal nil, FileTest.size?($mrbtest_io_wfname) 58 | assert_equal nil, FileTest.size?("not-exist-test-target-file") 59 | 60 | fp1 = File.open($mrbtest_io_rfname) 61 | fp2 = File.open($mrbtest_io_wfname) 62 | assert_equal $mrbtest_io_msg.size, FileTest.size?(fp1) 63 | assert_equal nil, FileTest.size?(fp2) 64 | fp1.close 65 | fp2.close 66 | 67 | assert_raise IOError do 68 | FileTest.size?(fp1) 69 | end 70 | assert_raise IOError do 71 | FileTest.size?(fp2) 72 | end 73 | 74 | fp1.closed? && fp2.closed? 75 | end 76 | 77 | assert("FileTest.socket?") do 78 | begin 79 | assert_true FileTest.socket?($mrbtest_io_socketname) 80 | rescue NotImplementedError => e 81 | skip e.message 82 | end 83 | end 84 | 85 | assert("FileTest.symlink?") do 86 | begin 87 | assert_true FileTest.symlink?($mrbtest_io_symlinkname) 88 | rescue NotImplementedError => e 89 | skip e.message 90 | end 91 | end 92 | 93 | assert("FileTest.zero?") do 94 | assert_equal false, FileTest.zero?($mrbtest_io_rfname) 95 | assert_equal true, FileTest.zero?($mrbtest_io_wfname) 96 | assert_equal false, FileTest.zero?("not-exist-test-target-file") 97 | 98 | fp1 = File.open($mrbtest_io_rfname) 99 | fp2 = File.open($mrbtest_io_wfname) 100 | assert_equal false, FileTest.zero?(fp1) 101 | assert_equal true, FileTest.zero?(fp2) 102 | fp1.close 103 | fp2.close 104 | 105 | assert_raise IOError do 106 | FileTest.zero?(fp1) 107 | end 108 | assert_raise IOError do 109 | FileTest.zero?(fp2) 110 | end 111 | 112 | fp1.closed? && fp2.closed? 113 | end 114 | 115 | assert('FileTest TEST CLEANUP') do 116 | assert_nil MRubyIOTestUtil.io_test_cleanup 117 | end 118 | -------------------------------------------------------------------------------- /test/gc_filedes.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ulimit -n 20 4 | mruby -e '100.times { File.open "'$0'" }' 5 | -------------------------------------------------------------------------------- /test/io.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # IO Test 3 | 4 | unless Object.respond_to? :assert_nothing_raised 5 | def assert_nothing_raised(*exp) 6 | ret = true 7 | if $mrbtest_assert 8 | $mrbtest_assert_idx += 1 9 | msg = exp.last.class == String ? exp.pop : "" 10 | begin 11 | yield 12 | rescue Exception => e 13 | msg = "#{msg} exception raised." 14 | diff = " Class: <#{e.class}>\n" + 15 | " Message: #{e.message}" 16 | $mrbtest_assert.push([$mrbtest_assert_idx, msg, diff]) 17 | ret = false 18 | end 19 | end 20 | ret 21 | end 22 | end 23 | 24 | assert('IO TEST SETUP') do 25 | MRubyIOTestUtil.io_test_setup 26 | end 27 | 28 | assert('IO', '15.2.20') do 29 | assert_equal(Class, IO.class) 30 | end 31 | 32 | assert('IO', '15.2.20.2') do 33 | assert_equal(Object, IO.superclass) 34 | end 35 | 36 | assert('IO', '15.2.20.3') do 37 | assert_include(IO.included_modules, Enumerable) 38 | end 39 | 40 | assert('IO.open', '15.2.20.4.1') do 41 | fd = IO.sysopen $mrbtest_io_rfname 42 | assert_equal Fixnum, fd.class 43 | io = IO.open fd 44 | assert_equal IO, io.class 45 | assert_equal $mrbtest_io_msg, io.read 46 | io.close 47 | 48 | fd = IO.sysopen $mrbtest_io_rfname 49 | IO.open(fd) do |io| 50 | assert_equal $mrbtest_io_msg, io.read 51 | end 52 | 53 | true 54 | end 55 | 56 | assert('IO#close', '15.2.20.5.1') do 57 | io = IO.new(IO.sysopen($mrbtest_io_rfname)) 58 | assert_nil io.close 59 | end 60 | 61 | assert('IO#closed?', '15.2.20.5.2') do 62 | io = IO.new(IO.sysopen($mrbtest_io_rfname)) 63 | assert_false io.closed? 64 | io.close 65 | assert_true io.closed? 66 | end 67 | 68 | #assert('IO#each', '15.2.20.5.3') do 69 | #assert('IO#each_byte', '15.2.20.5.4') do 70 | #assert('IO#each_line', '15.2.20.5.5') do 71 | 72 | assert('IO#eof?', '15.2.20.5.6') do 73 | io = IO.new(IO.sysopen($mrbtest_io_wfname, 'w'), 'w') 74 | assert_raise(IOError) do 75 | io.eof? 76 | end 77 | io.close 78 | 79 | # empty file 80 | io = IO.open(IO.sysopen($mrbtest_io_wfname, 'w'), 'w') 81 | io.close 82 | io = IO.open(IO.sysopen($mrbtest_io_wfname, 'r'), 'r') 83 | assert_true io.eof? 84 | io.close 85 | 86 | # nonempty file 87 | io = IO.new(IO.sysopen($mrbtest_io_rfname)) 88 | assert_false io.eof? 89 | io.readchar 90 | assert_false io.eof? 91 | io.read 92 | assert_true io.eof? 93 | io.close 94 | 95 | true 96 | end 97 | 98 | assert('IO#flush', '15.2.20.5.7') do 99 | # Note: mruby-io does not have any buffer to be flushed now. 100 | io = IO.new(IO.sysopen($mrbtest_io_wfname)) 101 | assert_equal io, io.flush 102 | io.close 103 | assert_raise(IOError) do 104 | io.flush 105 | end 106 | end 107 | 108 | assert('IO#getc', '15.2.20.5.8') do 109 | io = IO.new(IO.sysopen($mrbtest_io_rfname)) 110 | $mrbtest_io_msg.each_char { |ch| 111 | assert_equal ch, io.getc 112 | } 113 | assert_equal nil, io.getc 114 | io.close 115 | true 116 | end 117 | 118 | #assert('IO#gets', '15.2.20.5.9') do 119 | #assert('IO#initialize_copy', '15.2.20.5.10') do 120 | #assert('IO#print', '15.2.20.5.11') do 121 | #assert('IO#putc', '15.2.20.5.12') do 122 | #assert('IO#puts', '15.2.20.5.13') do 123 | 124 | assert('IO#read', '15.2.20.5.14') do 125 | IO.open(IO.sysopen($mrbtest_io_rfname)) do |io| 126 | assert_raise(ArgumentError) { io.read(-5) } 127 | assert_raise(TypeError) { io.read("str") } 128 | 129 | len = $mrbtest_io_msg.length 130 | assert_equal '', io.read(0) 131 | assert_equal 'mruby', io.read(5) 132 | assert_equal $mrbtest_io_msg[5,len], io.read(len) 133 | 134 | assert_equal "", io.read 135 | assert_nil io.read(1) 136 | end 137 | 138 | IO.open(IO.sysopen($mrbtest_io_rfname)) do |io| 139 | assert_equal $mrbtest_io_msg, io.read 140 | end 141 | end 142 | 143 | assert "IO#read(n) with n > IO::BUF_SIZE" do 144 | r,w = IO.pipe 145 | n = IO::BUF_SIZE+1 146 | w.write 'a'*n 147 | assert_equal r.read(n), 'a'*n 148 | end 149 | 150 | assert('IO#readchar', '15.2.20.5.15') do 151 | # almost same as IO#getc 152 | IO.open(IO.sysopen($mrbtest_io_rfname)) do |io| 153 | $mrbtest_io_msg.each_char { |ch| 154 | assert_equal ch, io.readchar 155 | } 156 | assert_raise(EOFError) do 157 | io.readchar 158 | end 159 | end 160 | end 161 | 162 | #assert('IO#readline', '15.2.20.5.16') do 163 | #assert('IO#readlines', '15.2.20.5.17') do 164 | 165 | assert('IO#sync', '15.2.20.5.18') do 166 | io = IO.new(IO.sysopen($mrbtest_io_rfname)) 167 | s = io.sync 168 | assert_true(s == true || s == false) 169 | io.close 170 | assert_raise(IOError) do 171 | io.sync 172 | end 173 | end 174 | 175 | assert('IO#sync=', '15.2.20.5.19') do 176 | io = IO.new(IO.sysopen($mrbtest_io_rfname)) 177 | io.sync = true 178 | assert_true io.sync 179 | io.sync = false 180 | assert_false io.sync 181 | io.close 182 | assert_raise(IOError) do 183 | io.sync = true 184 | end 185 | end 186 | 187 | assert('IO#write', '15.2.20.5.20') do 188 | io = IO.open(IO.sysopen($mrbtest_io_wfname)) 189 | assert_equal 0, io.write("") 190 | io.close 191 | 192 | io = IO.open(IO.sysopen($mrbtest_io_wfname, "r+"), "r+") 193 | assert_equal 7, io.write("abcdefg") 194 | io.rewind 195 | assert_equal "ab", io.read(2) 196 | assert_equal 3, io.write("123") 197 | io.rewind 198 | assert_equal "ab123fg", io.read 199 | io.close 200 | 201 | true 202 | end 203 | 204 | assert('IO#<<') do 205 | io = IO.open(IO.sysopen($mrbtest_io_wfname)) 206 | io << "" << "" 207 | assert_equal 0, io.pos 208 | io.close 209 | true 210 | end 211 | 212 | assert('IO.for_fd') do 213 | fd = IO.sysopen($mrbtest_io_rfname) 214 | io = IO.for_fd(fd) 215 | assert_equal $mrbtest_io_msg, io.read 216 | io.close 217 | true 218 | end 219 | 220 | assert('IO.new') do 221 | io = IO.new(0) 222 | io.close 223 | true 224 | end 225 | 226 | assert('IO gc check') do 227 | 100.times { IO.new(0) } 228 | end 229 | 230 | assert('IO.sysopen("./nonexistent")') do 231 | if Object.const_defined? :Errno 232 | eclass = Errno::ENOENT 233 | else 234 | eclass = RuntimeError 235 | end 236 | assert_raise eclass do 237 | fd = IO.sysopen "./nonexistent" 238 | IO._sysclose fd 239 | end 240 | end 241 | 242 | assert('IO.sysopen, IO#sysread') do 243 | fd = IO.sysopen $mrbtest_io_rfname 244 | io = IO.new fd 245 | str1 = " " 246 | str2 = io.sysread(5, str1) 247 | assert_equal $mrbtest_io_msg[0,5], str1 248 | assert_equal $mrbtest_io_msg[0,5], str2 249 | assert_raise EOFError do 250 | io.sysread(10000) 251 | io.sysread(10000) 252 | end 253 | 254 | assert_raise RuntimeError do 255 | io.sysread(5, "abcde".freeze) 256 | end 257 | 258 | io.close 259 | assert_equal "", io.sysread(0) 260 | assert_raise(IOError) { io.sysread(1) } 261 | assert_raise(ArgumentError) { io.sysread(-1) } 262 | io.closed? 263 | 264 | fd = IO.sysopen $mrbtest_io_wfname, "w" 265 | io = IO.new fd, "w" 266 | assert_raise(IOError) { io.sysread(1) } 267 | io.close 268 | true 269 | end 270 | 271 | assert('IO.sysopen, IO#syswrite') do 272 | fd = IO.sysopen $mrbtest_io_wfname, "w" 273 | io = IO.new fd, "w" 274 | str = "abcdefg" 275 | len = io.syswrite(str) 276 | assert_equal str.size, len 277 | io.close 278 | 279 | io = IO.new(IO.sysopen($mrbtest_io_rfname), "r") 280 | assert_raise(IOError) { io.syswrite("a") } 281 | io.close 282 | 283 | true 284 | end 285 | 286 | assert('IO#_read_buf') do 287 | fd = IO.sysopen $mrbtest_io_rfname 288 | io = IO.new fd 289 | def io._buf 290 | @buf 291 | end 292 | msg_len = $mrbtest_io_msg.size 293 | assert_equal '', io._buf 294 | assert_equal $mrbtest_io_msg, io._read_buf 295 | assert_equal $mrbtest_io_msg, io._buf 296 | assert_equal 'mruby', io.read(5) 297 | assert_equal 5, io.pos 298 | assert_equal msg_len - 5, io._buf.size 299 | assert_equal $mrbtest_io_msg[5,100], io.read 300 | assert_equal 0, io._buf.size 301 | assert_raise EOFError do 302 | io._read_buf 303 | end 304 | assert_equal true, io.eof 305 | assert_equal true, io.eof? 306 | io.close 307 | io.closed? 308 | end 309 | 310 | assert('IO#isatty') do 311 | f1 = File.open("/dev/tty") 312 | f2 = File.open($mrbtest_io_rfname) 313 | 314 | assert_true f1.isatty 315 | assert_false f2.isatty 316 | 317 | f1.close 318 | f2.close 319 | true 320 | end 321 | 322 | assert('IO#pos=, IO#seek') do 323 | fd = IO.sysopen $mrbtest_io_rfname 324 | io = IO.new fd 325 | def io._buf 326 | @buf 327 | end 328 | assert_equal 'm', io.getc 329 | assert_equal 1, io.pos 330 | assert_equal 0, io.seek(0) 331 | assert_equal 0, io.pos 332 | io.close 333 | io.closed? 334 | end 335 | 336 | assert('IO#rewind') do 337 | fd = IO.sysopen $mrbtest_io_rfname 338 | io = IO.new fd 339 | assert_equal 'm', io.getc 340 | assert_equal 1, io.pos 341 | assert_equal 0, io.rewind 342 | assert_equal 0, io.pos 343 | io.close 344 | io.closed? 345 | end 346 | 347 | assert('IO#gets') do 348 | fd = IO.sysopen $mrbtest_io_rfname 349 | io = IO.new fd 350 | 351 | # gets without arguments 352 | assert_equal $mrbtest_io_msg, io.gets, "gets without arguments" 353 | assert_equal nil, io.gets, "gets returns nil, when EOF" 354 | 355 | # gets with limit 356 | io.pos = 0 357 | assert_equal $mrbtest_io_msg[0, 5], io.gets(5), "gets with limit" 358 | 359 | # gets with rs 360 | io.pos = 0 361 | assert_equal $mrbtest_io_msg[0, 6], io.gets(' '), "gets with rs" 362 | 363 | # gets with rs, limit 364 | io.pos = 0 365 | assert_equal $mrbtest_io_msg[0, 5], io.gets(' ', 5), "gets with rs, limit" 366 | io.close 367 | assert_equal true, io.closed?, "close success" 368 | 369 | # reading many-lines file. 370 | fd = IO.sysopen $mrbtest_io_wfname, "w" 371 | io = IO.new fd, "w" 372 | io.write "0123456789" * 2 + "\na" 373 | assert_equal 22, io.pos 374 | io.close 375 | assert_equal true, io.closed? 376 | 377 | fd = IO.sysopen $mrbtest_io_wfname 378 | io = IO.new fd 379 | line = io.gets 380 | 381 | # gets first line 382 | assert_equal "0123456789" * 2 + "\n", line, "gets first line" 383 | assert_equal 21, line.size 384 | assert_equal 21, io.pos 385 | 386 | # gets second line 387 | assert_equal "a", io.gets, "gets second line" 388 | 389 | # gets third line 390 | assert_equal nil, io.gets, "gets third line; returns nil" 391 | 392 | io.close 393 | io.closed? 394 | end 395 | 396 | assert('IO#gets - paragraph mode') do 397 | fd = IO.sysopen $mrbtest_io_wfname, "w" 398 | io = IO.new fd, "w" 399 | io.write "0" * 10 + "\n" 400 | io.write "1" * 10 + "\n\n" 401 | io.write "2" * 10 + "\n" 402 | assert_equal 34, io.pos 403 | io.close 404 | assert_equal true, io.closed? 405 | 406 | fd = IO.sysopen $mrbtest_io_wfname 407 | io = IO.new fd 408 | para1 = "#{'0' * 10}\n#{'1' * 10}\n\n" 409 | text1 = io.gets("") 410 | assert_equal para1, text1 411 | para2 = "#{'2' * 10}\n" 412 | text2 = io.gets("") 413 | assert_equal para2, text2 414 | io.close 415 | io.closed? 416 | end 417 | 418 | assert('IO.popen') do 419 | begin 420 | $? = nil 421 | io = IO.popen("echo mruby-io") 422 | assert_true io.close_on_exec? 423 | assert_equal Fixnum, io.pid.class 424 | 425 | out = io.read 426 | assert_equal out.class, String 427 | assert_include out, 'mruby-io' 428 | 429 | io.close 430 | if Object.const_defined? :Process 431 | assert_true $?.success? 432 | else 433 | assert_equal 0, $? 434 | end 435 | 436 | assert_true io.closed? 437 | rescue NotImplementedError => e 438 | skip e.message 439 | end 440 | end 441 | 442 | assert('IO.popen with in option') do 443 | begin 444 | IO.pipe do |r, w| 445 | w.write 'hello' 446 | w.close 447 | assert_equal "hello", IO.popen("cat", "r", in: r) { |i| i.read } 448 | assert_equal "", r.read 449 | end 450 | assert_raise(ArgumentError) { IO.popen("hello", "r", in: Object.new) } 451 | rescue NotImplementedError => e 452 | skip e.message 453 | end 454 | end 455 | 456 | assert('IO.popen with out option') do 457 | begin 458 | IO.pipe do |r, w| 459 | IO.popen("echo 'hello'", "w", out: w) {} 460 | w.close 461 | assert_equal "hello\n", r.read 462 | end 463 | rescue NotImplementedError => e 464 | skip e.message 465 | end 466 | end 467 | 468 | assert('IO.popen with err option') do 469 | begin 470 | IO.pipe do |r, w| 471 | assert_equal "", IO.popen("echo 'hello' 1>&2", "r", err: w) { |i| i.read } 472 | w.close 473 | assert_equal "hello\n", r.read 474 | end 475 | rescue NotImplementedError => e 476 | skip e.message 477 | end 478 | end 479 | 480 | assert('IO.read') do 481 | # empty file 482 | fd = IO.sysopen $mrbtest_io_wfname, "w" 483 | io = IO.new fd, "w" 484 | io.close 485 | assert_equal "", IO.read($mrbtest_io_wfname) 486 | assert_equal nil, IO.read($mrbtest_io_wfname, 1) 487 | 488 | # one byte file 489 | fd = IO.sysopen $mrbtest_io_wfname, "w" 490 | io = IO.new fd, "w" 491 | io.write "123" 492 | io.close 493 | assert_equal "123", IO.read($mrbtest_io_wfname) 494 | assert_equal "", IO.read($mrbtest_io_wfname, 0) 495 | assert_equal "1", IO.read($mrbtest_io_wfname, 1) 496 | assert_equal "", IO.read($mrbtest_io_wfname, 0, 10) 497 | assert_equal "23", IO.read($mrbtest_io_wfname, 2, 1) 498 | assert_equal "23", IO.read($mrbtest_io_wfname, 10, 1) 499 | assert_equal "", IO.read($mrbtest_io_wfname, nil, 10) 500 | assert_equal nil, IO.read($mrbtest_io_wfname, 1, 10) 501 | end 502 | 503 | assert('IO#fileno') do 504 | fd = IO.sysopen $mrbtest_io_rfname 505 | io = IO.new fd 506 | assert_equal io.fileno, fd 507 | assert_equal io.to_i, fd 508 | io.close 509 | io.closed? 510 | end 511 | 512 | assert('IO#close_on_exec') do 513 | fd = IO.sysopen $mrbtest_io_wfname, "w" 514 | io = IO.new fd, "w" 515 | begin 516 | # IO.sysopen opens a file descripter with O_CLOEXEC flag. 517 | assert_true io.close_on_exec? 518 | rescue ScriptError 519 | skip "IO\#close_on_exec is not implemented." 520 | end 521 | 522 | io.close_on_exec = false 523 | assert_equal(false, io.close_on_exec?) 524 | io.close_on_exec = true 525 | assert_equal(true, io.close_on_exec?) 526 | io.close_on_exec = false 527 | assert_equal(false, io.close_on_exec?) 528 | 529 | io.close 530 | io.closed? 531 | 532 | begin 533 | r, w = IO.pipe 534 | assert_equal(true, r.close_on_exec?) 535 | r.close_on_exec = false 536 | assert_equal(false, r.close_on_exec?) 537 | r.close_on_exec = true 538 | assert_equal(true, r.close_on_exec?) 539 | 540 | assert_equal(true, w.close_on_exec?) 541 | w.close_on_exec = false 542 | assert_equal(false, w.close_on_exec?) 543 | w.close_on_exec = true 544 | assert_equal(true, w.close_on_exec?) 545 | ensure 546 | r.close unless r.closed? 547 | w.close unless w.closed? 548 | end 549 | end 550 | 551 | assert('IO#sysseek') do 552 | IO.open(IO.sysopen($mrbtest_io_rfname)) do |io| 553 | assert_equal 2, io.sysseek(2) 554 | assert_equal 5, io.sysseek(3, IO::SEEK_CUR) # 2 + 3 => 5 555 | assert_equal $mrbtest_io_msg.size - 4, io.sysseek(-4, IO::SEEK_END) 556 | end 557 | end 558 | 559 | assert('IO.pipe') do 560 | begin 561 | called = false 562 | IO.pipe do |r, w| 563 | assert_true r.kind_of?(IO) 564 | assert_true w.kind_of?(IO) 565 | assert_false r.closed? 566 | assert_false w.closed? 567 | assert_true FileTest.pipe?(r) 568 | assert_true FileTest.pipe?(w) 569 | assert_nil r.pid 570 | assert_nil w.pid 571 | assert_true 2 < r.fileno 572 | assert_true 2 < w.fileno 573 | assert_true r.fileno != w.fileno 574 | assert_false r.sync 575 | assert_true w.sync 576 | assert_equal 8, w.write('test for') 577 | assert_equal 'test', r.read(4) 578 | assert_equal ' for', r.read(4) 579 | assert_equal 5, w.write(' pipe') 580 | assert_equal nil, w.close 581 | assert_equal ' pipe', r.read 582 | called = true 583 | assert_raise(IOError) { r.write 'test' } 584 | # TODO: 585 | # This assert expect raise IOError but got RuntimeError 586 | # Because mruby-io not have flag for I/O readable 587 | # assert_raise(IOError) { w.read } 588 | end 589 | assert_true called 590 | 591 | assert_nothing_raised do 592 | IO.pipe { |r, w| r.close; w.close } 593 | end 594 | rescue NotImplementedError => e 595 | skip e.message 596 | end 597 | end 598 | 599 | assert('`cmd`') do 600 | begin 601 | assert_equal `echo foo`, "foo\n" 602 | rescue NotImplementedError => e 603 | skip e.message 604 | end 605 | end 606 | 607 | assert('IO TEST CLEANUP') do 608 | assert_nil MRubyIOTestUtil.io_test_cleanup 609 | end 610 | -------------------------------------------------------------------------------- /test/mruby_io_test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #if defined(_WIN32) || defined(_WIN64) 5 | #include 6 | #include 7 | #else 8 | #include 9 | #include 10 | #include 11 | #endif 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | #include "mruby.h" 18 | #include "mruby/array.h" 19 | #include "mruby/error.h" 20 | #include "mruby/string.h" 21 | #include "mruby/variable.h" 22 | 23 | static mrb_value 24 | mrb_io_test_io_setup(mrb_state *mrb, mrb_value self) 25 | { 26 | char rfname[] = "tmp.mruby-io-test.XXXXXXXX"; 27 | char wfname[] = "tmp.mruby-io-test.XXXXXXXX"; 28 | char symlinkname[] = "tmp.mruby-io-test.XXXXXXXX"; 29 | char socketname[] = "/tmp/mruby-io-test.XXXXXXXX"; 30 | char msg[] = "mruby io test\n"; 31 | mode_t mask; 32 | int fd0, fd1, fd2, fd3; 33 | FILE *fp; 34 | 35 | #ifndef _WIN32 36 | struct sockaddr_un sun0; 37 | #endif 38 | 39 | mask = umask(077); 40 | fd0 = mkstemp(rfname); 41 | fd1 = mkstemp(wfname); 42 | #ifndef _WIN32 43 | fd2 = mkstemp(symlinkname); 44 | fd3 = mkstemp(socketname); 45 | #endif 46 | if (fd0 == -1 || fd1 == -1 || fd2 == -1 || fd3 == -1) { 47 | mrb_raise(mrb, E_RUNTIME_ERROR, "can't create temporary file"); 48 | return mrb_nil_value(); 49 | } 50 | umask(mask); 51 | 52 | mrb_gv_set(mrb, mrb_intern_cstr(mrb, "$mrbtest_io_rfname"), mrb_str_new_cstr(mrb, rfname)); 53 | mrb_gv_set(mrb, mrb_intern_cstr(mrb, "$mrbtest_io_wfname"), mrb_str_new_cstr(mrb, wfname)); 54 | mrb_gv_set(mrb, mrb_intern_cstr(mrb, "$mrbtest_io_symlinkname"), mrb_str_new_cstr(mrb, symlinkname)); 55 | mrb_gv_set(mrb, mrb_intern_cstr(mrb, "$mrbtest_io_socketname"), mrb_str_new_cstr(mrb, socketname)); 56 | mrb_gv_set(mrb, mrb_intern_cstr(mrb, "$mrbtest_io_msg"), mrb_str_new_cstr(mrb, msg)); 57 | 58 | fp = fopen(rfname, "wb"); 59 | if (fp == NULL) { 60 | mrb_raise(mrb, E_RUNTIME_ERROR, "can't open temporary file"); 61 | return mrb_nil_value(); 62 | } 63 | fputs(msg, fp); 64 | fclose(fp); 65 | 66 | fp = fopen(wfname, "wb"); 67 | if (fp == NULL) { 68 | mrb_raise(mrb, E_RUNTIME_ERROR, "can't open temporary file"); 69 | return mrb_nil_value(); 70 | } 71 | fclose(fp); 72 | 73 | #ifndef _WIN32 74 | unlink(symlinkname); 75 | close(fd2); 76 | if (symlink(rfname, symlinkname) == -1) { 77 | mrb_raise(mrb, E_RUNTIME_ERROR, "can't make a symbolic link"); 78 | } 79 | 80 | unlink(socketname); 81 | close(fd3); 82 | fd3 = socket(AF_UNIX, SOCK_STREAM, 0); 83 | if (fd3 == -1) { 84 | mrb_raise(mrb, E_RUNTIME_ERROR, "can't make a socket"); 85 | } 86 | sun0.sun_family = AF_UNIX; 87 | snprintf(sun0.sun_path, sizeof(sun0.sun_path), "%s", socketname); 88 | if (bind(fd3, (struct sockaddr *)&sun0, sizeof(sun0)) == -1) { 89 | mrb_raisef(mrb, E_RUNTIME_ERROR, "can't bind AF_UNIX socket to %S: %S", 90 | mrb_str_new_cstr(mrb, sun0.sun_path), 91 | mrb_fixnum_value(errno)); 92 | } 93 | close(fd3); 94 | #endif 95 | 96 | return mrb_true_value(); 97 | } 98 | 99 | static mrb_value 100 | mrb_io_test_io_cleanup(mrb_state *mrb, mrb_value self) 101 | { 102 | mrb_value rfname = mrb_gv_get(mrb, mrb_intern_cstr(mrb, "$mrbtest_io_rfname")); 103 | mrb_value wfname = mrb_gv_get(mrb, mrb_intern_cstr(mrb, "$mrbtest_io_wfname")); 104 | mrb_value symlinkname = mrb_gv_get(mrb, mrb_intern_cstr(mrb, "$mrbtest_io_symlinkname")); 105 | mrb_value socketname = mrb_gv_get(mrb, mrb_intern_cstr(mrb, "$mrbtest_io_socketname")); 106 | 107 | if (mrb_type(rfname) == MRB_TT_STRING) { 108 | remove(RSTRING_PTR(rfname)); 109 | } 110 | if (mrb_type(wfname) == MRB_TT_STRING) { 111 | remove(RSTRING_PTR(wfname)); 112 | } 113 | if (mrb_type(symlinkname) == MRB_TT_STRING) { 114 | remove(RSTRING_PTR(symlinkname)); 115 | } 116 | if (mrb_type(socketname) == MRB_TT_STRING) { 117 | remove(RSTRING_PTR(socketname)); 118 | } 119 | 120 | mrb_gv_set(mrb, mrb_intern_cstr(mrb, "$mrbtest_io_rfname"), mrb_nil_value()); 121 | mrb_gv_set(mrb, mrb_intern_cstr(mrb, "$mrbtest_io_wfname"), mrb_nil_value()); 122 | mrb_gv_set(mrb, mrb_intern_cstr(mrb, "$mrbtest_io_symlinkname"), mrb_nil_value()); 123 | mrb_gv_set(mrb, mrb_intern_cstr(mrb, "$mrbtest_io_socketname"), mrb_nil_value()); 124 | mrb_gv_set(mrb, mrb_intern_cstr(mrb, "$mrbtest_io_msg"), mrb_nil_value()); 125 | 126 | return mrb_nil_value(); 127 | } 128 | 129 | static mrb_value 130 | mrb_io_test_file_setup(mrb_state *mrb, mrb_value self) 131 | { 132 | mrb_value ary = mrb_io_test_io_setup(mrb, self); 133 | #ifndef _WIN32 134 | if (symlink("/usr/bin", "test-bin") == -1) { 135 | mrb_raise(mrb, E_RUNTIME_ERROR, "can't make a symbolic link"); 136 | } 137 | #endif 138 | 139 | return ary; 140 | } 141 | 142 | static mrb_value 143 | mrb_io_test_file_cleanup(mrb_state *mrb, mrb_value self) 144 | { 145 | mrb_io_test_io_cleanup(mrb, self); 146 | remove("test-bin"); 147 | 148 | return mrb_nil_value(); 149 | } 150 | 151 | static mrb_value 152 | mrb_io_test_mkdtemp(mrb_state *mrb, mrb_value klass) 153 | { 154 | mrb_value str; 155 | char *cp; 156 | 157 | mrb_get_args(mrb, "S", &str); 158 | cp = mrb_str_to_cstr(mrb, str); 159 | if (mkdtemp(cp) == NULL) { 160 | mrb_sys_fail(mrb, "mkdtemp"); 161 | } 162 | return mrb_str_new_cstr(mrb, cp); 163 | } 164 | 165 | static mrb_value 166 | mrb_io_test_rmdir(mrb_state *mrb, mrb_value klass) 167 | { 168 | mrb_value str; 169 | char *cp; 170 | 171 | mrb_get_args(mrb, "S", &str); 172 | cp = mrb_str_to_cstr(mrb, str); 173 | if (rmdir(cp) == -1) { 174 | mrb_sys_fail(mrb, "rmdir"); 175 | } 176 | return mrb_true_value(); 177 | } 178 | 179 | void 180 | mrb_mruby_io_gem_test(mrb_state* mrb) 181 | { 182 | struct RClass *io_test = mrb_define_module(mrb, "MRubyIOTestUtil"); 183 | mrb_define_class_method(mrb, io_test, "io_test_setup", mrb_io_test_io_setup, MRB_ARGS_NONE()); 184 | mrb_define_class_method(mrb, io_test, "io_test_cleanup", mrb_io_test_io_cleanup, MRB_ARGS_NONE()); 185 | 186 | mrb_define_class_method(mrb, io_test, "file_test_setup", mrb_io_test_file_setup, MRB_ARGS_NONE()); 187 | mrb_define_class_method(mrb, io_test, "file_test_cleanup", mrb_io_test_file_cleanup, MRB_ARGS_NONE()); 188 | 189 | mrb_define_class_method(mrb, io_test, "mkdtemp", mrb_io_test_mkdtemp, MRB_ARGS_REQ(1)); 190 | mrb_define_class_method(mrb, io_test, "rmdir", mrb_io_test_rmdir, MRB_ARGS_REQ(1)); 191 | } 192 | --------------------------------------------------------------------------------