rstyx 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/COPYING +482 -0
- data/ChangeLog +4 -0
- data/Manifest.txt +17 -0
- data/NEWS +2 -0
- data/README +41 -0
- data/Rakefile +55 -0
- data/examples/readstyxfile.rb +48 -0
- data/lib/rstyx/authmodules.rb +90 -0
- data/lib/rstyx/client.rb +709 -693
- data/lib/rstyx/common.rb +71 -0
- data/lib/rstyx/errors.rb +35 -2
- data/lib/rstyx/messages.rb +377 -1113
- data/lib/rstyx/server.rb +1305 -0
- data/lib/rstyx/version.rb +2 -2
- data/lib/rstyx.rb +11 -4
- data/tests/tc_client.rb +260 -45
- data/tests/tc_message.rb +245 -388
- data/tests/tc_styxservproto.rb +596 -0
- metadata +51 -26
data/lib/rstyx/client.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
#!/usr/bin/ruby
|
2
2
|
#
|
3
|
-
# Copyright (C) 2005 Rafael Sevilla
|
3
|
+
# Copyright (C) 2005-2007 Rafael Sevilla
|
4
4
|
# This file is part of RStyx
|
5
5
|
#
|
6
6
|
# RStyx is free software; you can redistribute it and/or modify
|
@@ -15,646 +15,599 @@
|
|
15
15
|
#
|
16
16
|
# You should have received a copy of the GNU Lesser General Public
|
17
17
|
# License along with RStyx; if not, write to the Free Software
|
18
|
-
# Foundation, Inc.,
|
19
|
-
#
|
18
|
+
# Foundation, Inc., 51 Franklin St., Fifth Floor, Boston, MA
|
19
|
+
# 02110-1301 USA.
|
20
20
|
#
|
21
21
|
# Styx Client
|
22
22
|
#
|
23
23
|
# Author:: Rafael R. Sevilla (mailto:dido@imperium.ph)
|
24
|
-
# Copyright:: Copyright (c) 2005 Rafael R. Sevilla
|
24
|
+
# Copyright:: Copyright (c) 2005-2007 Rafael R. Sevilla
|
25
25
|
# License:: GNU Lesser General Public License
|
26
26
|
#
|
27
|
-
# $Id: client.rb
|
27
|
+
# $Id: client.rb 201 2007-06-08 17:13:58Z dido $
|
28
28
|
#
|
29
29
|
|
30
|
-
require 'English'
|
31
|
-
require 'socket'
|
32
30
|
require 'thread'
|
33
31
|
require 'timeout'
|
34
|
-
require '
|
35
|
-
require '
|
32
|
+
require 'rubygems'
|
33
|
+
require 'eventmachine'
|
36
34
|
|
37
35
|
module RStyx
|
38
36
|
|
39
37
|
module Client
|
40
38
|
|
41
39
|
##
|
42
|
-
#
|
40
|
+
# Message receiving module for the Styx client. The client will
|
41
|
+
# assemble all inbound messages.
|
43
42
|
#
|
44
|
-
|
43
|
+
module StyxClient
|
44
|
+
attr_accessor :sentmessages
|
45
|
+
|
46
|
+
def post_init
|
47
|
+
@msgbuffer = ""
|
48
|
+
@lock = Mutex.new
|
49
|
+
@sentmessages = {}
|
50
|
+
end
|
51
|
+
|
45
52
|
##
|
46
|
-
#
|
53
|
+
# Send a message asynchronously.
|
47
54
|
#
|
48
|
-
# +
|
49
|
-
# +
|
50
|
-
#
|
51
|
-
# return:: an unused value
|
55
|
+
# +message+:: [StyxMessage] the message to be sent
|
56
|
+
# +block+:: [Proc] the callback to use
|
57
|
+
# return:: [Fixnum] the tag number used.
|
52
58
|
#
|
53
|
-
def
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
59
|
+
def send_message_async(message, &block)
|
60
|
+
# store the message and callback indexed by tag
|
61
|
+
@lock.synchronize do
|
62
|
+
if message.tag.nil?
|
63
|
+
# If a tag has not been explicitly specified, get
|
64
|
+
# a new tag for the message. We use the current
|
65
|
+
# thread's object ID as the base and use what
|
66
|
+
# amounts to a linear probing algorithm to
|
67
|
+
# determine a new tag in case of collisions.
|
68
|
+
tag = Thread.current.object_id % MAX_TAG
|
69
|
+
while @sentmessages.has_key?(tag)
|
70
|
+
tag += 1
|
71
|
+
end
|
72
|
+
message.tag = tag
|
62
73
|
end
|
74
|
+
@sentmessages[message.tag] = [message, block]
|
63
75
|
end
|
64
76
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
return(val)
|
77
|
+
DEBUG > 0 && puts(" >> #{message.to_s}")
|
78
|
+
DEBUG > 1 && puts(" >> #{message.to_bytes.unpack("H*").inspect}")
|
79
|
+
send_data(message.to_bytes)
|
80
|
+
return(message.tag)
|
70
81
|
end
|
71
82
|
|
72
|
-
end
|
73
|
-
|
74
|
-
##
|
75
|
-
# An abstract class for Styx connections. DO NOT INSTANTIATE THIS
|
76
|
-
# CLASS DIRECTLY!
|
77
|
-
#
|
78
|
-
# A +Connection+ subclass instance acts in the same way as the
|
79
|
-
# +File+ class would under Ruby, and many of the class methods for
|
80
|
-
# File are implemented as instance methods for Connection, and are
|
81
|
-
# relevant to the Styx filesystem the Connection is connected to.
|
82
|
-
# There are a few differences: for instance, new is not equivalent
|
83
|
-
# to open, for obvious reasons.
|
84
|
-
#
|
85
|
-
class Connection
|
86
|
-
attr_reader :msize, :version, :rootfid, :user
|
87
|
-
|
88
83
|
##
|
89
|
-
#
|
90
|
-
#
|
91
|
-
# finishes.
|
84
|
+
# Send a message synchronously. If an error occurs, a
|
85
|
+
# StyxException is raised.
|
92
86
|
#
|
93
|
-
# +
|
87
|
+
# +message+:: The Styx message to send.
|
88
|
+
# +timeout+:: optional timeout for receiving the response.
|
94
89
|
#
|
95
|
-
def
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
90
|
+
def send_message(message, timeout=0)
|
91
|
+
# The queue here holds the response message, and is used
|
92
|
+
# to communicate with the receive_data thread, which ultimately
|
93
|
+
# calls the block passed to send_message_async.
|
94
|
+
queue = Queue.new
|
95
|
+
send_message_async(message) do |tx,rx|
|
96
|
+
# Enqueue the response message -- this runs in the
|
97
|
+
# receive_data thread
|
98
|
+
queue << rx
|
99
|
+
end
|
100
|
+
Timeout::timeout(timeout, StyxException.new("timeout waiting for a reply to #{message.to_s}")) do
|
101
|
+
# This will block until the queue contains something
|
102
|
+
resp = queue.shift
|
103
|
+
# Check the response to see if it is the response to
|
104
|
+
# the transmitted message.
|
105
|
+
if resp.class == Message::Rerror
|
106
|
+
raise StyxException.new("#{resp.ename}")
|
107
|
+
end
|
113
108
|
|
114
|
-
|
115
|
-
|
116
|
-
yield self
|
117
|
-
ensure
|
118
|
-
self.disconnect
|
109
|
+
if resp.ident != message.ident + 1
|
110
|
+
raise StyxException.new("Unexpected #{resp.to_s} received in response to #{message.to_s}")
|
119
111
|
end
|
120
|
-
|
121
|
-
return(self)
|
112
|
+
return(resp)
|
122
113
|
end
|
123
114
|
end
|
124
115
|
|
125
|
-
protected
|
126
|
-
|
127
116
|
##
|
128
|
-
#
|
129
|
-
# a subclass of IO (e.g. a TCPSocket) on which the read and write
|
130
|
-
# methods can be used.
|
117
|
+
# Receive data from the network connection, called by EventMachine.
|
131
118
|
#
|
132
|
-
def
|
133
|
-
|
134
|
-
|
119
|
+
def receive_data(data)
|
120
|
+
@msgbuffer << data
|
121
|
+
DEBUG > 1 && puts(" << #{data.unpack("H*").inspect}")
|
122
|
+
while @msgbuffer.length > 4
|
123
|
+
length = @msgbuffer.unpack("V")[0]
|
124
|
+
# Break out if there is not enough data in the message
|
125
|
+
# buffer to construct a message.
|
126
|
+
if @msgbuffer.length < length
|
127
|
+
break
|
128
|
+
end
|
135
129
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
rescue Exception => e
|
149
|
-
done = true
|
150
|
-
raise StyxException.new("error reading from socket: #{e.message}")
|
130
|
+
# Decode the received data
|
131
|
+
message, @msgbuffer = @msgbuffer.unpack("a#{length}a*")
|
132
|
+
styxmsg = Message::StyxMessage.from_bytes(message)
|
133
|
+
DEBUG > 0 && puts(" << #{styxmsg.to_s}")
|
134
|
+
# and look for its tag in the hash of received messages
|
135
|
+
tmsg, cb = @lock.synchronize do
|
136
|
+
@sentmessages.delete(styxmsg.tag)
|
137
|
+
end
|
138
|
+
|
139
|
+
if tmsg.nil?
|
140
|
+
# Discard unrecognized messages.
|
141
|
+
next
|
151
142
|
end
|
152
143
|
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
if
|
158
|
-
|
159
|
-
|
160
|
-
styxmsg = Message::StyxMessage.decode(message)
|
161
|
-
|
162
|
-
# let everyone know there's a new message available
|
163
|
-
@lock.synchronize do
|
164
|
-
@rmessages[styxmsg.tag] = styxmsg
|
165
|
-
@condvar.signal
|
144
|
+
if styxmsg.class == Message::Rflush
|
145
|
+
# We need to delete the oldtag as well, and send the
|
146
|
+
# rflush to the original sender if possible, so they
|
147
|
+
# don't wait forever.
|
148
|
+
if tmsg.respond_to?(:oldtag)
|
149
|
+
otmsg, ocb = @lock.synchronize do
|
150
|
+
@sentmessages.delete(tmsg.oldtag)
|
166
151
|
end
|
167
152
|
end
|
153
|
+
|
154
|
+
if !otmsg.nil? && !ocb.nil?
|
155
|
+
ocb.call(otmsg, styxmsg)
|
156
|
+
end
|
168
157
|
end
|
169
|
-
end
|
170
|
-
end
|
171
158
|
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
159
|
+
# Now, activate the callback block.
|
160
|
+
if !(tmsg.nil? || cb.nil?)
|
161
|
+
cb.call(tmsg, styxmsg)
|
162
|
+
end
|
163
|
+
|
164
|
+
# after all this is done, there may still be enough data in
|
165
|
+
# the message buffer for more messages so keep looping.
|
166
|
+
end
|
167
|
+
# If we get here, we don't have enough data in the buffer to
|
168
|
+
# build a new message.
|
179
169
|
end
|
180
170
|
|
181
171
|
##
|
182
|
-
#
|
183
|
-
#
|
184
|
-
# +message+:: [StyxMessage] the message to be sent
|
185
|
-
# return:: [Fixnum] the tag number used
|
172
|
+
# Disconnect from the remote server.
|
186
173
|
#
|
187
|
-
def
|
188
|
-
|
189
|
-
|
174
|
+
def disconnect
|
175
|
+
# flush all outstanding messages before disconnect
|
176
|
+
sentmessages.each_key do |tag|
|
177
|
+
rflush = send_message(Message::Tflush.new(:oldtag => tag))
|
190
178
|
end
|
191
179
|
|
192
|
-
|
193
|
-
|
194
|
-
# when we get the Rclunk corresponding to it.
|
195
|
-
if message.mtype == Message::StyxMessage::TCLUNK
|
196
|
-
@pendingclunks[message.tag] = message.fid
|
197
|
-
end
|
180
|
+
EventMachine::stop_event_loop
|
181
|
+
end
|
198
182
|
|
199
|
-
|
200
|
-
|
201
|
-
|
183
|
+
end # module StyxClient
|
184
|
+
|
185
|
+
class Connection
|
186
|
+
attr_accessor :usedfids, :pendingclunks, :umask
|
187
|
+
attr_reader :connectstate, :msize, :version
|
188
|
+
attr_reader :rootdirectory, :rootfid
|
189
|
+
|
190
|
+
def initialize(auth=nil)
|
191
|
+
@usedfids = []
|
192
|
+
@pendingclunks = {}
|
193
|
+
@rpendingclunks = {}
|
194
|
+
@conn = nil
|
195
|
+
@rootfid = nil
|
196
|
+
@eventthread = nil
|
197
|
+
@authenticator = auth
|
198
|
+
@clunklock = Mutex.new
|
199
|
+
@umask = ::File.umask
|
202
200
|
end
|
203
201
|
|
204
202
|
##
|
205
|
-
#
|
206
|
-
#
|
207
|
-
# +tag+:: [Fixnum] Tag to wait for
|
208
|
-
# return:: [Message::StyxMessage] the message received
|
203
|
+
# Get a new free FID.
|
209
204
|
#
|
210
|
-
def
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
205
|
+
def get_free_fid
|
206
|
+
found = false
|
207
|
+
val = nil
|
208
|
+
0.upto(MAX_FID) do |i|
|
209
|
+
unless @usedfids.include?(i)
|
210
|
+
val = i
|
211
|
+
break
|
216
212
|
end
|
217
|
-
msg = @rmessages[tag]
|
218
|
-
@rmessages.delete(tag)
|
219
|
-
end
|
220
|
-
|
221
|
-
# puts "Received message #{msg.to_s}"
|
222
|
-
# Special things to do for certain messages
|
223
|
-
case msg.mtype
|
224
|
-
when Message::StyxMessage::RERROR
|
225
|
-
# raise an exception when we receive an Rerror
|
226
|
-
raise StyxException.new(msg.ename)
|
227
|
-
when Message::StyxMessage::RCLUNK
|
228
|
-
# find the fid corresponding to the original Tclunk and free it
|
229
|
-
fid = @pendingclunks[tag]
|
230
|
-
@pendingclunks.delete(tag)
|
231
|
-
return_fid(fid)
|
232
213
|
end
|
233
|
-
return(msg)
|
234
|
-
end
|
235
214
|
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
def get_free_fid
|
242
|
-
fid = Util.get_first_unused(0xffffffff,
|
243
|
-
lambda {|i| @usedfids.include?(i) },
|
244
|
-
"fids")
|
245
|
-
@usedfids << fid
|
246
|
-
return(fid)
|
215
|
+
if val.nil?
|
216
|
+
raise StyxException.new("No more free fids")
|
217
|
+
end
|
218
|
+
@usedfids << val
|
219
|
+
return(val)
|
247
220
|
end
|
248
221
|
|
249
222
|
##
|
250
|
-
#
|
251
|
-
#
|
252
|
-
# +fid+:: [Fixnum] the fid to return
|
223
|
+
# Returns a fid after we're done using it.
|
253
224
|
#
|
254
225
|
def return_fid(fid)
|
255
226
|
@usedfids.delete(fid)
|
256
227
|
end
|
257
228
|
|
258
|
-
|
259
|
-
# Send a message and wait for the reply
|
260
|
-
#
|
261
|
-
# +message+:: [StyxMessage] the message to be sent
|
262
|
-
# return:: [StyxMessage] the reply to our message
|
263
|
-
#
|
264
|
-
def send_message(message)
|
265
|
-
tag = send_message_async(message)
|
266
|
-
return(get_reply(tag))
|
267
|
-
end
|
229
|
+
protected
|
268
230
|
|
269
231
|
##
|
270
|
-
#
|
232
|
+
# This method is used to prepare the connection, and should be
|
233
|
+
# defined by subclasses.
|
271
234
|
#
|
272
|
-
def
|
273
|
-
|
274
|
-
# clunked last.
|
275
|
-
while (@usedfids.length > 0)
|
276
|
-
# The fid is automatically removed from @usedfids when the
|
277
|
-
# Rclunk message is received
|
278
|
-
rclunk = send_message(Message::Tclunk.new(@usedfids[-1]))
|
279
|
-
end
|
280
|
-
|
281
|
-
# Flush all outstanding messages
|
282
|
-
@rmessages.each_key do |tag|
|
283
|
-
rflush = send_message(Message::Tflush.new(tag))
|
284
|
-
end
|
285
|
-
# kill the receiver thread (FIXME: there has got to be a cleaner way!)
|
286
|
-
@receiver.kill
|
287
|
-
@conn.close
|
235
|
+
def prepare_connection
|
236
|
+
raise StyxException.new("No prepare_connection method defined")
|
288
237
|
end
|
289
238
|
|
239
|
+
public
|
240
|
+
|
290
241
|
##
|
291
|
-
#
|
292
|
-
#
|
293
|
-
#
|
294
|
-
# terminates.
|
295
|
-
#
|
296
|
-
# +path+:: the path to the Styx file to be opened
|
297
|
-
# +mode+:: the mode to open the file (which can be r, w, r+, or e)
|
298
|
-
# +perm+:: the (optional) permissions bit mask. This is only
|
299
|
-
# relevant if the open creates a file.
|
300
|
-
# +rfid+:: the root fid. If this is nil, use the root fid we got
|
301
|
-
# when we connected.
|
302
|
-
#
|
303
|
-
# All parameters are optional. If no path is specified, it defaults
|
304
|
-
# to reading the root directory on the Styx server.
|
305
|
-
#
|
306
|
-
# FIXME: This doesn't deal with the case where MAXWELEM is reached
|
307
|
-
# yet.
|
308
|
-
# TODO: Allow the caller to make use of the Styx file access mode
|
309
|
-
# values (OREAD, OWRITE, ORDWR, OEXEC, OTRUNC, ORCLOSE).
|
242
|
+
# Connect to a remote Styx server. If a block is passed, yield
|
243
|
+
# self to the block and then do a disconnect when the block
|
244
|
+
# finishes.
|
310
245
|
#
|
311
|
-
def
|
312
|
-
|
313
|
-
fid = get_free_fid
|
246
|
+
def connect(&block)
|
247
|
+
prepare_connection()
|
314
248
|
|
315
|
-
|
316
|
-
|
317
|
-
end
|
318
|
-
|
319
|
-
#
|
320
|
-
# The mode string is the standard Ruby mode string, viz.
|
321
|
-
# "r" - read-only, start at beginning of file
|
322
|
-
# "r+" - read/write, start at beginning of file
|
323
|
-
# "w" - write only, truncate existing file or create it if it
|
324
|
-
# doesn't exist.
|
325
|
-
# "w+" - read/write, truncate existing file or create it if it
|
326
|
-
# doesn't exist
|
327
|
-
# "a" - write only, starts at end of file if it exists otherwise
|
328
|
-
# creates a new file for writing.
|
329
|
-
# "a+" - read/write, starts at end of file if it exists, otherwise
|
330
|
-
# creates a new file for writing.
|
331
|
-
#
|
332
|
-
# There is also a mode specific to RStyx, not a standard mode
|
333
|
-
# for Ruby:
|
249
|
+
# Connection has been established. Begin the handshaking process
|
250
|
+
# with the remote server.
|
334
251
|
#
|
335
|
-
#
|
252
|
+
# 1. Send a Tversion message and check the response from the
|
253
|
+
# remote Styx server.
|
336
254
|
#
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
create = false
|
342
|
-
truncate = false
|
343
|
-
append = false
|
344
|
-
mval = Message::Topen::OREAD
|
345
|
-
case mode
|
346
|
-
when "r"
|
347
|
-
# all default above
|
348
|
-
when "r+"
|
349
|
-
write = true
|
350
|
-
mval = Message::Topen::ORDWR
|
351
|
-
when "w"
|
352
|
-
read = false
|
353
|
-
write = true
|
354
|
-
create = true
|
355
|
-
truncate = true
|
356
|
-
mval = Message::Topen::OWRITE
|
357
|
-
when "w+"
|
358
|
-
read = true
|
359
|
-
write = true
|
360
|
-
create = true
|
361
|
-
truncate = true
|
362
|
-
mval = Message::Topen::ORDWR
|
363
|
-
when "a"
|
364
|
-
read = false
|
365
|
-
write = true
|
366
|
-
create = true
|
367
|
-
append = true
|
368
|
-
mval = Message::Topen::OWRITE
|
369
|
-
when "a+"
|
370
|
-
read = true
|
371
|
-
write = true
|
372
|
-
create = true
|
373
|
-
append = true
|
374
|
-
mval = Message::Topen::ORDWR
|
375
|
-
when "e"
|
376
|
-
read = true
|
377
|
-
write = true
|
378
|
-
mval = Message::Topen::OEXEC
|
379
|
-
else
|
380
|
-
raise StyxException.new("Invalid mode string '#{mode}'")
|
381
|
-
end
|
382
|
-
|
383
|
-
if truncate
|
384
|
-
mval |= Message::Topen::OTRUNC
|
255
|
+
rver = send_message(Message::Tversion.new(:msize => 8216,
|
256
|
+
:version => "9P2000"))
|
257
|
+
if (rver.version != "9P2000")
|
258
|
+
raise StyxException.new("Server uses unsupported Styx version #{rver.version}")
|
385
259
|
end
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
#
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
# would have its Qid.qtype high bit set to 1. In this case,
|
399
|
-
# mode can only be 'r', and instead of a StyxIO, a StyxDir
|
400
|
-
# instance is created instead.
|
260
|
+
@msize = rver.msize
|
261
|
+
@version = rver.version
|
262
|
+
rfid = nil
|
263
|
+
# 2. Attach to the remote server
|
264
|
+
if @auth.nil?
|
265
|
+
# unauthenticated connection
|
266
|
+
rfid = get_free_fid
|
267
|
+
rattach = send_message(Message::Tattach.new(:fid => rfid,
|
268
|
+
:afid => NOFID,
|
269
|
+
:uname => ENV['USER'],
|
270
|
+
:aname => ""))
|
271
|
+
else
|
401
272
|
#
|
402
|
-
#
|
403
|
-
#
|
273
|
+
# 3. Perform authentication based on the passed authenticator
|
274
|
+
# object.
|
404
275
|
#
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
rescue StyxException => se
|
411
|
-
# If the walk generated an error, see if the create flag
|
412
|
-
# was true. If so, the error may be ignored, and the
|
413
|
-
# open_msgclass set to Message::Tcreate. Otherwise, return
|
414
|
-
# the fid and reraise the exception.
|
415
|
-
if create
|
416
|
-
# Walk to the directory of the file to be made, if possible
|
417
|
-
begin
|
418
|
-
rwalk = send_message(Message::Twalk.new(rfid, fid,
|
419
|
-
File.dirname(path)))
|
420
|
-
rescue StyxException => se
|
421
|
-
return_fid(fid)
|
422
|
-
raise se # reraise the exception
|
423
|
-
end
|
424
|
-
open_msg = Message::Tcreate.new(fid, File.basename(path),
|
425
|
-
mval, perm)
|
426
|
-
else
|
427
|
-
return_fid(fid)
|
428
|
-
raise se # reraise the exception
|
429
|
-
end
|
276
|
+
# If we have an authenticator object, we call its authenticator
|
277
|
+
# method with ourself.
|
278
|
+
#
|
279
|
+
rfid = @auth.authenticate(self)
|
430
280
|
end
|
431
281
|
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
fp = iclass.new(self, path, fid, ropen.iounit, mode)
|
436
|
-
|
437
|
-
# if we're supposed to append, seek to the end of the file
|
438
|
-
if append
|
439
|
-
rstat = send_message(Message::Tstat.new(fid))
|
440
|
-
fp.seek(rstat.stat.length, :seek_set)
|
441
|
-
end
|
282
|
+
# If we get here, we're connected, and rfid represents the root
|
283
|
+
# fid of the connection
|
284
|
+
@rootfid = rfid
|
442
285
|
|
443
|
-
|
444
|
-
|
445
|
-
yield
|
446
|
-
|
447
|
-
|
448
|
-
end
|
449
|
-
else
|
450
|
-
return(fp)
|
286
|
+
if block_given?
|
287
|
+
begin
|
288
|
+
yield self
|
289
|
+
ensure
|
290
|
+
self.disconnect
|
451
291
|
end
|
452
|
-
|
453
|
-
|
454
|
-
raise se # reraise the exception
|
292
|
+
else
|
293
|
+
return(self)
|
455
294
|
end
|
456
295
|
end
|
457
296
|
|
458
297
|
##
|
459
|
-
#
|
460
|
-
# should also work properly on directories
|
298
|
+
# Disconnect from the remote server.
|
461
299
|
#
|
462
|
-
def
|
463
|
-
|
464
|
-
|
465
|
-
|
300
|
+
def disconnect
|
301
|
+
# Clunk all outstanding fids in reverse order so the root fid
|
302
|
+
# gets clunked last.
|
303
|
+
while (@usedfids.length > 0)
|
466
304
|
begin
|
467
|
-
|
468
|
-
rescue
|
469
|
-
|
470
|
-
|
305
|
+
rclunk = tclunk(@usedfids[-1], true)
|
306
|
+
rescue
|
307
|
+
# An error is most likely a no such fid error. Return the fid
|
308
|
+
# manually in this case.
|
309
|
+
return_fid(@usedfids[-1])
|
471
310
|
end
|
311
|
+
end
|
472
312
|
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
313
|
+
@conn.disconnect
|
314
|
+
@eventthread.kill
|
315
|
+
end
|
316
|
+
|
317
|
+
##
|
318
|
+
# Send a message, and return the response. Delegates to
|
319
|
+
# @conn#send_message. Do not use this method to send Tclunk
|
320
|
+
# messages!
|
321
|
+
#
|
322
|
+
def send_message(msg, timeout=0)
|
323
|
+
@conn.send_message(msg, timeout)
|
324
|
+
end
|
325
|
+
|
326
|
+
##
|
327
|
+
# Fire and forget a Tclunk for some fid. When the Rclunk is
|
328
|
+
# received, return the fid. USE THIS METHOD, AND THIS METHOD
|
329
|
+
# ONLY, to send Tclunk messages.
|
330
|
+
#
|
331
|
+
def tclunk(fid, sync=false)
|
332
|
+
if @rpendingclunks.has_key?(fid)
|
333
|
+
return
|
334
|
+
end
|
335
|
+
q = nil
|
336
|
+
if sync
|
337
|
+
q = Queue.new
|
338
|
+
end
|
339
|
+
tag = @conn.send_message_async(Message::Tclunk.new(:fid => fid)) do |tx,rx|
|
340
|
+
# Test whether the response is an Rclunk.
|
341
|
+
if rx.class != Message::Rclunk
|
342
|
+
# this is an error condition, but it will only get reported
|
343
|
+
# if Thread.abort_on_exception is set to true, or if
|
344
|
+
# the tclunk is synchronous
|
345
|
+
exc = StyxException.new("#{tx.to_s} received #{rx.to_s}")
|
346
|
+
if sync
|
347
|
+
q << exc
|
348
|
+
else
|
349
|
+
raise exc
|
482
350
|
end
|
483
|
-
|
351
|
+
end
|
352
|
+
# return the FID
|
353
|
+
fid = @pendingclunks.delete(tag)
|
354
|
+
@rpendingclunks.delete(fid)
|
355
|
+
return_fid(fid)
|
356
|
+
if sync
|
357
|
+
q << fid
|
358
|
+
end
|
359
|
+
end
|
360
|
+
@pendingclunks[tag] = fid
|
361
|
+
@rpendingclunks[fid] = tag
|
362
|
+
if sync
|
363
|
+
res = q.shift
|
364
|
+
if res.class == StyxException
|
365
|
+
raise res
|
484
366
|
end
|
485
367
|
end
|
486
368
|
end
|
487
369
|
|
488
|
-
alias rmdir delete
|
489
|
-
alias unlink delete
|
490
|
-
|
491
370
|
##
|
492
|
-
#
|
493
|
-
#
|
371
|
+
# Open a file on the remote server, throwing a StyxException if the
|
372
|
+
# file can't be found or opened in a given mode.
|
494
373
|
#
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
374
|
+
# +path+:: The path of the file relative to the server root.
|
375
|
+
# +mode+:: Integer representing the mode, or one of "r", "r+",
|
376
|
+
# "w", "w+", "a", "a+", "e" as aliases
|
377
|
+
# +return+:: A File object representing the opened file, or
|
378
|
+
# possibly a Directory object if the file was a directory.
|
379
|
+
#
|
380
|
+
# If a block is passed, it will yield the file object to the block
|
381
|
+
# and close the file when the block finishes (actually it will pass
|
382
|
+
# the block on to the StyxFile#open method, which does just that).
|
383
|
+
#
|
384
|
+
def open(path, mode="r", perm=0666, &block)
|
385
|
+
file = File.new(self, path)
|
386
|
+
|
387
|
+
append = false
|
388
|
+
create = false
|
389
|
+
numeric_mode = nil
|
390
|
+
if mode.is_a?(Integer)
|
391
|
+
numeric_mode = mode
|
392
|
+
else
|
393
|
+
case mode.to_s
|
394
|
+
when "r"
|
395
|
+
numeric_mode = OREAD
|
396
|
+
when "r+"
|
397
|
+
numeric_mode = ORDWR
|
398
|
+
when "w"
|
399
|
+
numeric_mode = OTRUNC | OWRITE
|
400
|
+
create = true
|
401
|
+
when "w+"
|
402
|
+
numeric_mode = OTRUNC | ORDWR
|
403
|
+
create = true
|
404
|
+
when "a"
|
405
|
+
numeric_mode = OWRITE
|
406
|
+
append = true
|
407
|
+
create = true
|
408
|
+
when "a+"
|
409
|
+
numeric_mode = ORDWR
|
410
|
+
append = true
|
411
|
+
create = true
|
412
|
+
when "e"
|
413
|
+
numeric_mode = OEXEC
|
414
|
+
end
|
504
415
|
end
|
505
416
|
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
Message::Topen::OREAD,
|
510
|
-
perm))
|
511
|
-
rescue StyxException => se
|
512
|
-
return_fid(fid)
|
513
|
-
raise se
|
514
|
-
ensure
|
515
|
-
rclunk = send_message(Message::Tclunk.new(fid))
|
417
|
+
fp = file.open(numeric_mode, perm, create, &block)
|
418
|
+
if append
|
419
|
+
fp.seek(0, 2)
|
516
420
|
end
|
421
|
+
return(fp)
|
517
422
|
end
|
518
423
|
|
519
|
-
end
|
424
|
+
end # class Connection
|
520
425
|
|
521
426
|
##
|
522
|
-
# TCP connection
|
427
|
+
# TCP connection subclass.
|
523
428
|
#
|
524
429
|
class TCPConnection < Connection
|
525
|
-
|
526
|
-
|
527
|
-
##
|
528
|
-
# Create a Styx connection over TCP
|
529
|
-
#
|
530
|
-
# +host+:: [String] the host to connect to
|
531
|
-
# +port+:: [Fixnum] the port on which to connect
|
532
|
-
# +user+:: [String] the user to connect as
|
533
|
-
#
|
534
|
-
def initialize(host, port, user="")
|
430
|
+
def initialize(host, port, auth=nil)
|
535
431
|
@host = host
|
536
432
|
@port = port
|
537
|
-
super(
|
433
|
+
super(auth)
|
538
434
|
end
|
539
435
|
|
436
|
+
protected
|
437
|
+
|
540
438
|
##
|
541
|
-
# TCP connection
|
439
|
+
# Prepare a TCP connection to the Styx server
|
542
440
|
#
|
543
|
-
def
|
544
|
-
|
545
|
-
|
441
|
+
def prepare_connection
|
442
|
+
queue = Queue.new
|
443
|
+
@eventthread = Thread.new do
|
444
|
+
EventMachine::run do
|
445
|
+
queue << EventMachine::connect(@host, @port, StyxClient)
|
446
|
+
end
|
447
|
+
end
|
448
|
+
|
449
|
+
@conn = queue.shift
|
546
450
|
end
|
547
451
|
|
548
|
-
|
452
|
+
public
|
453
|
+
|
454
|
+
end # class TCPConnection
|
549
455
|
|
550
456
|
##
|
551
|
-
#
|
552
|
-
|
553
|
-
|
457
|
+
# A Styx client's view of a file. This class should probably
|
458
|
+
# never be directly instantiated, but only via Connection#open.
|
459
|
+
# The buffering algorithm in use here is somewhat based on the
|
460
|
+
# Buffering mix-in module in the Ruby OpenSSL module written by
|
461
|
+
# Goto Yuuzou, but modified a bit to provide for offset
|
462
|
+
# handling. Note that this class isn't thread-safe.
|
463
|
+
#
|
464
|
+
class File
|
465
|
+
include Enumerable
|
554
466
|
|
555
|
-
|
556
|
-
|
557
|
-
|
467
|
+
attr_reader :conn, :path
|
468
|
+
attr_accessor :mode, :fid, :qid, :iounit, :sync
|
469
|
+
|
470
|
+
def initialize(conn, path)
|
471
|
+
@conn = conn # the connection on which the file sits
|
472
|
+
@path = path # pathname of the file
|
473
|
+
@fid = -1 # the client's file identifier
|
474
|
+
@qid = nil # the server's unique identifier for this file
|
475
|
+
# maximum number of bytes that can be read or written to the file at a time
|
476
|
+
@iounit = 0
|
477
|
+
@mode = -1 # mode under which the file is opened, -1 == not open
|
478
|
+
@offset = 0 # File offset. This is the same as @boffset only after a seek
|
479
|
+
@rboffset = 0 # Read buffer offset
|
480
|
+
@eof = false
|
481
|
+
@rbuffer = ""
|
482
|
+
@sync = false # whether or not to buffer writes
|
558
483
|
end
|
559
484
|
|
560
485
|
##
|
561
|
-
#
|
486
|
+
# Open the file on the server. If a block is passed to this method
|
487
|
+
# it will pass the file to the block and close the file automatically
|
488
|
+
# when the block terminates.
|
562
489
|
#
|
563
|
-
|
564
|
-
|
565
|
-
|
490
|
+
# This follows more or less the same semantics as sys-open(2)
|
491
|
+
# on Inferno, performing an open with truncate, when a file
|
492
|
+
# is opened that doesn't exist.
|
493
|
+
#
|
494
|
+
# XXX - twalk should handle the case of MAXWELEM, as well as for
|
495
|
+
# when the twalk message is too large to fit in the server's
|
496
|
+
# designated msize.
|
497
|
+
#
|
498
|
+
# +mode+:: Integer representing the mode - see the constants in common.rb
|
499
|
+
# +perm+:: Permissions of the file (only used on open/create)
|
500
|
+
# +create+:: should we create the file if it doesn't exist?
|
501
|
+
#
|
502
|
+
def open(mode, perm, create, &block)
|
503
|
+
dfid = @conn.get_free_fid
|
504
|
+
basename = ::File.basename(@path)
|
505
|
+
dirname = ::File.dirname(@path)
|
506
|
+
# Walk to the dirname first
|
507
|
+
twalk = Message::Twalk.new(:fid => @conn.rootfid, :newfid => dfid)
|
508
|
+
twalk.path = dirname
|
509
|
+
rwalk = @conn.send_message(twalk)
|
510
|
+
# if the rwalk has some other length than the number of path
|
511
|
+
# elements in the original twalk, we have failed.
|
512
|
+
if rwalk.qids.length != twalk.wnames.length
|
513
|
+
raise StyxException.new(("#{path} no such file or directory"))
|
514
|
+
end
|
515
|
+
ropen = fid = nil
|
516
|
+
begin
|
517
|
+
# Next, walk to the basename from there, using a new fid
|
518
|
+
fid = @conn.get_free_fid
|
519
|
+
twalk = Message::Twalk.new(:fid => dfid, :newfid => fid)
|
520
|
+
twalk.path = basename
|
521
|
+
rwalk = @conn.send_message(twalk)
|
522
|
+
# Do a Topen if this succeeds
|
523
|
+
open_msg = Message::Topen.new(:fid => fid, :mode => mode)
|
524
|
+
ropen = @conn.send_message(open_msg)
|
525
|
+
@conn.tclunk(dfid)
|
526
|
+
rescue Exception => e
|
527
|
+
# If we are being directed to create the file if it doesn't
|
528
|
+
# already exist, send a Tcreate message, and use the response
|
529
|
+
# for it as the ropen (the two classes respond to exactly the
|
530
|
+
# same messages--ah the wonders of duck typing!). If not,
|
531
|
+
# or if that fails, this should propagate an exception upwards.
|
532
|
+
if create
|
533
|
+
# Alter the submitted permissions mask according to
|
534
|
+
# the connection's umask
|
535
|
+
perm &= ~(@conn.umask)
|
536
|
+
create_msg = Message::Tcreate.new(:fid => dfid, :name => basename,
|
537
|
+
:perm => perm, :mode => mode)
|
538
|
+
ropen = @conn.send_message(create_msg)
|
539
|
+
fid = dfid
|
540
|
+
else
|
541
|
+
raise e
|
542
|
+
end
|
543
|
+
end
|
566
544
|
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
545
|
+
# If we get here, we were able to successfully open or create
|
546
|
+
# the file.
|
547
|
+
@mode = mode
|
548
|
+
@fid = fid
|
549
|
+
@qid = ropen.qid
|
550
|
+
@iounit = ropen.iounit
|
551
|
+
# Determine if the file is actually a directory. Such a file
|
552
|
+
# would have its Qid.qtype high bit set to 1. In this case, instead
|
553
|
+
# of returning self, we return self wrapped in a Directory object.
|
554
|
+
retval = self
|
555
|
+
if twalk.wnames.length == 0 || (rwalk.qids[-1].qtype & 0x80 != 0)
|
556
|
+
retval = Directory.new(self)
|
557
|
+
end
|
558
|
+
if block_given?
|
559
|
+
begin
|
560
|
+
yield retval
|
561
|
+
ensure
|
562
|
+
retval.close
|
563
|
+
end
|
564
|
+
else
|
565
|
+
return(retval)
|
566
|
+
end
|
571
567
|
end
|
572
568
|
|
573
569
|
##
|
574
|
-
#
|
570
|
+
# Read the Stat information for the file.
|
575
571
|
#
|
576
|
-
|
577
|
-
|
578
|
-
end
|
579
|
-
|
580
|
-
##
|
581
|
-
# Empty the buffer. Return all the data in the buffer
|
572
|
+
# returns: the RStyx::Message::Stat instance corresponding to
|
573
|
+
# the file
|
582
574
|
#
|
583
|
-
def
|
584
|
-
|
585
|
-
|
586
|
-
return(retval)
|
575
|
+
def stat
|
576
|
+
rstat = @conn.send_message(Message::Tstat.new(:fid => @fid))
|
577
|
+
return(rstat.stat)
|
587
578
|
end
|
588
579
|
|
589
580
|
##
|
590
|
-
#
|
591
|
-
#
|
581
|
+
# Close the file. This will flush all unwritten buffered data
|
582
|
+
# if any.
|
592
583
|
#
|
593
|
-
def
|
594
|
-
|
595
|
-
@
|
596
|
-
|
597
|
-
@
|
584
|
+
def close
|
585
|
+
flush rescue nil
|
586
|
+
if @fid >= 0
|
587
|
+
# Clunk the fid
|
588
|
+
@conn.tclunk(@fid)
|
598
589
|
end
|
599
|
-
|
590
|
+
@fid = -1
|
591
|
+
@iounit = 0
|
592
|
+
@mode = -1
|
600
593
|
end
|
601
594
|
|
602
|
-
|
603
|
-
|
604
|
-
# written to the buffer (the excess) if the buffer is full.
|
605
|
-
def fill(data)
|
606
|
-
if data.length > slack
|
607
|
-
excess = data[slack..(data.length)]
|
608
|
-
data = data[0..(slack-1)]
|
609
|
-
end
|
610
|
-
@data << data
|
611
|
-
return(excess)
|
595
|
+
def closed?
|
596
|
+
return(@mode < 0)
|
612
597
|
end
|
613
598
|
|
614
|
-
|
615
|
-
|
616
|
-
##
|
617
|
-
# A file on a Styx server. This should probably not be instantiated
|
618
|
-
# directly, but only by the use of Connection#open.
|
619
|
-
#
|
620
|
-
# TODO: This should later become a true subclass of IO.
|
621
|
-
#
|
622
|
-
class StyxIO
|
623
|
-
attr_reader :name, :mode, :closed, :offset, :iounit, :fid
|
624
|
-
attr_accessor :sync
|
625
|
-
|
626
|
-
# Whether or not to buffer writes. Unlike regular IO objects,
|
627
|
-
# StyxIO has this true by default.
|
628
|
-
attr_accessor :sync
|
629
|
-
include Enumerable
|
630
|
-
|
631
|
-
def initialize(conn, name, fid, iounit, mode="r")
|
632
|
-
@conn = conn
|
633
|
-
@name = name
|
634
|
-
@mode = mode
|
635
|
-
@closed = false # closed flag
|
636
|
-
@fid = fid
|
637
|
-
@iounit = iounit # maximum payload of a Twrite or Rread message
|
638
|
-
@offset = 0 # file offset
|
639
|
-
@sync = true
|
640
|
-
|
641
|
-
@buffer = Buffer.new(iounit) # the read buffer
|
642
|
-
@boffset = 0 # the offset of the last buffered read or write
|
643
|
-
@lastop = :none # the last operation performed
|
644
|
-
@sync = false # Buffer or not buffer writes
|
645
|
-
end
|
599
|
+
private
|
646
600
|
|
647
601
|
##
|
648
602
|
# Read at most +size+ bytes from +offset+
|
649
603
|
# If the size argument is negative or omitted, read until EOF.
|
650
|
-
# This
|
651
|
-
# not be used directly.
|
604
|
+
# This should probably not be used directly.
|
652
605
|
#
|
653
606
|
# +size+:: number of bytes to read from the file
|
654
607
|
# +offset+:: the offset to read from.
|
655
608
|
# return:: the data followed by the new offset
|
656
609
|
#
|
657
|
-
def
|
610
|
+
def _sysread(size=-1, offset=0)
|
658
611
|
contents = ""
|
659
612
|
bytes_to_read = size
|
660
613
|
loop do
|
@@ -665,7 +618,10 @@ module RStyx
|
|
665
618
|
else
|
666
619
|
n = bytes_to_read
|
667
620
|
end
|
668
|
-
rread =
|
621
|
+
rread =
|
622
|
+
@conn.send_message(Message::Tread.new(:fid => @fid,
|
623
|
+
:offset => offset,
|
624
|
+
:count => n))
|
669
625
|
if rread.data.length == 0
|
670
626
|
break # EOF
|
671
627
|
end
|
@@ -681,16 +637,17 @@ module RStyx
|
|
681
637
|
##
|
682
638
|
#
|
683
639
|
# Write data to the file at +offset+. No buffering is
|
684
|
-
# performed. This
|
685
|
-
# directly.
|
640
|
+
# performed. This should probably not be used directly.
|
686
641
|
#
|
687
|
-
# +
|
642
|
+
# +d+:: data to be written
|
688
643
|
# +offset+:: the offset to write at
|
689
644
|
#
|
690
|
-
# returns the new offset
|
645
|
+
# returns the new offset and the number of bytes written
|
691
646
|
#
|
692
|
-
def
|
647
|
+
def _syswrite(d, offset)
|
648
|
+
str = d.to_s
|
693
649
|
pos = 0
|
650
|
+
count = 0
|
694
651
|
loop do
|
695
652
|
bytes_left = str.length - pos
|
696
653
|
if bytes_left <= 0
|
@@ -700,256 +657,323 @@ module RStyx
|
|
700
657
|
else
|
701
658
|
n = bytes_left
|
702
659
|
end
|
703
|
-
rwrite = @conn.send_message(Message::Twrite.new(@fid,
|
704
|
-
|
705
|
-
|
706
|
-
raise StyxException.new("error writing data")
|
707
|
-
end
|
660
|
+
rwrite = @conn.send_message(Message::Twrite.new(:fid => @fid,
|
661
|
+
:offset => offset,
|
662
|
+
:data => str[pos..(pos+n)]))
|
708
663
|
pos += n
|
709
664
|
offset += n
|
665
|
+
count += rwrite.count
|
710
666
|
end
|
711
|
-
return(offset)
|
667
|
+
return([offset, count])
|
712
668
|
end
|
713
669
|
|
714
|
-
public
|
715
|
-
|
716
670
|
##
|
717
|
-
#
|
718
|
-
# operations should be possible.
|
671
|
+
# Add up to @iounit bytes to the read buffer.
|
719
672
|
#
|
720
|
-
def
|
721
|
-
|
722
|
-
|
723
|
-
|
673
|
+
def fill_rbuff
|
674
|
+
d, @rboffset = _sysread(@iounit, @rboffset)
|
675
|
+
if d.empty?
|
676
|
+
@eof = true
|
677
|
+
end
|
678
|
+
@rbuffer << d
|
724
679
|
end
|
725
680
|
|
726
681
|
##
|
727
|
-
#
|
728
|
-
# If the size argument is negative or omitted, read until EOF.
|
729
|
-
# Returns nil if we read from the end of stream.
|
730
|
-
#
|
731
|
-
# This is a buffered read.
|
732
|
-
#
|
733
|
-
# +size+:: number of bytes to read from the file
|
682
|
+
# Consume +size+ bytes from the read buffer.
|
734
683
|
#
|
735
|
-
def
|
736
|
-
|
737
|
-
|
738
|
-
|
684
|
+
def consume_rbuff(size=nil)
|
685
|
+
if @rbuffer.empty?
|
686
|
+
nil
|
687
|
+
else
|
688
|
+
size ||= @rbuffer.size
|
689
|
+
ret = @rbuffer[0, size]
|
690
|
+
@rbuffer[0, size] = ""
|
691
|
+
return(ret)
|
739
692
|
end
|
740
|
-
|
741
|
-
|
742
|
-
bytes_to_read = size
|
743
|
-
contents = ""
|
744
|
-
loop do
|
745
|
-
if @buffer.empty?
|
746
|
-
# fill the buffer if the buffer goes empty
|
747
|
-
rdata, @boffset = sysread_int(@buffer.slack, @boffset)
|
748
|
-
if rdata.length == 0
|
749
|
-
break
|
750
|
-
end
|
751
|
-
@buffer.fill(rdata)
|
752
|
-
end
|
753
|
-
|
754
|
-
d = ""
|
755
|
-
|
756
|
-
if size < 0 || bytes_to_read > @buffer.bufsize
|
757
|
-
d = @buffer.empty
|
758
|
-
else
|
759
|
-
d = @buffer.consume(bytes_to_read)
|
760
|
-
end
|
693
|
+
end
|
761
694
|
|
762
|
-
|
763
|
-
contents << d
|
764
|
-
if size >= 0
|
765
|
-
bytes_to_read -= d.length
|
766
|
-
end
|
695
|
+
public
|
767
696
|
|
768
|
-
|
697
|
+
##
|
698
|
+
# Read at most +size+ bytes from the Styx file or to the end of
|
699
|
+
# file if omitted. Returns nil if called at end of file.
|
700
|
+
#
|
701
|
+
def read(size=-1)
|
702
|
+
until @eof
|
703
|
+
# Fill up the buffer until we have at least the requested
|
704
|
+
# size, or until end of file if size is negative.
|
705
|
+
if size > 0 && size <= @rbuffer.size
|
769
706
|
break
|
770
707
|
end
|
708
|
+
fill_rbuff
|
771
709
|
end
|
772
|
-
|
773
|
-
|
710
|
+
|
711
|
+
# We managed to slurp the entire file!
|
712
|
+
if size < 0
|
713
|
+
size = @rbuffer.size
|
774
714
|
end
|
775
|
-
|
715
|
+
|
716
|
+
@offset += size
|
717
|
+
retval = consume_rbuff(size) || ""
|
718
|
+
(size && retval.empty?) ? nil : retval
|
776
719
|
end
|
777
720
|
|
778
721
|
##
|
779
|
-
#
|
722
|
+
# Reads the next "line" from the Styx file; lines are separated by
|
723
|
+
# +eol+. An +eol+ of nil reads the entire contents. Returns nil
|
724
|
+
# if called at end of file.
|
780
725
|
#
|
781
|
-
def
|
782
|
-
|
783
|
-
|
726
|
+
def gets(eol=$/)
|
727
|
+
idx = @rbuffer.index(eol)
|
728
|
+
until @eof
|
729
|
+
if idx
|
730
|
+
break
|
731
|
+
end
|
732
|
+
fill_rbuff
|
733
|
+
idx = @rbuffer.index(eol)
|
784
734
|
end
|
785
|
-
|
786
|
-
|
787
|
-
|
735
|
+
if eol.is_a?(Regexp)
|
736
|
+
size = idx ? idx+$&.size : nil
|
737
|
+
else
|
738
|
+
size = idx ? idx+eol.size : nil
|
788
739
|
end
|
789
|
-
|
740
|
+
@offset += size
|
741
|
+
return(consume_rbuff(size))
|
790
742
|
end
|
791
743
|
|
792
744
|
##
|
793
|
-
#
|
794
|
-
#
|
795
|
-
# for reading.
|
745
|
+
# Executes the block for evely line in the Styx file, where lines
|
746
|
+
# are separated by +eol+.
|
796
747
|
#
|
797
|
-
def
|
798
|
-
|
799
|
-
|
800
|
-
end
|
801
|
-
|
802
|
-
while (!(c=read(1)).nil?)
|
803
|
-
yield c[0]
|
748
|
+
def each(eol=$/)
|
749
|
+
while line = self.gets(eol)
|
750
|
+
yield line
|
804
751
|
end
|
805
752
|
end
|
806
753
|
|
754
|
+
alias each_line each
|
755
|
+
|
807
756
|
##
|
808
|
-
# Reads the
|
809
|
-
#
|
810
|
-
#
|
811
|
-
|
812
|
-
|
813
|
-
|
814
|
-
|
815
|
-
# of file.
|
816
|
-
#
|
817
|
-
def gets(sep_str=$RS)
|
818
|
-
line = ""
|
819
|
-
while (!(c=read(1)).nil?)
|
820
|
-
line << c
|
821
|
-
if c == $RS
|
822
|
-
break
|
823
|
-
end
|
824
|
-
end
|
825
|
-
if line.length == 0
|
826
|
-
return(nil)
|
757
|
+
# Reads all of the lines in the Styx file, and returns them in an
|
758
|
+
# array. Lines are separated by an optional separator +eol+.
|
759
|
+
#
|
760
|
+
def readlines(eol=$/)
|
761
|
+
ary = []
|
762
|
+
while line = self.gets(eol)
|
763
|
+
ary << line
|
827
764
|
end
|
828
|
-
|
765
|
+
ary
|
829
766
|
end
|
830
767
|
|
831
768
|
##
|
832
|
-
#
|
833
|
-
|
834
|
-
|
835
|
-
|
836
|
-
|
837
|
-
if !block_given?
|
838
|
-
raise LocalJumpError("no block given")
|
839
|
-
end
|
769
|
+
# Reads a line as with gets, but
|
770
|
+
def readline(eol=$/)
|
771
|
+
raise EOFError if eof?
|
772
|
+
return(gets(eol))
|
773
|
+
end
|
840
774
|
|
841
|
-
|
842
|
-
|
775
|
+
def getc
|
776
|
+
c = read(1)
|
777
|
+
return(c ? c[0] : nil)
|
778
|
+
end
|
779
|
+
|
780
|
+
def readchar
|
781
|
+
raise EOFError if eof?
|
782
|
+
getc
|
783
|
+
end
|
784
|
+
|
785
|
+
def ungetc(c)
|
786
|
+
@rbuffer[0,0] = c.chr
|
787
|
+
@offset -= 1
|
788
|
+
end
|
789
|
+
|
790
|
+
def eof?
|
791
|
+
if !@eof && @rbuffer.empty?
|
792
|
+
fill_rbuff
|
843
793
|
end
|
794
|
+
return(@eof && @rbuffer.empty?)
|
844
795
|
end
|
845
796
|
|
797
|
+
alias eof eof?
|
798
|
+
|
799
|
+
private
|
800
|
+
|
846
801
|
##
|
847
|
-
#
|
802
|
+
# Write data to the buffer if the buffer is not yet full, or
|
803
|
+
# if it has been determined (e.g. by an end of line marker) that
|
804
|
+
# we should flush the buffer, and actually write.
|
848
805
|
#
|
849
|
-
def
|
850
|
-
|
851
|
-
@
|
806
|
+
def do_write(s)
|
807
|
+
unless defined?(@wbuffer)
|
808
|
+
@wbuffer = ""
|
809
|
+
# we obviously start writing at the current offset
|
810
|
+
@wboffset = @offset
|
852
811
|
end
|
853
|
-
@
|
854
|
-
|
855
|
-
#
|
856
|
-
#
|
857
|
-
|
858
|
-
|
859
|
-
|
812
|
+
@wbuffer << s
|
813
|
+
@offset += s.length
|
814
|
+
#
|
815
|
+
# We flush the buffer if at least one of the following conditions
|
816
|
+
# has been met:
|
817
|
+
#
|
818
|
+
# 1. The sync flag is set to true.
|
819
|
+
# 2. The write buffer size has equalled or exceeded the connection's
|
820
|
+
# iounit.
|
821
|
+
# 3. The write buffer now contains an end of line marker, in which
|
822
|
+
# cas we flush only until the end of line marker.
|
823
|
+
#
|
824
|
+
if @sync || @wbuffer.size >= @iounit || (idx = @wbuffer.rindex($/))
|
825
|
+
remain = idx ? idx + $/.size : @wbuffer.length
|
826
|
+
nwritten = 0
|
827
|
+
ofs = @wboffset
|
828
|
+
while remain > 0
|
829
|
+
str = @wbuffer[nwritten, remain]
|
830
|
+
ofs, nwrote = _syswrite(str, ofs)
|
831
|
+
remain -= nwrote
|
832
|
+
nwritten += nwrote
|
833
|
+
end
|
834
|
+
@wbuffer[0, nwritten] = ""
|
835
|
+
@wboffset += nwritten
|
860
836
|
end
|
837
|
+
end
|
838
|
+
|
839
|
+
public
|
840
|
+
|
841
|
+
def write(s)
|
842
|
+
do_write(s)
|
843
|
+
return(s.length)
|
844
|
+
end
|
845
|
+
|
846
|
+
def <<(s)
|
847
|
+
do_write(s)
|
848
|
+
return(self)
|
849
|
+
end
|
861
850
|
|
862
|
-
|
863
|
-
|
864
|
-
|
865
|
-
|
866
|
-
|
867
|
-
|
868
|
-
|
851
|
+
def puts(*args)
|
852
|
+
s = ""
|
853
|
+
if args.empty?
|
854
|
+
s << "\n"
|
855
|
+
end
|
856
|
+
args.each do |arg|
|
857
|
+
s << arg.to_s
|
858
|
+
if $/ && /\n\z/ !~ s
|
859
|
+
s << "\n"
|
869
860
|
end
|
870
|
-
data = @buffer.fill(data)
|
871
861
|
end
|
872
|
-
|
873
|
-
return(
|
862
|
+
do_write(s)
|
863
|
+
return(nil)
|
864
|
+
end
|
865
|
+
|
866
|
+
def print(*args)
|
867
|
+
s = ""
|
868
|
+
args.each{ |arg| s << arg.to_s }
|
869
|
+
do_write(s)
|
870
|
+
return(nil)
|
871
|
+
end
|
872
|
+
|
873
|
+
def printf(s, *args)
|
874
|
+
do_write(s % args)
|
875
|
+
return(nil)
|
874
876
|
end
|
875
877
|
|
876
|
-
##
|
877
|
-
# Flushes any buffered data
|
878
|
-
#
|
879
878
|
def flush
|
880
|
-
|
881
|
-
|
882
|
-
|
883
|
-
|
879
|
+
osync = @sync
|
880
|
+
@sync = true
|
881
|
+
do_write ""
|
882
|
+
@sync = osync
|
884
883
|
end
|
885
884
|
|
886
885
|
##
|
887
|
-
#
|
886
|
+
# Seek in the file. The whence values may be one of SEEK_SET
|
887
|
+
# SEEK_CUR, or SEEK_END, as defined in rstyx/common, and they
|
888
|
+
# result in the offset being taken from the beginning of the
|
889
|
+
# file, relative to the current offset, or from the end of the
|
890
|
+
# file respectively.
|
888
891
|
#
|
889
|
-
#
|
892
|
+
# XXX: A seek, no matter where it goes, will always invalidate
|
893
|
+
# any buffering. This is fine for write buffers, but is
|
894
|
+
# somewhat wasteful for the read buffers.
|
890
895
|
#
|
891
|
-
def seek(offset, whence
|
892
|
-
# flush the buffers
|
896
|
+
def seek(offset, whence)
|
897
|
+
# Before seeking, flush the write buffers
|
893
898
|
flush
|
899
|
+
s = self.stat
|
894
900
|
case whence
|
895
|
-
when
|
896
|
-
|
897
|
-
|
898
|
-
|
899
|
-
|
901
|
+
when 0
|
902
|
+
@offset = offset
|
903
|
+
when 1
|
904
|
+
@offset += offset
|
905
|
+
when 2
|
906
|
+
# We have to obtain the stat of the file to do this kind of seek.
|
907
|
+
@offset = s.length + offset
|
908
|
+
else
|
909
|
+
raise StyxException.new("Invalid seek")
|
900
910
|
end
|
901
|
-
|
902
|
-
@
|
911
|
+
# After seeking, discard the read buffers
|
912
|
+
@rbuffer = ""
|
913
|
+
@rboffset = @offset
|
914
|
+
@eof = (@offset >= s.length)
|
915
|
+
return(@offset)
|
916
|
+
end
|
917
|
+
|
918
|
+
def tell
|
919
|
+
return(@offset)
|
903
920
|
end
|
904
921
|
|
905
|
-
##
|
906
|
-
# Seek to the start of the file
|
907
|
-
#
|
908
922
|
def rewind
|
909
|
-
seek(0,
|
923
|
+
seek(0, 0)
|
910
924
|
end
|
911
925
|
|
912
926
|
##
|
913
|
-
#
|
927
|
+
# Reads +size+ bytes from the Styx file and returns them as a string.
|
928
|
+
# Do not mix with other methods that read from the Styx file or you
|
929
|
+
# may get unpredictable results.
|
914
930
|
#
|
915
|
-
def
|
916
|
-
|
931
|
+
def sysread(size=-1)
|
932
|
+
data, @offset = _sysread(size, @offset)
|
933
|
+
if data.length == 0
|
934
|
+
return(nil)
|
935
|
+
end
|
936
|
+
return(data)
|
917
937
|
end
|
918
938
|
|
919
939
|
##
|
920
|
-
#
|
921
|
-
#
|
922
|
-
|
923
|
-
|
924
|
-
|
940
|
+
# Writes +data+ to the Styx file. Returns the number of bytes written.
|
941
|
+
# do not mix with other methods that write to the Styx file or you
|
942
|
+
# may get unpredictable results.
|
943
|
+
def syswrite(data)
|
944
|
+
@offset, count = _syswrite(data, @offset)
|
945
|
+
return(count)
|
925
946
|
end
|
926
947
|
|
927
|
-
end
|
948
|
+
end # class File
|
928
949
|
|
929
950
|
##
|
930
|
-
#
|
931
|
-
#
|
951
|
+
# Styx directory. This obtains the entries inside a directory, and
|
952
|
+
# works by delegating to File.
|
932
953
|
#
|
933
|
-
class
|
954
|
+
class Directory
|
934
955
|
include Enumerable
|
935
956
|
|
936
|
-
def initialize(
|
937
|
-
@io =
|
957
|
+
def initialize(fp)
|
958
|
+
@io = fp
|
938
959
|
# directory entry buffer
|
939
960
|
@read_direntries = []
|
961
|
+
# byte buffer
|
962
|
+
@data = ""
|
940
963
|
end
|
941
964
|
|
942
965
|
def close
|
943
966
|
@io.close
|
944
967
|
end
|
945
968
|
|
946
|
-
##
|
947
|
-
# get the fid corresponding to this directory
|
948
|
-
#
|
949
969
|
def fid
|
950
970
|
@io.fid
|
951
971
|
end
|
952
972
|
|
973
|
+
def qid
|
974
|
+
@io.qid
|
975
|
+
end
|
976
|
+
|
953
977
|
##
|
954
978
|
# Read the next directory entry from the dir and return the file
|
955
979
|
# name as a string. Returns nil at the end of stream.
|
@@ -961,18 +985,25 @@ module RStyx
|
|
961
985
|
return(@read_direntries.shift.name)
|
962
986
|
end
|
963
987
|
# read iounit bytes from the directory--this must be unbuffered
|
964
|
-
|
988
|
+
d = @io.sysread
|
989
|
+
if d.nil?
|
990
|
+
return(nil)
|
991
|
+
end
|
992
|
+
@data << d
|
965
993
|
|
966
|
-
if (data.
|
994
|
+
if (@data.empty?)
|
967
995
|
return(nil)
|
968
996
|
end
|
969
997
|
|
970
998
|
# decode the directory entries in the iounit
|
971
|
-
|
972
|
-
delen = data
|
973
|
-
|
974
|
-
|
975
|
-
|
999
|
+
loop do
|
1000
|
+
delen = @data.unpack("v")[0]
|
1001
|
+
if delen.nil? || delen + 1 > @data.length
|
1002
|
+
break
|
1003
|
+
end
|
1004
|
+
edirent = @data[0..(delen + 1)]
|
1005
|
+
@data = @data[(delen + 2)..-1]
|
1006
|
+
@read_direntries << Message::Stat.from_bytes(edirent)
|
976
1007
|
end
|
977
1008
|
de = @read_direntries.shift
|
978
1009
|
if (de.nil?)
|
@@ -982,36 +1013,21 @@ module RStyx
|
|
982
1013
|
end
|
983
1014
|
|
984
1015
|
##
|
985
|
-
#
|
986
|
-
#
|
987
|
-
#
|
988
|
-
def rewind
|
989
|
-
@io.rewind
|
990
|
-
end
|
991
|
-
|
992
|
-
##
|
993
|
-
# same as StyxIO#stat
|
994
|
-
#
|
995
|
-
def stat
|
996
|
-
return(@io.stat)
|
997
|
-
end
|
998
|
-
|
999
|
-
##
|
1000
|
-
# Call the block once for each entry in the directory, passing the
|
1001
|
-
# filename of each entry as a parameter to the block.
|
1016
|
+
# Call the block once for each entry in the directory, passing
|
1017
|
+
# the filename of each entry as a parameter to the block.
|
1002
1018
|
#
|
1003
1019
|
def each
|
1004
1020
|
if !block_given?
|
1005
|
-
raise LocalJumpError("no block given")
|
1021
|
+
raise LocalJumpError.new("no block given")
|
1006
1022
|
end
|
1007
1023
|
|
1008
1024
|
self.rewind
|
1009
|
-
|
1025
|
+
until (de = read).nil?
|
1010
1026
|
yield de
|
1011
1027
|
end
|
1012
1028
|
end
|
1013
1029
|
|
1014
|
-
end
|
1030
|
+
end # class Directory
|
1015
1031
|
|
1016
1032
|
end # module Client
|
1017
1033
|
|