client.rb (6553B)
1 # Copyright (C) 2007 Kris Maglione 2 # See PERMISSIONS 3 4 module R9P 5 class Exception <Exception 6 end 7 class ProtocolException <R9P::Exception 8 end 9 class RPCError <R9P::Exception 10 end 11 class Client 12 RootFid = 0 13 module Constants 14 OREAD = 0x00 15 OWRITE = 0x01 16 ORDWR = 0x02 17 OEXEC = 0x03 18 OEXCL = 0x04 19 OTRUNC = 0x10 20 OREXEC = 0x20 21 ORCLOSE = 0x40 22 OAPPEND = 0x80 23 end 24 include Constants 25 include Qlock 26 27 def initialize(con, root = nil) 28 qlinit 29 @lastfid = RootFid 30 @fids = [] 31 @files = {} 32 @mux = Mux.new(con, :maxtag => 256) { |dat| 33 fcall = Fcall.from dat 34 } 35 36 resp = dorpc Fcall::Tversion, :version => R9P::Version, :msize => 65535 37 if resp.version != R9P::Version 38 raise ProtocolException, "Can't speak 9P version '#{resp.version}'" 39 end 40 @msize = resp.msize 41 42 dorpc Fcall::Tattach, :fid => RootFid, :afid => Fcall::NoFid, :uname => ENV['USER'], :aname => '' 43 44 if root 45 path = splitpath(root) 46 resp = dorpc Fcall::Twalk, :fid => RootFid, :newfid => RootFid, :wname => path 47 end 48 49 if block_given? 50 begin 51 yield self 52 ensure 53 cleanup 54 end 55 end 56 return 57 rescue 58 @mux.fd.close if @mux 59 raise 60 end 61 62 def cleanup 63 @files.each_value { |f| 64 f.close 65 } 66 ensure 67 @mux.fd.close 68 @mux = nil 69 end 70 71 def dorpc(clas, *args) 72 fcall = clas.new *args 73 resp = @mux.rpc fcall 74 if resp.kind_of? Fcall::Rerror 75 raise RPCError, "RPC returned error (#{fcall.class.inspect}: #{fcall.fields.inspect}): #{resp.ename}" 76 end 77 if fcall.type != resp.type ^ 1 78 raise ProtocolException, "Missmatched RPC message types: #{fcall.class.inspect} => #{resp.class.inspect}" 79 end 80 resp 81 end 82 83 def splitpath(path) 84 path.split('/').reject{|v| v=='' } 85 end 86 private:splitpath 87 88 def getfid 89 qsync { 90 @fids.pop || @lastfid += 1 91 } 92 end 93 def putfid(fid) 94 qsync { 95 @files.delete(fid) 96 @fids.push fid 97 } 98 end 99 private :getfid, :putfid 100 101 def clunk(fid) 102 dorpc Fcall::Tclunk, :fid => fid 103 ensure 104 putfid fid 105 end 106 private :clunk 107 108 def walk(path) 109 path = path.clone 110 fid = getfid 111 ofid = RootFid 112 begin 113 dorpc Fcall::Twalk, :fid => ofid, :newfid => fid, :wname => path.slice!(0..Fcall::MaxWelem-1) 114 ofid = fid 115 end while path.length > 0 116 begin 117 yield fid 118 rescue 119 clunk fid 120 raise 121 end 122 end 123 124 def _open(path, mode, block) 125 resp = fid = file = nil 126 127 walk(path) do |fid| 128 fid = fid 129 resp = yield(fid) 130 end 131 132 file = File.new(self, resp, fid, mode) do 133 clunk fid 134 end 135 @files[fid] = file 136 137 if block 138 begin 139 block.call file 140 ensure 141 file.close 142 end 143 else 144 file 145 end 146 end 147 private :_open, :walk 148 149 def open(path, mode = OREAD, &block) 150 path = splitpath(path) 151 152 _open(path, mode, block) do |fid| 153 dorpc Fcall::Topen, :fid => fid, :mode => mode 154 end 155 end 156 157 def create(path, mode = OREAD, perm = 0, &block) 158 path = splitpath(path) 159 name = path.pop 160 161 _open(path, mode, block) do |fid| 162 dorpc Fcall::Tcreate, :fid => fid, :mode => mode, :name => name, :perm => perm 163 end 164 end 165 166 def remove(path) 167 path = splitpath(path) 168 169 walk(path) do |fid| 170 dorpc Fcall::Tremove, :fid => fid 171 end 172 true 173 end 174 175 def stat(path) 176 path = splitpath(path) 177 178 walk(path) do |fid| 179 begin 180 resp = dorpc Fcall::Tstat, :fid => fid 181 resp.stat 182 ensure 183 clunk fid 184 end 185 end 186 rescue RPCError 187 return nil 188 end 189 190 class File 191 include Stat::Constants 192 include Client::Constants 193 include Qlock 194 attr_reader :fid, :qid, :mode, :offset, :iounit 195 def initialize(client, fcall, fid, mode, &cleanup) 196 qlinit 197 @client = client 198 @fid = fid 199 @cleanup = cleanup 200 @mode = mode 201 @iounit = fcall.iounit 202 @qid = fcall.qid 203 204 @offset = 0 205 @fd = nil 206 end 207 208 def dorpc(clas, args = {}) 209 @client.dorpc clas, args.merge!(:fid => @fid) 210 end 211 private :dorpc 212 213 def stat 214 resp = dorpc Fcall::Tstat 215 resp.stat 216 end 217 218 def read(count = @iounit, offset = @offset, buf = '') 219 qsync { 220 while count > 0 221 n = [count, @iounit].min 222 count -= n 223 224 resp = dorpc Fcall::Tread, :offset => @offset, :count => n 225 data = resp.data 226 227 @offset += data.length 228 buf << data 229 230 if data.length < n 231 break 232 end 233 end 234 } 235 if buf.length == 0 236 nil 237 else 238 buf 239 end 240 end 241 def write(data, offset = @offset) 242 off = 0 243 qsync { 244 while off < data.length 245 n = [data.length, @iounit].min 246 247 resp = dorpc Fcall::Twrite, :offset => @offset, :data => data[off..off+n-1] 248 249 off += resp.count 250 @offset += resp.count 251 if resp.count < n 252 break 253 end 254 end 255 } 256 off 257 end 258 def readdir 259 unless @qid.type.to_i&Qid::QTDIR 260 raise Exception, "Can only call readdir on a directory" 261 end 262 data = '' 263 begin 264 dat = read(@iounit, data.length) 265 data << dat if dat 266 end while dat 267 Stat.from_data data 268 end 269 270 def close 271 fd_close if @fd 272 ensure 273 @cleanup.call self 274 275 @tg = @fid = @client = @qid = nil 276 end 277 def fd_close 278 @fd.close 279 ensure 280 @fd = nil 281 @tg.list.each { |thr| 282 thr.kill if thr.alive? 283 thr.join 284 } 285 end 286 287 def to_fd 288 if @fd 289 raise Exception, "File descriptor already open for file" 290 end 291 292 fds = Socket::pair Socket::PF_UNIX, Socket::SOCK_STREAM, 0 293 @tg = ThreadGroup.new 294 mtx = Mutex.new 295 296 reader = nil 297 writer = nil 298 mtx.lock 299 @tg.add reader = Thread.new(fds[1]) { |fd| 300 mtx.synchronize { 301 unless (@mode&3) == OWRITE || (@mode&3) == ORDWR 302 fd.close unless writer.alive? 303 return 304 end 305 } 306 while data = fd.sysread(@iounit) 307 if write(data) == 0 308 break 309 end 310 end 311 mtx.synchronize { 312 if fd.eof 313 writer.terminate 314 fd.close 315 elsif !writer.alive? 316 fd.close 317 end 318 } 319 } 320 @tg.add writer = Thread.new(fds[1]) { |fd| 321 mtx.synchronize { 322 unless (@mode&3) == OREAD || (@mode&3) == ORDWR 323 fd.close unless reader.alive 324 return 325 end 326 } 327 len = 1 328 while data = read 329 while data.length > 0 330 len = fd.write data 331 if len == 0 332 break 333 end 334 data.slice!(0..len-1) 335 end 336 if len == 0 337 break 338 end 339 end 340 mtx.synchronize { 341 reader.terminate 342 fd.close 343 } 344 } 345 mtx.unlock 346 347 @fd = fds[0] 348 if block_given? 349 begin 350 yield @fd 351 ensure 352 fd_close 353 end 354 else 355 @fd 356 end 357 end 358 end 359 end 360 end