rstyx 0.1.0 → 0.2.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/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: []
|