rstyx 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/rstyx/client.rb +570 -51
- data/lib/rstyx/messages.rb +8 -5
- data/lib/rstyx/version.rb +2 -2
- data/tests/tc_client.rb +74 -0
- data/tests/tc_message.rb +4 -3
- metadata +20 -24
data/lib/rstyx/client.rb
CHANGED
@@ -24,9 +24,10 @@
|
|
24
24
|
# Copyright:: Copyright (c) 2005 Rafael R. Sevilla
|
25
25
|
# License:: GNU Lesser General Public License
|
26
26
|
#
|
27
|
-
# $Id: client.rb,v 1.
|
27
|
+
# $Id: client.rb,v 1.10 2005/11/01 13:02:18 dido Exp $
|
28
28
|
#
|
29
29
|
|
30
|
+
require 'English'
|
30
31
|
require 'socket'
|
31
32
|
require 'thread'
|
32
33
|
require 'timeout'
|
@@ -67,17 +68,23 @@ module RStyx
|
|
67
68
|
end
|
68
69
|
return(val)
|
69
70
|
end
|
71
|
+
|
70
72
|
end
|
71
73
|
|
72
74
|
##
|
73
75
|
# An abstract class for Styx connections. DO NOT INSTANTIATE THIS
|
74
76
|
# CLASS DIRECTLY!
|
75
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
|
+
#
|
76
85
|
class Connection
|
77
86
|
attr_reader :msize, :version, :rootfid, :user
|
78
87
|
|
79
|
-
TIMEOUT = 60
|
80
|
-
|
81
88
|
##
|
82
89
|
# Create a new Styx connection. If a block is passed, yield the
|
83
90
|
# new connection to the block and do a disconnect when the block
|
@@ -288,35 +295,154 @@ module RStyx
|
|
288
295
|
#
|
289
296
|
# +path+:: the path to the Styx file to be opened
|
290
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.
|
291
305
|
#
|
292
|
-
# FIXME: This doesn't deal with the case where MAXWELEM is reached
|
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).
|
293
310
|
#
|
294
|
-
def open(path, mode='r')
|
311
|
+
def open(path="", mode='r', perm=0644, rfid=nil)
|
295
312
|
# get a new fid for the file
|
296
313
|
fid = get_free_fid
|
297
|
-
|
314
|
+
|
315
|
+
if rfid.nil?
|
316
|
+
rfid = @rootfid
|
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:
|
334
|
+
#
|
335
|
+
# "e" - execute the file
|
336
|
+
#
|
337
|
+
# The "b" suffix on DOS/Windows is not recognized as valid.
|
338
|
+
#
|
339
|
+
read = true
|
340
|
+
write = false
|
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
|
385
|
+
end
|
386
|
+
|
387
|
+
iclass = StyxIO
|
298
388
|
begin
|
299
|
-
twalk = Message::Twalk.new(
|
389
|
+
twalk = Message::Twalk.new(rfid, fid, path)
|
300
390
|
rwalk = send_message(twalk)
|
301
391
|
# if the rwalk has some other length than the number of path
|
302
392
|
# elements in the original twalk, we have failed.
|
303
393
|
if rwalk.qids.length != twalk.path_elements.length
|
304
394
|
raise StyxException.new(sprintf("%s: no such file or directory", twalk.path_elements[rwalk.qids.length]))
|
305
395
|
end
|
306
|
-
|
307
|
-
#
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
396
|
+
open_msg = Message::Topen.new(fid, mval)
|
397
|
+
# Determine if the file is actually a directory. Such a file
|
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.
|
401
|
+
#
|
402
|
+
# If the number of path elements involved is zero, assume a
|
403
|
+
# directory, as the root fid is always one.
|
404
|
+
#
|
405
|
+
if twalk.path_elements.length == 0 ||
|
406
|
+
rwalk.qids[twalk.path_elements.length-1].qtype & 0x80 != 0
|
407
|
+
iclass = StyxDir
|
408
|
+
end
|
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
|
430
|
+
end
|
431
|
+
|
432
|
+
begin
|
314
433
|
# Create, open, and return a StyxIO object
|
315
|
-
ropen = send_message(
|
316
|
-
fp =
|
434
|
+
ropen = send_message(open_msg)
|
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
|
442
|
+
|
317
443
|
if block_given?
|
318
444
|
begin
|
319
|
-
|
445
|
+
yield fp
|
320
446
|
ensure
|
321
447
|
fp.close
|
322
448
|
end
|
@@ -325,7 +451,68 @@ module RStyx
|
|
325
451
|
end
|
326
452
|
rescue StyxException => se
|
327
453
|
return_fid(fid)
|
328
|
-
raise se
|
454
|
+
raise se # reraise the exception
|
455
|
+
end
|
456
|
+
end
|
457
|
+
|
458
|
+
##
|
459
|
+
# Delete the files specified. Unlike File.delete, this method
|
460
|
+
# should also work properly on directories
|
461
|
+
#
|
462
|
+
def delete(*files)
|
463
|
+
files.each do |file|
|
464
|
+
fid = get_free_fid
|
465
|
+
# walk to the file to be deleted
|
466
|
+
begin
|
467
|
+
rwalk = send_message(Message::Twalk.new(@rootfid, fid, file))
|
468
|
+
rescue StyxException => se
|
469
|
+
return_fid(fid)
|
470
|
+
raise se
|
471
|
+
end
|
472
|
+
|
473
|
+
begin
|
474
|
+
rremove = send_message(Message::Tremove.new(fid))
|
475
|
+
return_fid(fid)
|
476
|
+
rescue StyxException => se
|
477
|
+
# because the Twalk succeeded, we need to clunk the fid
|
478
|
+
begin
|
479
|
+
rclunk = send_message(Message::Tclunk.new(fid))
|
480
|
+
rescue StyxException => se
|
481
|
+
return_fid(fid)
|
482
|
+
end
|
483
|
+
raise se
|
484
|
+
end
|
485
|
+
end
|
486
|
+
end
|
487
|
+
|
488
|
+
alias rmdir delete
|
489
|
+
alias unlink delete
|
490
|
+
|
491
|
+
##
|
492
|
+
# Create a new directory named by +dirname+, with permissions
|
493
|
+
# specified by an optional parameter +perm+.
|
494
|
+
#
|
495
|
+
def mkdir(dirname, perm=0755)
|
496
|
+
begin
|
497
|
+
fid = get_free_fid
|
498
|
+
perm |= Message::Tcreate::DMDIR
|
499
|
+
rwalk = send_message(Message::Twalk.new(@rootfid, fid,
|
500
|
+
File.dirname(dirname)))
|
501
|
+
rescue StyxException => se
|
502
|
+
return_fid(fid)
|
503
|
+
raise se
|
504
|
+
end
|
505
|
+
|
506
|
+
begin
|
507
|
+
rcreate = send_message(Message::Tcreate.new(fid,
|
508
|
+
File.basename(dirname),
|
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))
|
329
516
|
end
|
330
517
|
end
|
331
518
|
|
@@ -361,13 +548,85 @@ module RStyx
|
|
361
548
|
end
|
362
549
|
|
363
550
|
##
|
364
|
-
#
|
365
|
-
|
551
|
+
# Internally used class for buffers
|
552
|
+
class Buffer
|
553
|
+
attr_reader :bufsize
|
554
|
+
|
555
|
+
def initialize(bufsize)
|
556
|
+
@bufsize = bufsize # maximum size of the buffer
|
557
|
+
@data = "" # data in the buffer
|
558
|
+
end
|
559
|
+
|
560
|
+
##
|
561
|
+
# Returns true if the buffer is empty
|
562
|
+
#
|
563
|
+
def empty?
|
564
|
+
return(@data.length == 0)
|
565
|
+
end
|
566
|
+
|
567
|
+
##
|
568
|
+
# Returns true if the buffer is full
|
569
|
+
def full?
|
570
|
+
return(@data.length == @bufsize)
|
571
|
+
end
|
572
|
+
|
573
|
+
##
|
574
|
+
# How much data is needed to fill the buffer?
|
575
|
+
#
|
576
|
+
def slack
|
577
|
+
return(@bufsize - @data.length)
|
578
|
+
end
|
579
|
+
|
580
|
+
##
|
581
|
+
# Empty the buffer. Return all the data in the buffer
|
582
|
+
#
|
583
|
+
def empty
|
584
|
+
retval = @data
|
585
|
+
@data = ""
|
586
|
+
return(retval)
|
587
|
+
end
|
588
|
+
|
589
|
+
##
|
590
|
+
# Consume data from the buffer. Returns a string of at most +size+
|
591
|
+
# length from the buffer.
|
592
|
+
#
|
593
|
+
def consume(size)
|
594
|
+
retval = @data[0..(size-1)]
|
595
|
+
@data = @data[size..@data.length]
|
596
|
+
if @data == nil # if the buffer goes empty
|
597
|
+
@data = ""
|
598
|
+
end
|
599
|
+
return(retval)
|
600
|
+
end
|
601
|
+
|
602
|
+
##
|
603
|
+
# Write data to the buffer. Returns the data that was not actually
|
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)
|
612
|
+
end
|
613
|
+
|
614
|
+
end
|
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.
|
366
619
|
#
|
367
620
|
# TODO: This should later become a true subclass of IO.
|
368
621
|
#
|
369
622
|
class StyxIO
|
370
|
-
attr_reader :name, :mode, :closed, :offset
|
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
|
371
630
|
|
372
631
|
def initialize(conn, name, fid, iounit, mode="r")
|
373
632
|
@conn = conn
|
@@ -377,25 +636,25 @@ module RStyx
|
|
377
636
|
@fid = fid
|
378
637
|
@iounit = iounit # maximum payload of a Twrite or Rread message
|
379
638
|
@offset = 0 # file offset
|
380
|
-
|
639
|
+
@sync = true
|
381
640
|
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
def close
|
387
|
-
flush # flush any bytes buffered (later)
|
388
|
-
rclunk = @conn.send_message(Message::Tclunk.new(@fid))
|
389
|
-
# FIXME: abort all outstanding messages with flushes
|
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
|
390
645
|
end
|
391
646
|
|
392
647
|
##
|
393
|
-
# Read at most +size+ bytes from
|
648
|
+
# Read at most +size+ bytes from +offset+
|
394
649
|
# If the size argument is negative or omitted, read until EOF.
|
650
|
+
# This is an unbuffered read! This function should probably
|
651
|
+
# not be used directly.
|
395
652
|
#
|
396
653
|
# +size+:: number of bytes to read from the file
|
654
|
+
# +offset+:: the offset to read from.
|
655
|
+
# return:: the data followed by the new offset
|
397
656
|
#
|
398
|
-
def
|
657
|
+
def sysread_int(size=-1, offset=0)
|
399
658
|
contents = ""
|
400
659
|
bytes_to_read = size
|
401
660
|
loop do
|
@@ -406,27 +665,31 @@ module RStyx
|
|
406
665
|
else
|
407
666
|
n = bytes_to_read
|
408
667
|
end
|
409
|
-
rread = @conn.send_message(Message::Tread.new(@fid,
|
668
|
+
rread = @conn.send_message(Message::Tread.new(@fid, offset, n))
|
410
669
|
if rread.data.length == 0
|
411
670
|
break # EOF
|
412
671
|
end
|
413
|
-
|
672
|
+
offset += rread.data.length
|
414
673
|
contents << rread.data
|
415
674
|
if size >= 0
|
416
675
|
bytes_to_read -= rread.data.length
|
417
676
|
end
|
418
677
|
end
|
419
|
-
return(contents)
|
678
|
+
return([contents, offset])
|
420
679
|
end
|
421
680
|
|
422
681
|
##
|
423
682
|
#
|
424
|
-
# Write data to the file at
|
425
|
-
# performed
|
683
|
+
# Write data to the file at +offset+. No buffering is
|
684
|
+
# performed. This function should probably not be used
|
685
|
+
# directly.
|
426
686
|
#
|
427
687
|
# +str+:: data to be written
|
688
|
+
# +offset+:: the offset to write at
|
428
689
|
#
|
429
|
-
|
690
|
+
# returns the new offset
|
691
|
+
#
|
692
|
+
def syswrite_int(str, offset)
|
430
693
|
pos = 0
|
431
694
|
loop do
|
432
695
|
bytes_left = str.length - pos
|
@@ -437,28 +700,197 @@ module RStyx
|
|
437
700
|
else
|
438
701
|
n = bytes_left
|
439
702
|
end
|
440
|
-
rwrite = @conn.send_message(Message::Twrite.new(@fid,
|
703
|
+
rwrite = @conn.send_message(Message::Twrite.new(@fid, offset,
|
441
704
|
str[pos..(pos+n)]))
|
442
705
|
if rwrite.count != n
|
443
706
|
raise StyxException.new("error writing data")
|
444
707
|
end
|
445
708
|
pos += n
|
446
|
-
|
709
|
+
offset += n
|
447
710
|
end
|
711
|
+
return(offset)
|
712
|
+
end
|
448
713
|
|
714
|
+
public
|
715
|
+
|
716
|
+
##
|
717
|
+
# closes the file. Once this has been done, no more read or write
|
718
|
+
# operations should be possible.
|
719
|
+
#
|
720
|
+
def close
|
721
|
+
flush # flush any bytes buffered
|
722
|
+
rclunk = @conn.send_message(Message::Tclunk.new(@fid))
|
723
|
+
# FIXME: abort all outstanding messages with flushes
|
724
|
+
end
|
725
|
+
|
726
|
+
##
|
727
|
+
# Read at most +size+ bytes from the current file position.
|
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
|
734
|
+
#
|
735
|
+
def read(size=-1)
|
736
|
+
# flush any buffered data first, so the buffer becomes empty
|
737
|
+
if @lastop == :write
|
738
|
+
flush
|
739
|
+
end
|
740
|
+
@lastop = :read
|
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
|
761
|
+
|
762
|
+
@offset += d.length
|
763
|
+
contents << d
|
764
|
+
if size >= 0
|
765
|
+
bytes_to_read -= d.length
|
766
|
+
end
|
767
|
+
|
768
|
+
if bytes_to_read == 0
|
769
|
+
break
|
770
|
+
end
|
771
|
+
end
|
772
|
+
if (contents.length == 0)
|
773
|
+
return(nil)
|
774
|
+
end
|
775
|
+
return(contents)
|
776
|
+
end
|
777
|
+
|
778
|
+
##
|
779
|
+
# Do an unbuffered read.
|
780
|
+
#
|
781
|
+
def sysread(size=-1)
|
782
|
+
unless @buffer.empty?
|
783
|
+
raise IOError.new("sysread for buffered IO")
|
784
|
+
end
|
785
|
+
data, @offset = sysread_int(size, @offset)
|
786
|
+
if (data.length == 0)
|
787
|
+
return(nil)
|
788
|
+
end
|
789
|
+
return(data)
|
790
|
+
end
|
791
|
+
|
792
|
+
##
|
793
|
+
# Calls the given block once for each byte (0..255) in the StyxIO,
|
794
|
+
# passing the byte as an argument. The Styx file must be opened
|
795
|
+
# for reading.
|
796
|
+
#
|
797
|
+
def each_byte
|
798
|
+
if !block_given?
|
799
|
+
raise LocalJumpError("no block given")
|
800
|
+
end
|
801
|
+
|
802
|
+
while (!(c=read(1)).nil?)
|
803
|
+
yield c[0]
|
804
|
+
end
|
805
|
+
end
|
806
|
+
|
807
|
+
##
|
808
|
+
# Reads the next "line" from the Styx file; lines are separated by
|
809
|
+
# sep_str. A separator of nil reads the entire contents, [and a
|
810
|
+
# zero length separator reads the input a paragraph at a time (two
|
811
|
+
# successive newlines in the input separate paragraphs) TODO: this
|
812
|
+
# needs to be implemented]. The Styxfile
|
813
|
+
# must be opened for reading. The line read in will be returned and
|
814
|
+
# also assigned to $LAST_READ_LINE. Returns nil if called at end
|
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)
|
827
|
+
end
|
828
|
+
return(line)
|
829
|
+
end
|
830
|
+
|
831
|
+
##
|
832
|
+
# Executes the passed block for every line in styxio, where lines
|
833
|
+
# are separated by sep_str. The Styx file must be opened for
|
834
|
+
# reading.
|
835
|
+
#
|
836
|
+
def each
|
837
|
+
if !block_given?
|
838
|
+
raise LocalJumpError("no block given")
|
839
|
+
end
|
840
|
+
|
841
|
+
while (!(line = gets).nil?)
|
842
|
+
yield line
|
843
|
+
end
|
844
|
+
end
|
845
|
+
|
846
|
+
##
|
847
|
+
# A buffered write of data to the file.
|
848
|
+
#
|
849
|
+
def write(data)
|
850
|
+
if @lastop == :read
|
851
|
+
@buffer.empty # empty the buffer
|
852
|
+
end
|
853
|
+
@lastop = :write
|
854
|
+
|
855
|
+
# If the sync flag is set, simply write the data straight
|
856
|
+
# to the file with no buffering at all.
|
857
|
+
if @sync
|
858
|
+
@offset = syswrite_int(data, @offset)
|
859
|
+
return(data.length)
|
860
|
+
end
|
861
|
+
|
862
|
+
# If the sync flag is not set, try to write the data to the buffer
|
863
|
+
# until the buffer becomes full.
|
864
|
+
bytes_written = data.length
|
865
|
+
while !data.nil?
|
866
|
+
if @buffer.full?
|
867
|
+
# flush the buffer if the buffer fills up
|
868
|
+
flush
|
869
|
+
end
|
870
|
+
data = @buffer.fill(data)
|
871
|
+
end
|
872
|
+
@offset += bytes_written
|
873
|
+
return(bytes_written)
|
449
874
|
end
|
450
875
|
|
451
876
|
##
|
452
877
|
# Flushes any buffered data
|
453
878
|
#
|
454
879
|
def flush
|
455
|
-
|
880
|
+
if @lastop == :write
|
881
|
+
@boffset = syswrite_int(@buffer.empty, @boffset)
|
882
|
+
return(self)
|
883
|
+
end
|
456
884
|
end
|
457
885
|
|
458
886
|
##
|
459
887
|
# Move to a new seek position
|
460
888
|
#
|
889
|
+
# TODO: implement :seek_end
|
890
|
+
#
|
461
891
|
def seek(offset, whence=:seek_set)
|
892
|
+
# flush the buffers before doing the seek
|
893
|
+
flush
|
462
894
|
case whence
|
463
895
|
when :seek_set: @offset = offset
|
464
896
|
when :seek_cur: @offset += offset
|
@@ -466,8 +898,17 @@ module RStyx
|
|
466
898
|
when :seek_end: raise StyxException.new("Can't seek from end of file")
|
467
899
|
else raise StyxException.new("whence must be :seek_set, :seek_cur, or :seek_end")
|
468
900
|
end
|
901
|
+
@boffset = @offset
|
902
|
+
@buffer.empty
|
469
903
|
end
|
470
|
-
|
904
|
+
|
905
|
+
##
|
906
|
+
# Seek to the start of the file
|
907
|
+
#
|
908
|
+
def rewind
|
909
|
+
seek(0, :seek_set)
|
910
|
+
end
|
911
|
+
|
471
912
|
##
|
472
913
|
# Return the current file position
|
473
914
|
#
|
@@ -485,15 +926,93 @@ module RStyx
|
|
485
926
|
|
486
927
|
end
|
487
928
|
|
488
|
-
|
929
|
+
##
|
930
|
+
# A directory on a Styx server. This obtains the entries inside
|
931
|
+
# a directory. This actually delegates to StyxIO.
|
932
|
+
#
|
933
|
+
class StyxDir
|
934
|
+
include Enumerable
|
489
935
|
|
490
|
-
|
936
|
+
def initialize(conn, name, fid, iounit, mode="r")
|
937
|
+
@io = StyxIO.new(conn, name, fid, iounit, mode)
|
938
|
+
# directory entry buffer
|
939
|
+
@read_direntries = []
|
940
|
+
end
|
941
|
+
|
942
|
+
def close
|
943
|
+
@io.close
|
944
|
+
end
|
945
|
+
|
946
|
+
##
|
947
|
+
# get the fid corresponding to this directory
|
948
|
+
#
|
949
|
+
def fid
|
950
|
+
@io.fid
|
951
|
+
end
|
952
|
+
|
953
|
+
##
|
954
|
+
# Read the next directory entry from the dir and return the file
|
955
|
+
# name as a string. Returns nil at the end of stream.
|
956
|
+
#
|
957
|
+
def read
|
958
|
+
# if there are directory entries left over from the previous
|
959
|
+
# read that have not yet been returned, return them.
|
960
|
+
if @read_direntries.length != 0
|
961
|
+
return(@read_direntries.shift.name)
|
962
|
+
end
|
963
|
+
# read iounit bytes from the directory--this must be unbuffered
|
964
|
+
data = @io.read(@io.iounit)
|
965
|
+
|
966
|
+
if (data.nil?)
|
967
|
+
return(nil)
|
968
|
+
end
|
969
|
+
|
970
|
+
# decode the directory entries in the iounit
|
971
|
+
while data.length != 0
|
972
|
+
delen = data[0..1].unpack("v")[0]
|
973
|
+
edirent = data[0..(delen + 1)]
|
974
|
+
data = data[(delen + 2)..(data.length)]
|
975
|
+
@read_direntries << Message::DirEntry.decode(edirent)
|
976
|
+
end
|
977
|
+
de = @read_direntries.shift
|
978
|
+
if (de.nil?)
|
979
|
+
return(nil)
|
980
|
+
end
|
981
|
+
return(de.name)
|
982
|
+
end
|
983
|
+
|
984
|
+
##
|
985
|
+
# Seek to the beginning of the directory. This is the only type of
|
986
|
+
# seek allowed.
|
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.
|
1002
|
+
#
|
1003
|
+
def each
|
1004
|
+
if !block_given?
|
1005
|
+
raise LocalJumpError("no block given")
|
1006
|
+
end
|
1007
|
+
|
1008
|
+
self.rewind
|
1009
|
+
while (!(de = self.read).nil?)
|
1010
|
+
yield de
|
1011
|
+
end
|
1012
|
+
end
|
491
1013
|
|
492
|
-
if $0 == __FILE__
|
493
|
-
RStyx::Client::TCPConnection.new("127.0.0.1", 1234) do |c|
|
494
|
-
c.open("hello.txt", "r+") do |fp|
|
495
|
-
puts fp.stat.to_s
|
496
1014
|
end
|
497
|
-
end
|
498
|
-
end
|
499
1015
|
|
1016
|
+
end # module Client
|
1017
|
+
|
1018
|
+
end # module RStyx
|
data/lib/rstyx/messages.rb
CHANGED
@@ -24,7 +24,7 @@
|
|
24
24
|
# Copyright:: Copyright (c) 2005 Rafael R. Sevilla
|
25
25
|
# License:: GNU Lesser General Public License
|
26
26
|
#
|
27
|
-
# $Id: messages.rb,v 1.
|
27
|
+
# $Id: messages.rb,v 1.26 2005/11/01 11:40:26 dido Exp $
|
28
28
|
#
|
29
29
|
|
30
30
|
require 'rstyx/errors'
|
@@ -163,7 +163,7 @@ module RStyx
|
|
163
163
|
str << Util.strpack(@gid)
|
164
164
|
str << Util.strpack(@muid)
|
165
165
|
@size = str.length
|
166
|
-
return([size].pack("v") + str)
|
166
|
+
return([@size].pack("v") + str)
|
167
167
|
end
|
168
168
|
|
169
169
|
##
|
@@ -1340,6 +1340,7 @@ module RStyx
|
|
1340
1340
|
@stat = stat
|
1341
1341
|
@mtype = RSTAT
|
1342
1342
|
@messagebody = @stat.to_bytes
|
1343
|
+
@messagebody = [@stat.size + 2].pack("v") + @messagebody
|
1343
1344
|
@length = 7 + @messagebody.length
|
1344
1345
|
@body = sprintf("%s", @stat)
|
1345
1346
|
end
|
@@ -1353,7 +1354,7 @@ module RStyx
|
|
1353
1354
|
#
|
1354
1355
|
def Rstat.decode(message)
|
1355
1356
|
len = message[0..3].unpack("V")[0]
|
1356
|
-
stat = DirEntry.decode(message[
|
1357
|
+
stat = DirEntry.decode(message[9..len])
|
1357
1358
|
return(Rstat.new(stat))
|
1358
1359
|
end
|
1359
1360
|
|
@@ -1375,7 +1376,9 @@ module RStyx
|
|
1375
1376
|
@fid = fid
|
1376
1377
|
@stat = stat
|
1377
1378
|
@mtype = TWSTAT
|
1378
|
-
@messagebody =
|
1379
|
+
@messagebody = @stat.to_bytes
|
1380
|
+
@messagebody = [@fid].pack("V") +
|
1381
|
+
[@stat.size + 2].pack("v") + @messagebody
|
1379
1382
|
@length = 7 + @messagebody.length
|
1380
1383
|
@body = sprintf("%s, %s", @fid, @stat)
|
1381
1384
|
end
|
@@ -1390,7 +1393,7 @@ module RStyx
|
|
1390
1393
|
def Twstat.decode(message)
|
1391
1394
|
len = message[0..3].unpack("V")[0]
|
1392
1395
|
fid = message[7..10].unpack("V")[0]
|
1393
|
-
stat = DirEntry.decode(message[
|
1396
|
+
stat = DirEntry.decode(message[13..len])
|
1394
1397
|
return(Twstat.new(fid, stat))
|
1395
1398
|
end
|
1396
1399
|
|
data/lib/rstyx/version.rb
CHANGED
@@ -20,14 +20,14 @@
|
|
20
20
|
#
|
21
21
|
# RStyx version code
|
22
22
|
#
|
23
|
-
# $Id: version.rb,v 1.
|
23
|
+
# $Id: version.rb,v 1.2 2005/11/01 13:17:27 dido Exp $
|
24
24
|
#
|
25
25
|
|
26
26
|
module RStyx
|
27
27
|
module Version
|
28
28
|
|
29
29
|
MAJOR = 0
|
30
|
-
MINOR =
|
30
|
+
MINOR = 2
|
31
31
|
TINY = 0
|
32
32
|
|
33
33
|
# The version of RStyx in use.
|
data/tests/tc_client.rb
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
#
|
3
|
+
# Copyright (C) 2005 Rafael Sevilla
|
4
|
+
# This file is part of RStyx
|
5
|
+
#
|
6
|
+
# RStyx is free software; you can redistribute it and/or modify
|
7
|
+
# it under the terms of the GNU Lesser General Public License as
|
8
|
+
# published by the Free Software Foundation; either version 2.1
|
9
|
+
# of the License, or (at your option) any later version.
|
10
|
+
#
|
11
|
+
# RStyx is distributed in the hope that it will be useful, but
|
12
|
+
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
# GNU Lesser General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU Lesser General Public
|
17
|
+
# License along with RStyx; if not, write to the Free Software
|
18
|
+
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
19
|
+
# 02111-1307 USA.
|
20
|
+
#
|
21
|
+
# Unit tests for Styx client.
|
22
|
+
#
|
23
|
+
# To use these tests, redefine CONNECT_HOST and CONNECT_PORT to point to
|
24
|
+
# a Styx server that will accept connections with no authentication and
|
25
|
+
# will allow write access. I'm currently using 4th Edition Inferno hosted
|
26
|
+
# on GNU/Linux to do this
|
27
|
+
#
|
28
|
+
# $Id: tc_client.rb,v 1.2 2005/11/01 13:17:04 dido Exp $
|
29
|
+
#
|
30
|
+
require 'test/unit'
|
31
|
+
require 'rstyx'
|
32
|
+
|
33
|
+
class ClientTest < Test::Unit::TestCase
|
34
|
+
CONNECT_HOST = "127.0.0.1"
|
35
|
+
CONNECT_PORT = "1234"
|
36
|
+
TEST_DIRECTORY = "/rstyx_test_dir"
|
37
|
+
TEST_FILE = TEST_DIRECTORY + "/test.file"
|
38
|
+
|
39
|
+
def test_client
|
40
|
+
RStyx::Client::TCPConnection.new(CONNECT_HOST, CONNECT_PORT) do |c|
|
41
|
+
begin
|
42
|
+
# Create the directory
|
43
|
+
assert_nothing_raised { c.mkdir(TEST_DIRECTORY) }
|
44
|
+
# Try to create the directory again (this should result in an error)
|
45
|
+
assert_raise(RStyx::StyxException) { c.mkdir(TEST_DIRECTORY) }
|
46
|
+
|
47
|
+
# open a file in the directory for writing, and fill it with data
|
48
|
+
assert_nothing_raised do
|
49
|
+
c.open(TEST_FILE, "w") do |fp|
|
50
|
+
0.upto(1024) do |i|
|
51
|
+
fp.write(sprintf("%04x\n", i))
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Open the file for reading, and check that the data is correct
|
57
|
+
c.open(TEST_FILE, "r") do |fp|
|
58
|
+
i = 0
|
59
|
+
fp.each do |line|
|
60
|
+
assert(line == sprintf("%04x\n", i))
|
61
|
+
i += 1
|
62
|
+
end
|
63
|
+
end
|
64
|
+
ensure
|
65
|
+
assert_nothing_raised { c.delete(TEST_FILE) }
|
66
|
+
assert_raise(RStyx::StyxException) { c.delete(TEST_FILE) }
|
67
|
+
# Make sure the directories are deleted.
|
68
|
+
assert_nothing_raised { c.rmdir("/rstyx_test_dir") }
|
69
|
+
# Deleting the directory twice should generate an error
|
70
|
+
assert_raise(RStyx::StyxException) { c.rmdir(TEST_DIRECTORY) }
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
data/tests/tc_message.rb
CHANGED
@@ -20,7 +20,7 @@
|
|
20
20
|
#
|
21
21
|
# Unit tests for Styx messages
|
22
22
|
#
|
23
|
-
# $Id: tc_message.rb,v 1.
|
23
|
+
# $Id: tc_message.rb,v 1.25 2005/11/01 11:44:06 dido Exp $
|
24
24
|
#
|
25
25
|
require 'test/unit'
|
26
26
|
require 'rstyx/messages'
|
@@ -1138,10 +1138,11 @@ class MessageTest < Test::Unit::TestCase
|
|
1138
1138
|
de.muid = "quux"
|
1139
1139
|
rs = RStyx::Message::Rstat.new(de)
|
1140
1140
|
bytes = rs.to_bytes(0x1234)
|
1141
|
-
expect = "\x7d\x34\x12\x3c\x00\x34\x12\xab\x90\x78\x56\x01\xef\xbe\xad\xde\xee\xee\xff\xc0\xce\xfa\xed\xfe\xf0\xde\xbc\x9a\xef\xbe\xad\xde\xbe\xba\xfe\xca\x10\x32\x54\x76\x98\xba\xdc\xfe\x03\x00foo\x03\x00bar\x03\x00baz\x04\x00quux"
|
1141
|
+
expect = "\x7d\x34\x12\x3e\x00\x3c\x00\x34\x12\xab\x90\x78\x56\x01\xef\xbe\xad\xde\xee\xee\xff\xc0\xce\xfa\xed\xfe\xf0\xde\xbc\x9a\xef\xbe\xad\xde\xbe\xba\xfe\xca\x10\x32\x54\x76\x98\xba\xdc\xfe\x03\x00foo\x03\x00bar\x03\x00baz\x04\x00quux"
|
1142
1142
|
len = expect.length + 4
|
1143
1143
|
packlen = [len].pack("V")
|
1144
1144
|
expect = packlen + expect
|
1145
|
+
puts ""
|
1145
1146
|
assert(expect == bytes)
|
1146
1147
|
|
1147
1148
|
# decode expect
|
@@ -1207,7 +1208,7 @@ class MessageTest < Test::Unit::TestCase
|
|
1207
1208
|
de.muid = "quux"
|
1208
1209
|
tw = RStyx::Message::Twstat.new(0x12345678, de)
|
1209
1210
|
bytes = tw.to_bytes(0x1234)
|
1210
|
-
expect = "\x7e\x34\x12\x78\x56\x34\x12\x3c\x00\x34\x12\xab\x90\x78\x56\x01\xef\xbe\xad\xde\xee\xee\xff\xc0\xce\xfa\xed\xfe\xf0\xde\xbc\x9a\xef\xbe\xad\xde\xbe\xba\xfe\xca\x10\x32\x54\x76\x98\xba\xdc\xfe\x03\x00foo\x03\x00bar\x03\x00baz\x04\x00quux"
|
1211
|
+
expect = "\x7e\x34\x12\x78\x56\x34\x12\x3e\x00\x3c\x00\x34\x12\xab\x90\x78\x56\x01\xef\xbe\xad\xde\xee\xee\xff\xc0\xce\xfa\xed\xfe\xf0\xde\xbc\x9a\xef\xbe\xad\xde\xbe\xba\xfe\xca\x10\x32\x54\x76\x98\xba\xdc\xfe\x03\x00foo\x03\x00bar\x03\x00baz\x04\x00quux"
|
1211
1212
|
len = expect.length + 4
|
1212
1213
|
packlen = [len].pack("V")
|
1213
1214
|
expect = packlen + expect
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
|
-
!ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
rubygems_version: 0.8.11
|
3
3
|
specification_version: 1
|
4
4
|
name: rstyx
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.
|
7
|
-
date: 2005-
|
6
|
+
version: 0.2.0
|
7
|
+
date: 2005-11-01 00:00:00 +08:00
|
8
8
|
summary: RStyx is an implementation of the Styx/9P2000 distributed filesystem protocol for Ruby.
|
9
9
|
require_paths:
|
10
|
-
- lib
|
10
|
+
- lib
|
11
11
|
email: dido@imperium.ph
|
12
12
|
homepage: http://rubyforge.org/projects/rstyx
|
13
13
|
rubyforge_project:
|
@@ -18,35 +18,31 @@ bindir: bin
|
|
18
18
|
has_rdoc: true
|
19
19
|
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
20
|
requirements:
|
21
|
-
|
22
|
-
|
23
|
-
|
21
|
+
-
|
22
|
+
- ">"
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: 0.0.0
|
24
25
|
version:
|
25
26
|
platform: ruby
|
26
27
|
signing_key:
|
27
28
|
cert_chain:
|
28
29
|
authors:
|
29
|
-
- Rafael Sevilla
|
30
|
+
- Rafael Sevilla
|
30
31
|
files:
|
31
|
-
- lib/rstyx
|
32
|
-
- lib/rstyx.rb
|
33
|
-
- lib/rstyx/messages.rb
|
34
|
-
- lib/rstyx/client.rb
|
35
|
-
- lib/rstyx/version.rb
|
36
|
-
- lib/rstyx/errors.rb
|
37
|
-
- tests/
|
32
|
+
- lib/rstyx
|
33
|
+
- lib/rstyx.rb
|
34
|
+
- lib/rstyx/messages.rb
|
35
|
+
- lib/rstyx/client.rb
|
36
|
+
- lib/rstyx/version.rb
|
37
|
+
- lib/rstyx/errors.rb
|
38
|
+
- tests/tc_client.rb
|
39
|
+
- tests/tc_message.rb
|
38
40
|
test_files: []
|
39
|
-
|
40
41
|
rdoc_options:
|
41
|
-
- --title
|
42
|
-
- RStyx -- Styx/9P2000 for Ruby
|
42
|
+
- "--title"
|
43
|
+
- "RStyx -- Styx/9P2000 for Ruby"
|
43
44
|
extra_rdoc_files: []
|
44
|
-
|
45
45
|
executables: []
|
46
|
-
|
47
46
|
extensions: []
|
48
|
-
|
49
47
|
requirements: []
|
50
|
-
|
51
|
-
dependencies: []
|
52
|
-
|
48
|
+
dependencies: []
|