r9p

git clone git://oldgit.suckless.org/r9p/
Log | Files | Refs

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