commit b122f0a35779899572a459d53d75e2d6a9e2327a
Author: Kris Maglione <jg@suckless.org>
Date: Wed, 14 Mar 2007 18:55:34 -0400
Initial commit.
Diffstat:
r9p.rb | | | 15 | +++++++++++++++ |
r9p/PERMISSIONS | | | 12 | ++++++++++++ |
r9p/client.rb | | | 274 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
r9p/fcall.rb | | | 169 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
r9p/ixp.rb | | | 0 | |
r9p/mux.rb | | | 239 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
r9p/qid.rb | | | 45 | +++++++++++++++++++++++++++++++++++++++++++++ |
r9p/serial.rb | | | 181 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
r9p/stat.rb | | | 63 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
r9p/test.rb | | | 65 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
r9p/thread.rb | | | 117 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
11 files changed, 1180 insertions(+), 0 deletions(-)
diff --git a/r9p.rb b/r9p.rb
@@ -0,0 +1,15 @@
+$debug ||= 0
+
+module R9P
+ NoFid = ~0
+ NoTag = ~0
+ P9Version = '9P2000'
+end
+
+require 'r9p/thread.rb'
+require 'r9p/mux.rb'
+require 'r9p/serial.rb'
+require 'r9p/qid.rb'
+require 'r9p/stat.rb'
+require 'r9p/fcall.rb'
+require 'r9p/client.rb'
diff --git a/r9p/PERMISSIONS b/r9p/PERMISSIONS
@@ -0,0 +1,12 @@
+Copyright (C) 2007 Kris Maglione <fbsdaemon@gmail.com>
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
diff --git a/r9p/client.rb b/r9p/client.rb
@@ -0,0 +1,274 @@
+# Copyright (C) 2007 Kris Maglione
+# See PERMISSIONS
+
+module R9P
+ class Exception <Exception
+ end
+ class ProtocolException <Exception
+ end
+ class Client
+ RootFid = 0
+ module Constants
+ OREAD = 0x00
+ OWRITE = 0x01
+ ORDWR = 0x02
+ OEXEC = 0x03
+ OEXCL = 0x04
+ OTRUNC = 0x10
+ OREXEC = 0x20
+ ORCLOSE = 0x40
+ OAPPEND = 0x80
+ end
+ include Constants
+ include Qlock
+ attr_reader :msize
+
+ def initialize(con, root = nil)
+ qlinit
+ @lastfid = RootFid
+ @fids = []
+ @files = {}
+ @mux = Mux.new(con, :maxtag => 256) { |dat|
+ fcall = Fcall.from dat
+ }
+
+ resp = dorpc Fcall::Tversion, :version => P9Version, :msize => 65535
+ if resp.version != P9Version
+ throw ProtocolException, "Can't speak 9P version '#{resp.version}'"
+ end
+ @msize = resp.msize
+
+ dorpc Fcall::Tattach, :fid => RootFid, :afid => NoFid, :uname => ENV['USER'], :aname => ''
+
+ if root
+ path = root.split('/').reject{|v| v=='' }
+ resp = dorpc Fcall::Twalk, :fid => RootFid, :newfid => RootFid, :wname => path
+ end
+
+ if block_given?
+ begin
+ yield self
+ ensure
+ cleanup
+ end
+ end
+ return
+ ensure
+ @mux.fd.close if @mux
+ end
+
+ def cleanup
+ @files.each_value { |f|
+ f.close
+ }
+ ensure
+ @mux.fd.close
+ end
+
+ def dorpc(clas, *args)
+ fcall = clas.new *args
+ resp = @mux.rpc fcall
+ if resp.kind_of? Fcall::Rerror
+ throw Exception, "RPC returned error: #{fcall.class.inspect} #{fcall.fields.inspect}: #{resp.ename}"
+ end
+ if fcall.type != resp.type ^ 1
+ throw ProtocolException, "Missmatched RPC message types: #{fcall.class.inspect} => #{resp.class.inspect}"
+ end
+ resp
+ end
+
+ def getfid
+ qsync {
+ @fids.pop || @lastfid += 1
+ }
+ end
+ def putfid(fid)
+ qsync {
+ if @files.delete(fid)
+ @fids.push fid
+ else
+ throw Exception, "Attempt to free unused fid"
+ end
+ }
+ end
+
+ def open(path, mode = OREAD)
+ path = path.split('/').reject{|v| v=='' }
+ fid = getfid
+
+ resp = dorpc Fcall::Twalk, :fid => RootFid, :newfid => fid, :wname => path
+ file = File.new self, fid, mode
+ @files[fid] = file
+
+ if block_given?
+ begin
+ yield file
+ ensure
+ file.close
+ end
+ else
+ file
+ end
+ end
+
+ class File
+ include Stat::Constants
+ include Client::Constants
+ include Qlock
+ def initialize(client, fid, mode)
+ qlinit
+ @fid = fid
+ @client = client
+ @offset = 0
+ @mode = mode
+ @fd = nil
+
+ fcall = dorpc Fcall::Topen, :mode => mode
+ @iounit = fcall.iounit
+ @qid = fcall.qid
+ end
+
+ def stat
+ resp = dorpc Fcall::Tstat
+ resp.stat
+ end
+
+ def read(count = @iounit, offset = @offset, buf = '')
+ qsync {
+ while count > 0
+ n = [count, @iounit].min
+ count -= n
+
+ resp = dorpc Fcall::Tread, :offset => @offset, :count => n
+ data = resp.data
+
+ @offset += data.length
+ buf << data
+
+ if data.length < n
+ break
+ end
+ end
+ }
+ if buf.length == 0
+ nil
+ else
+ buf
+ end
+ end
+ def write(data, offset = @offset)
+ off = 0
+ qsync {
+ while off < data.length
+ n = [data.length, @iounit].min
+
+ resp = dorpc Fcall::Twrite, :offset => @offset, :data => data[off..off+n-1]
+
+ off += resp.count
+ @offset += resp.count
+ if resp.count < n
+ break
+ end
+ end
+ }
+ off
+ end
+
+ def close
+ fd_close if @fd
+ ensure
+ qsync {
+ dorpc Fcall::Tclunk
+ }
+ @client.putfid @fid
+
+ @tg = @fd = @fid = @client = @qid = nil
+ end
+ def fd_close
+ @fd.close
+ ensure
+ @tg.list.each { |thr|
+ thr.kill if thr.alive?
+ thr.join
+ }
+ end
+
+ def to_fd
+ if @fd
+ throw Exception, "File descriptor already open for file"
+ end
+
+ fds = Socket::pair Socket::PF_UNIX, Socket::SOCK_STREAM, 0
+ @tg = ThreadGroup.new
+ mtx = Mutex.new
+
+ reader = nil
+ writer = nil
+ mtx.lock
+ @tg.add reader = Thread.new(fds[1]) { |fd|
+ mtx.synchronize {
+ unless (@mode&3) == OWRITE || (@mode&3) == ORDWR
+ fd.close unless writer.alive?
+ return
+ end
+ }
+ while data = fd.sysread(@iounit)
+ if write(data) == 0
+ break
+ end
+ end
+ mtx.synchronize {
+ if fd.eof
+ writer.terminate
+ fd.close
+ elsif !writer.alive?
+ fd.close
+ end
+ }
+ }
+ @tg.add writer = Thread.new(fds[1]) { |fd|
+ mtx.synchronize {
+ unless (@mode&3) == OREAD || (@mode&3) == ORDWR
+ fd.close unless reader.alive
+ return
+ end
+ }
+ len = 1
+ while data = read
+ while data.length > 0
+ len = fd.write data
+ if len == 0
+ break
+ end
+ data.slice!(0..len-1)
+ end
+ if len == 0
+ break
+ end
+ end
+ mtx.synchronize {
+ reader.terminate
+ fd.close
+ }
+ }
+ mtx.unlock
+
+ @fd = fds[0]
+ if block_given?
+ begin
+ yield @fd
+ ensure
+ fd_close
+ end
+ else
+ @fd
+ end
+ end
+
+ def dorpc(clas, args = {})
+ @client.dorpc clas, args.merge!(:fid => @fid)
+ end
+ private :dorpc
+ end
+ end
+end
diff --git a/r9p/fcall.rb b/r9p/fcall.rb
@@ -0,0 +1,169 @@
+# Copyright (C) 2007 Kris Maglione
+# See PERMISSIONS
+
+module R9P
+ class Fcall <Serial
+ Types = []
+ field :size, :dword, :size, 4
+ field :type, :byte
+ field :tag, :word
+
+ def self.type(n)
+ Types[n] = self
+ @type = n
+ end
+ def self.from(data)
+ type = data[4]
+ Types[type].new data
+ end
+
+ def initialize(data = nil)
+ fields[:type] = self.class.module_eval { @type }
+ super(data)
+ end
+
+ def succ(args = {})
+ Types[type+1].new args.merge(:tag => tag)
+ end
+
+ class Tversion <Fcall
+ type 100
+ field :msize, :dword
+ field :version, :word, :string
+ end
+ class Rversion <Fcall
+ type 101
+ field :msize, :dword
+ field :version, :word, :string
+ end
+
+ class Tauth <Fcall
+ type 102
+ field :afid, :dword
+ field :uname, :word, :string
+ field :aname, :word, :string
+ end
+ class Rauth <Fcall
+ type 103
+ field :aqid, Qid
+ end
+
+ class Tattach <Fcall
+ type 104
+ field :fid, :dword
+ field :afid, :dword
+ field :uname, :word, :string
+ field :aname, :word, :string
+ end
+ class Rattach <Fcall
+ type 105
+ field :qid, Qid
+ end
+
+ class Terror <Fcall
+ type 106
+ def initialize
+ throw "Illegal 9P tag 'Terror' encountered"
+ end
+ end
+ class Rerror <Fcall
+ type 107
+ field :ename, :word, :string
+ end
+
+ class Tflush <Fcall
+ type 108
+ field :oldtag, :word
+ end
+ class Rflush <Fcall
+ type 109
+ end
+
+ class Twalk <Fcall
+ type 110
+ field :fid, :dword
+ field :newfid, :dword
+ field :wname, :word, [ :word, :string ]
+ end
+ class Rwalk <Fcall
+ type 111
+ field :wqid, :word, [ Qid ]
+ end
+
+ class Topen <Fcall
+ type 112
+ field :fid, :dword
+ field :mode, :byte
+ end
+ class Ropen <Fcall
+ type 113
+ field :qid, Qid
+ field :iounit, :dword
+ end
+
+ class Tcreate <Fcall
+ type 114
+ field :fid, :dword
+ field :name, :word, :string
+ field :perm, :dword
+ field :mode, :byte
+ end
+ class Rcreate <Fcall
+ type 115
+ field :qid, Qid
+ field :iounit, :dword
+ end
+
+ class Tread <Fcall
+ type 116
+ field :fid, :dword
+ field :offset, :qword
+ field :count, :dword
+ end
+ class Rread <Fcall
+ type 117
+ field :data, :dword, :string
+ end
+
+ class Twrite <Fcall
+ type 118
+ field :fid, :dword
+ field :offset, :qword
+ field :data, :dword, :string
+ end
+ class Rwrite <Rread
+ type 119
+ end
+
+ class Tclunk <Fcall
+ type 120
+ field :fid, :dword
+ end
+ class Rclunk <Fcall
+ type 121
+ end
+
+ class Tremove <Tclunk
+ type 122
+ end
+ class Rremove <Fcall
+ type 123
+ end
+
+ class Tstat <Tclunk
+ type 124
+ end
+ class Rstat <Fcall
+ type 125
+ field :sstat, :word, :size, 2
+ field :stat, Stat
+ end
+
+ class Twstat <Rstat
+ type 126
+ end
+ class Rwstat <Fcall
+ type 127
+ end
+ end
+end
diff --git a/r9p/ixp.rb b/r9p/ixp.rb
diff --git a/r9p/mux.rb b/r9p/mux.rb
@@ -0,0 +1,239 @@
+# Derived from libmux, available in Plan 9 under /sys/src/libmux
+# under the following terms:
+#
+# Copyright (C) 2003-2006 Russ Cox, Massachusetts Institute of Technology
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+require 'socket'
+
+module R9P
+ class Mux
+ include Qlock
+ include Rendez
+
+ attr_reader :fd
+
+ def initialize(con, opts = {}, &block)
+ qlinit
+ @queue = []
+ @lock = self
+ @outlock = Mutex.new
+ @inlock = Mutex.new
+ @deserialize = block
+ @freetag = 0
+ @wait = []
+ @mwait = 0
+ @nwait = 0
+ @mintag = opts[:mintag] || 0
+ @maxtag = opts[:maxtag] || 1<<16-1
+
+ case con
+ when IO
+ @fd = con
+ when String
+ spec = con.split '!'
+ case spec.shift
+ when 'unix'
+ print "#{self.class.inspect}: init UNIXSocket: #{spec[0]}\n" if $debug > 1
+ @fd = UNIXSocket.new spec[0]
+ when 'tcp'
+ @fd = TCPSocket.new *spec
+ else
+ throw "Invalid connection spec"
+ end
+ else
+ throw "Invalid connection spec"
+ end
+ if @fd == nil
+ throw "No connection"
+ end
+ end
+
+ def rpc(dat)
+ r = newrpc dat
+
+ begin
+ qlock
+ while @muxer && @muxer != r && r.data == nil
+ r.sleep
+ end
+
+ if r.data == nil
+ if @muxer && @muxer != r
+ fail
+ end
+ @muxer = r
+ qunlock
+ begin
+ while r.data == nil
+ data = recv
+ if data == nil
+ qlock
+ @queue.delete r
+ throw "unexpected eof"
+ end
+ dispatch data
+ end
+ qlock
+ ensure
+ electmuxer
+ end
+ end
+
+ ensure
+ puttag(r)
+ qunlock
+ end
+ r.data
+ end
+
+ def electmuxer
+ @queue.each { |rpc|
+ if @muxer != rpc && rpc.async == false
+ @muxer = rpc
+ rpc.wakeup
+ return
+ end
+ }
+ @muxer = nil
+ end
+
+ def dispatch(dat)
+ tag = dat.tag - @mintag
+ r = nil
+ qsync {
+ if tag < 0 || tag > @mwait
+ printf "#{$0}: bad rpc tag: %ux\n", dat.tag
+ return
+ end
+ r = @wait[tag]
+ if r == nil || @queue.delete(r) == nil
+ printf "#{$0}: bad rpc tag: %ux (noone waiting on it)\n", dat.tag
+ return
+ end
+ }
+ r.data = dat
+ r.wakeup
+ end
+
+ def gettag(r)
+ tag = 0
+
+ while @nwait == @mwait
+ if @mwait < @maxtag - @mintag
+ mw = if @mwait == 0
+ 1
+ else
+ @mwait << 1
+ end
+ @wait[mw] = nil
+ @freetag = @mwait
+ @mwait = mw
+ break
+ end
+ sleep
+ end
+
+ tag = @freetag
+ if @wait[tag]
+ tag.upto(@mwait) { |n|
+ if @wait[n] == nil
+ tag = n
+ break
+ end
+ }
+ 0.upto(@freetag) { |n|
+ if @wait[n] == nil
+ tag = n
+ break
+ end
+ }
+ end
+
+ if @wait[tag]
+ throw "nwait botch"
+ end
+
+ @nwait += 1
+ @wait[tag] = r
+ r.tag = tag + @mintag
+ r.data.tag = r.tag
+ r.data = nil
+ r.tag
+ end
+ def puttag(r)
+ t = r.tag
+ if @wait[t] != r
+ fail
+ end
+ @wait[t] = nil
+ @nwait -= 1
+ @freetag = t
+ wakeup
+ end
+
+ def send(dat)
+ dat.serialize
+ len = @fd.syswrite dat.serial
+ print "send(#{dat.class.inspect}): len=#{len} data=#{dat.serial.inspect}" if $debug > 1
+
+ if len < dat.serial.length
+ return false
+ end
+ true
+ end
+ def recv
+ data = @fd.read 4
+ if data
+ len, = data.unpack "V"
+ data << @fd.read(len-4)
+ print "#{self.class.inspect}: recv: data=#{data.inspect}" if $debug > 1
+ @deserialize.call data
+ end
+ end
+
+ def newrpc(dat)
+ rpc = Rpc.new self
+ tag = nil
+
+ rpc.data = dat
+ qsync {
+ gettag rpc
+ @queue << rpc
+ }
+
+ if rpc.tag < 0 || send(dat) == false
+ qsync {
+ @queue.delete rpc
+ puttag rpc
+ }
+ return nil
+ end
+
+ rpc
+ end
+ private :gettag, :puttag,:send, :recv, :newrpc, :electmuxer, :dispatch
+
+ class Rpc
+ include Rendez
+ attr_accessor :data, :waiting, :async, :tag
+
+ def initialize(mux)
+ @lock = mux
+ @mux = mux
+ @data = nil
+ @waiting = true
+ @async = false
+ end
+ end
+ end
+end
diff --git a/r9p/qid.rb b/r9p/qid.rb
@@ -0,0 +1,45 @@
+# Copyright (C) 2007 Kris Maglione
+# See PERMISSIONS
+
+module R9P
+ class Qid <Serial
+ module Constants
+ QTFILE = 0x00
+ QTLINK = 0x01
+ QTSYMLINK = 0x02
+ QTTMP = 0x04
+ QTAUTH = 0x08
+ QTMOUNT = 0x10
+ QTEXCL = 0x20
+ QTAPPEND = 0x40
+ QTDIR = 0x80
+ end
+ include Constants
+
+ class Type <Serial
+ field :type, :byte
+
+ alias to_i type
+
+ def to_s
+ const = Qid.constants.grep(/^QT/)
+ const.collect! { |c|
+ v = Qid.const_get(c)
+ if type&v == v
+ c
+ end
+ }
+ const.reject! {|i| i == nil}
+ const.join '|'
+ end
+ end
+
+ field :type, Type
+ field :vers, :dword
+ field :path, :qword
+
+ def to_s
+ "type: #{type} vers: #{vers} path: #{path}"
+ end
+ end
+end
diff --git a/r9p/serial.rb b/r9p/serial.rb
@@ -0,0 +1,181 @@
+# Copyright (C) 2007 Kris Maglione
+# See PERMISSIONS
+
+module R9P
+ class Serial
+ Conv = {
+ :byte => [ 1, 'C' ],
+ :word => [ 2, 'v' ],
+ :dword => [ 4, 'V' ],
+ :qid => [ 13, 'CVVV' ],
+ }
+
+ attr_reader :fields, :serial, :opstack
+
+ def initialize(data)
+ @fields ||= {}
+ @serial ||= ''
+ @opstack = []
+ print "#{self.class.inspect}.new(#{data.inspect})\n" if $debug > 5
+ case data
+ when String
+ deserialize data
+ when Hash
+ fields.merge! data
+ end
+ print "\tFields: #{fields.inspect}\n\n" if $debug > 5
+ end
+
+ def deserialize(data = @serial)
+ @opstack = []
+ @serial = data
+ unstack self.class.field_stack
+ self
+ end
+ def serialize
+ print "serialize #{self.inspect}\n" if $debug > 4
+ @opstack = []
+ @serial = stack self.class.field_stack
+ end
+
+ def fields
+ @fields ||= {}
+ end
+ def opstack
+ @opstack
+ end
+ def serial
+ @serial ||= ""
+ end
+
+ def slice
+ if @offset > 0
+ @serial.slice!(0..@offset-1)
+ @offset = 0
+ end
+ end
+ def unstack(stack)
+ print "\nunstack(stack): #{self.class.inspect}\n" if $debug > 4
+ @offset = 0
+ stack.each { |op|
+ print "\top: #{op.inspect} top: #{@opstack[-1].inspect}\n" if $debug > 4
+ print "\tserial: #{@serial.inspect}\n" if $debug > 6
+ case op
+ when Field
+ fields[op.name] = pop
+ when Array
+ list = (1..pop).to_a.collect {
+ unstack op
+ pop
+ }
+ push list
+ when :string
+ num = pop
+ str, = unpack num, "a#{num}"
+ push str
+ when :qword
+ low, high = unpack 8, "VV"
+ push high<<32|low
+ when :size
+ when Symbol
+ push unpack(*Conv[op])[0]
+ when Module
+ if op <= Serial
+ slice
+ push op.new(@serial)
+ end
+ end
+ }
+ print "\n/unstack(stack): #{self.class.inspect}\n" if $debug > 4
+ slice
+ self
+ end
+ def stack(stack)
+ print "\nstack(stack): #{self.class.inspect}\n" if $debug > 4
+ stack.reverse.each { |op|
+ print "\top: #{op.inspect} top: #{@opstack[-1].inspect}\n" if $debug > 4
+ print "\tstack: #{@opstack.inspect}\n" if $debug > 5
+ print "\tserial: #{@serial.inspect}\n" if $debug > 5
+ case op
+ when Field
+ push @fields[op.name]
+ when Array
+ list = pop
+ list.reverse.each { |i|
+ push i
+ stack op
+ }
+ push list.length
+ when :string
+ str = pop
+ push str
+ push str.length
+ when :qword
+ num = pop
+ push [num, num >> 32].pack("VV")
+ when :size
+ add = pop
+ push @opstack.inject(add) { |l, o| l + o.length }
+ when Symbol
+ push [pop].pack(Conv[op][1])
+ when Module
+ if op <= Serial
+ push op.new(@serial).serialize
+ end
+ else
+ pop
+ push op
+ end
+ }
+ print "\n/stack(stack): #{self.class.inspect}\n" if $debug > 4
+ @opstack.reverse.join ''
+ end
+
+ def push(arg)
+ @opstack.push arg
+ end
+ def pop
+ @opstack.pop
+ end
+ def unpack(length, fmt)
+ print "unpack(#{length}, #{fmt}) @offset: #{@offset}\n" if $debug > 5
+ @offset += length
+ @serial.unpack("@#{@offset-length}#{fmt}")
+ end
+
+ def to_s
+ "fields: #{@fields.inspect}, serial: #{@serial.inspect}, stack: #{@opstack.inspect}"
+ end
+ def inspect
+ "#<#{self.class.inspect} #{to_s}>"
+ end
+
+ class Field
+ attr_reader :name
+ def initialize(name)
+ @name = name
+ end
+ end
+
+ class <<self
+ def field_stack
+ @field_stack ||=if self.superclass <= Serial
+ self.superclass.field_stack.clone
+ else
+ []
+ end
+ end
+ def field(name, *rest)
+ module_eval <<-"end_eval"
+ def #{name.id2name}
+ @fields[#{name.inspect}]
+ end
+ def #{name.id2name}=(val)
+ @fields[#{name.inspect}] = val
+ end
+ end_eval
+ self.field_stack.concat(rest) << Field.new(name)
+ end
+ end
+ end
+end
diff --git a/r9p/stat.rb b/r9p/stat.rb
@@ -0,0 +1,63 @@
+# Copyright (C) 2007 Kris Maglione
+# See PERMISSIONS
+
+module R9P
+ class Stat <Serial
+ module Constants
+ DMDIR =0x80000000
+ DMAPPEND =0x40000000
+ DMEXCL =0x20000000
+ DMMOUNT =0x10000000
+ DMAUTH =0x08000000
+ DMTMP =0x04000000
+ DMSYMLINK =0x02000000
+ DMDEVICE =0x00800000
+ DMNAMEDPIPE =0x00200000
+ DMSOCKET =0x00100000
+ DMSETUID =0x00080000
+ DMSETGID =0x00040000
+ end
+ include Constants
+
+ class Mode <Serial
+ field :mode, :dword
+ alias_method :to_i, :mode
+ def to_s
+ modes = 'rwxrwxrwx'
+ 0.upto(8) { |i|
+ if mode&(1 << i) == 0
+ modes[-i-1] = '-'
+ end
+ }
+ if mode&Stat::DMDIR > 0
+ 'd'
+ elsif mode&Stat::DMAPPEND > 0
+ 'a'
+ else
+ '-'
+ end + modes
+ end
+ end
+
+ field :size, :word, :size, 2
+ field :type, :word
+ field :dev, :dword
+ field :qid, Qid
+ field :mode, Mode
+ field :atime, :dword
+ field :atime, :dword
+ field :length, :qword
+ field :name, :word, :string
+ field :uid, :word, :string
+ field :gid, :word, :string
+ field :muid, :word, :string
+
+ def self.from_data(data)
+ list = []
+ while data.length > 0
+ list << new(data)
+ end
+ list
+ end
+ end
+end
diff --git a/r9p/test.rb b/r9p/test.rb
@@ -0,0 +1,65 @@
+require 'r9p.rb'
+
+module R9P
+ include Client::Constants
+
+ Client.new('unix!/tmp/ns.kris.:1/wmii') { |c|
+ c.open("event", OWRITE) { |f|
+ f.to_fd { |fd|
+ fd.print "Foo!\n"
+ }
+ }
+ c.open("keys") { |f|
+ print f.read
+ }
+ }
+end
+
+=begin
+
+include Client::Constants
+
+$pipe = UNIXSocket.new "/tmp/ns.kris.:1/wmii"
+#$pipe = IO.for_fd(0, "r+")
+
+def dorpc(fcall)
+ p fcall
+ fcall.serialize
+ $pipe.write(fcall.serial)
+
+ data = $pipe.read(4)
+ size, = data.unpack("V")
+ data << $pipe.read(size-data.length)
+
+ fcall = Fcall.from(data)
+ p fcall
+ print "\n\n"
+ fcall
+end
+
+$debug = 0
+
+rversion =dorpc Fcall::Tversion.new(:tag => 1, :msize => 8192, :version => "9P2000")
+dorpc Fcall::Tattach.new(:tag => 1, :fid => 1, :afid => ~0, :uname => "kris", :aname => "kris")
+dorpc Fcall::Twalk.new(:tag => 1, :fid => 1, :newfid => 3, :wname => [ "ctl" ])
+dorpc Fcall::Tstat.new(:tag => 1, :fid => 3, :offset => 0, :count => 500)
+dorpc Fcall::Topen.new(:tag => 1, :fid => 3, :mode => OREAD)
+
+off = 0
+begin
+ fcall = dorpc Fcall::Tread.new(:tag => 1, :fid => 3, :offset => off, :count => rversion.msize)
+ print fcall.data
+ off += fcall.data.length
+end while fcall.data.length > 0
+
+print "Done.\n"
+
+exit
+
+fcall = dorpc Fcall::Tread.new(:tag => 1, :fid => 3, :offset => 0, :count => 500)
+print "Stats: "
+Stat.from_data(fcall.data).each do |i|
+ i.fields.each_pair { |i, j| print "#{i} => #{j}\n" }
+ print "\n"
+end
+=end
diff --git a/r9p/thread.rb b/r9p/thread.rb
@@ -0,0 +1,117 @@
+# Copyright (C) 2007 Kris Maglione
+# See PERMISSIONS
+
+begin
+ require 'thread'
+rescue
+ # Threading is not necessary for most functions
+ module Stub
+ class <<self
+ def nops(*arg)
+ arg.each { |s|
+ module_eval <<-"end_eval"
+ alias #{s.id2name} nop
+ end_eval
+ }
+ end
+ def error(name, *arg)
+ first = arg.shift
+ module_eval <<-"end_eval"
+ def #{first.id2name}(*arg)
+ throw #{name.inspect}
+ end
+ end_eval
+ arg.each { |s| module_eval "alias #{s.id2name} #{first.id2name}" }
+ end
+ end
+ def nop(*arg)
+ puts "In nop!"
+ if block_given?
+ yield
+ end
+ end
+ end
+ class Thread
+ include Stub
+ @@dummy = new
+ def current
+ @@dummy
+ end
+ Stub.error 'Threading not implemented', :initialize, :wakeup
+ Stub.nops :exclusive, :critical, :critical=
+
+ class <<self
+ Stub.error 'Threading not implemented', :fork, :start, :stop
+ end
+ end
+
+ # Locking is not necessary without threading
+ class Mutex
+ include Stub
+ Stub.nops :lock, :unlock, :synchronize, :initialize, :locked?
+ def locked?
+ false
+ end
+ end
+end
+
+module R9P
+ module Qlock
+ def qlinit
+ @ql_queue = []
+ @ql_locked = nil
+ end
+
+ def qlock
+ cr = Thread.critical
+ while (Thread.critical = true; @ql_locked != nil && @ql_locked != Thread.current)
+ unless @ql_queue.include?(Thread.current)
+ @ql_queue.push Thread.current
+ end
+ Thread.stop
+ end
+ @ql_locked = Thread.current
+ Thread.critical = cr
+ self
+ end
+ def qunlock
+ if @ql_locked != Thread.current
+ raise
+ end
+ cr = Thread.critical
+ Thread.critical = true
+ thr = @ql_locked = @ql_queue.shift
+ @ql_locked.wakeup if @ql_locked
+ if block_given?
+ yield
+ end
+ ensure
+ Thread.critical = cr
+ thr.run if thr
+ end
+ def qsync
+ qlock
+ yield
+ ensure
+ qunlock
+ end
+ def qlocked?
+ @ql_locked ? true : false
+ end
+ end
+ module Rendez
+ def sleep
+ @thread = Thread.current
+ @lock.qunlock {
+ Thread.stop
+ }
+ @lock.qlock
+ @thread = nil if @thread == Thread.current
+ end
+ def wakeup
+ if @thread
+ @thread.wakeup
+ end
+ end
+ end
+end