rstyx 0.3.1 → 0.3.2
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/Manifest.txt +4 -1
- data/{README → README.txt} +1 -1
- data/Rakefile +9 -1
- data/examples/fileondisk.rb +22 -0
- data/examples/readstyxfile.rb +1 -1
- data/examples/testserver.rb +23 -0
- data/examples/writestyxfile.rb +49 -0
- data/lib/rstyx/messages.rb +259 -22
- data/lib/rstyx/server.rb +588 -56
- data/lib/rstyx/version.rb +2 -2
- data/tests/tc_styxservproto.rb +68 -44
- metadata +7 -3
data/lib/rstyx/server.rb
CHANGED
@@ -36,7 +36,7 @@
|
|
36
36
|
# Copyright:: Copyright (c) 2005-2007 Rafael R. Sevilla
|
37
37
|
# License:: GNU Lesser General Public License
|
38
38
|
#
|
39
|
-
# $Id: server.rb
|
39
|
+
# $Id: server.rb 247 2007-09-13 09:16:31Z dido $
|
40
40
|
#
|
41
41
|
require 'thread'
|
42
42
|
require 'monitor'
|
@@ -45,6 +45,7 @@ require 'rubygems'
|
|
45
45
|
require 'eventmachine'
|
46
46
|
require 'logger'
|
47
47
|
require 'socket'
|
48
|
+
require 'etc'
|
48
49
|
require 'rstyx/common'
|
49
50
|
require 'rstyx/messages'
|
50
51
|
require 'rstyx/errors'
|
@@ -89,8 +90,8 @@ module RStyx
|
|
89
90
|
##
|
90
91
|
# Handle version messages. This handles the version negotiation.
|
91
92
|
# At this point, the only version of the protocol supported is
|
92
|
-
# 9P2000: all other version strings result in
|
93
|
-
#
|
93
|
+
# 9P2000: all other version strings result in the server returning
|
94
|
+
# 'unknown' in its Rversion. A successful Tversion/Rversion
|
94
95
|
# negotiation results in the protocol_negotiated flag in the
|
95
96
|
# current session becoming true, and all other outstanding I/O
|
96
97
|
# on the session (e.g. opened fids and the like) all removed.
|
@@ -103,8 +104,13 @@ module RStyx
|
|
103
104
|
@cversion = msg.version
|
104
105
|
@cmsize = msg.msize
|
105
106
|
if @cversion != "9P2000"
|
106
|
-
# Unsupported protocol version
|
107
|
-
|
107
|
+
# Unsupported protocol version. As per Inferno's version(5):
|
108
|
+
#
|
109
|
+
# If the server does not understand the client's version
|
110
|
+
# string, it should respond with an Rversion message (not
|
111
|
+
# Rerror) with the _version_ string the 7 characters 'unknown'.
|
112
|
+
#
|
113
|
+
return(Message::Rversion.new(:version => "unknown", :msize => 0))
|
108
114
|
end
|
109
115
|
# Reset the session, which also causes the protocol negotiated
|
110
116
|
# flag in the session to be set to true.
|
@@ -310,7 +316,7 @@ module RStyx
|
|
310
316
|
# Create the file in the directory. Note that SDirectory#newfile
|
311
317
|
# has to do all of the permission checking and all that.
|
312
318
|
new_file = dir.newfile(msg.name, msg.perm)
|
313
|
-
dir << new_file
|
319
|
+
o dir << new_file
|
314
320
|
@session[msg.fid] = new_file
|
315
321
|
new_file.add_client(SFileClient.new(@session, msg.fid, msg.mode))
|
316
322
|
return(Message::Rcreate.new(:qid => new_file.qid,
|
@@ -589,10 +595,38 @@ module RStyx
|
|
589
595
|
# Session state of a Styx connection.
|
590
596
|
#
|
591
597
|
class Session < Monitor
|
592
|
-
|
593
|
-
|
598
|
+
##
|
599
|
+
# Maximum message size to be used by this session, based on
|
600
|
+
# the lesser of the server's and the client's msize.
|
601
|
+
#
|
602
|
+
attr_accessor :msize
|
603
|
+
##
|
604
|
+
# Authenticated flag
|
605
|
+
attr_accessor :auth
|
606
|
+
##
|
607
|
+
# List of active fids on this session
|
608
|
+
attr_accessor :fids
|
609
|
+
##
|
610
|
+
# List of active tags on this session
|
611
|
+
attr_accessor :tags
|
612
|
+
##
|
613
|
+
# Flag which is true if version negotiation has been performed
|
614
|
+
# on this session
|
615
|
+
attr_accessor :version_negotiated
|
616
|
+
##
|
617
|
+
# User this connection has authenticated against
|
618
|
+
#
|
619
|
+
attr_accessor :user
|
620
|
+
##
|
621
|
+
# Active iounit for this connection
|
622
|
+
#
|
594
623
|
attr_accessor :iounit
|
595
624
|
|
625
|
+
##
|
626
|
+
# Create a new session.
|
627
|
+
#
|
628
|
+
# _conn_:: The connection object (Server mixin)
|
629
|
+
#
|
596
630
|
def initialize(conn)
|
597
631
|
@conn = conn
|
598
632
|
@version_negotiated = false
|
@@ -603,10 +637,24 @@ module RStyx
|
|
603
637
|
@tags = []
|
604
638
|
end
|
605
639
|
|
640
|
+
##
|
641
|
+
# Return true if the session peer has completed version negotiation
|
642
|
+
#
|
606
643
|
def version_negotiated?
|
607
644
|
return(@version_negotiated)
|
608
645
|
end
|
609
646
|
|
647
|
+
##
|
648
|
+
# Reset the session, setting version negotiation flag and
|
649
|
+
# iounit.
|
650
|
+
#
|
651
|
+
#--
|
652
|
+
# FIXME: should clunk all outstanding fids and release all outstanding
|
653
|
+
# tags on this connection
|
654
|
+
#++
|
655
|
+
#
|
656
|
+
# _msize_:: the maximum message size for this connection
|
657
|
+
#
|
610
658
|
def reset_session(msize)
|
611
659
|
# XXX: clunk all outstanding fids and release all outstanding tags
|
612
660
|
@version_negotiated = true
|
@@ -617,6 +665,9 @@ module RStyx
|
|
617
665
|
# Associates a FID with a file. The FID passed must be checked before
|
618
666
|
# using this or the old FID will be forgotten.
|
619
667
|
#
|
668
|
+
# _fid_:: the fid to be associated
|
669
|
+
# _file_:: the SFile to associate with _fid_
|
670
|
+
#
|
620
671
|
def []=(fid, file)
|
621
672
|
@fids.delete(fid)
|
622
673
|
@fids[fid] = file
|
@@ -626,6 +677,8 @@ module RStyx
|
|
626
677
|
# Gets the file associated with the indexed FID. Raises a
|
627
678
|
# FidNotFoundException if the fid is not present.
|
628
679
|
#
|
680
|
+
# _fid_:: the fid to obtain the associated SFile instance of
|
681
|
+
#
|
629
682
|
def [](fid)
|
630
683
|
unless has_fid?(fid)
|
631
684
|
raise FidNotFoundException.new(fid)
|
@@ -633,10 +686,18 @@ module RStyx
|
|
633
686
|
return(@fids[fid])
|
634
687
|
end
|
635
688
|
|
689
|
+
##
|
690
|
+
# Returns true if _fid_ is associated with some file on this
|
691
|
+
# session.
|
692
|
+
#
|
636
693
|
def has_fid?(fid)
|
637
694
|
return(@fids.has_key?(fid))
|
638
695
|
end
|
639
696
|
|
697
|
+
##
|
698
|
+
# Clunk _fid_, i.e. make the server forget about the fid assignment
|
699
|
+
# for this connection.
|
700
|
+
#
|
640
701
|
def clunk(fid)
|
641
702
|
unless @fids.has_key?(fid)
|
642
703
|
raise FidNotFoundException.new(fid)
|
@@ -658,6 +719,9 @@ module RStyx
|
|
658
719
|
end
|
659
720
|
end
|
660
721
|
|
722
|
+
##
|
723
|
+
# Clunk all outstanding fids on this connection.
|
724
|
+
#
|
661
725
|
def clunk_all
|
662
726
|
@fids.each_key do |k|
|
663
727
|
begin
|
@@ -668,14 +732,17 @@ module RStyx
|
|
668
732
|
end
|
669
733
|
end
|
670
734
|
|
735
|
+
##
|
736
|
+
# Returns true if _tag_ is associated with some active message
|
737
|
+
#
|
671
738
|
def has_tag?(tag)
|
672
739
|
return(!@tags.index(tag).nil?)
|
673
740
|
end
|
674
741
|
|
675
742
|
##
|
676
|
-
# Adds the given
|
677
|
-
# see if it is already in use. Raises a TagInUseException if
|
678
|
-
#
|
743
|
+
# Adds the given _tag_ to the list of tags in use, first checking to
|
744
|
+
# see if it is already in use. Raises a TagInUseException if _tag_
|
745
|
+
# is already in use.
|
679
746
|
#
|
680
747
|
def add_tag(tag)
|
681
748
|
if has_tag?(tag)
|
@@ -687,13 +754,18 @@ module RStyx
|
|
687
754
|
alias << add_tag
|
688
755
|
|
689
756
|
##
|
690
|
-
# Called when a message is replied to, releasing
|
757
|
+
# Called when a message is replied to, releasing _tag_ so it can
|
758
|
+
# be used again.
|
759
|
+
#
|
691
760
|
def release_tag(tag)
|
692
761
|
@tags.delete(tag)
|
693
762
|
end
|
694
763
|
|
695
764
|
alias flush_tag release_tag
|
696
765
|
|
766
|
+
##
|
767
|
+
# Flush all outstanding tags on this session.
|
768
|
+
#
|
697
769
|
def flush_all
|
698
770
|
@tags.each do |f|
|
699
771
|
flush_tag(t)
|
@@ -702,11 +774,13 @@ module RStyx
|
|
702
774
|
|
703
775
|
##
|
704
776
|
# Check the permissions for a given mode
|
705
|
-
# +sf+ the file to check against
|
706
|
-
# +mode+ the mode to check (OEXEC, OWRITE, or OREAD)
|
707
777
|
#
|
708
|
-
|
778
|
+
#--
|
779
|
+
# FIXME: the permissions are only for anonymous access at the
|
709
780
|
# moment, so only the world permissions are ever checked.
|
781
|
+
#++
|
782
|
+
# _sf_:: the file to check against
|
783
|
+
# _mode_:: the mode to check (OEXEC, OWRITE, or OREAD)
|
710
784
|
#
|
711
785
|
def permission?(sf, mode)
|
712
786
|
if mode < 0 || mode > 2
|
@@ -726,15 +800,19 @@ module RStyx
|
|
726
800
|
end
|
727
801
|
|
728
802
|
##
|
729
|
-
# Check for executable permission for the
|
803
|
+
# Check for executable permission for the SFile _sf_.
|
730
804
|
#
|
731
805
|
def execute?(sf)
|
806
|
+
return(true)
|
732
807
|
end
|
733
808
|
|
734
809
|
##
|
735
810
|
# Checks that the given file can be opened with the given mode.
|
736
811
|
# Raises a StyxException if this is not possible.
|
737
812
|
#
|
813
|
+
# _sf_:: the SFile to be opened
|
814
|
+
# _mode_:: the open mode
|
815
|
+
#
|
738
816
|
def confirm_open(sf, mode)
|
739
817
|
if sf.exclusive? && sf.num_clients != 0
|
740
818
|
raise StyxException.new("can't open locked file")
|
@@ -745,7 +823,24 @@ module RStyx
|
|
745
823
|
|
746
824
|
end # class Session
|
747
825
|
|
826
|
+
##
|
827
|
+
# Base server class. This does nothing really useful, instantiate
|
828
|
+
# subclasses such as TCPServer instead.
|
829
|
+
#
|
748
830
|
class Server
|
831
|
+
##
|
832
|
+
# Create a new server. The _config_ hash contains the server
|
833
|
+
# configuration. The configuration options recognized by all
|
834
|
+
# Styx server subclasses are:
|
835
|
+
#
|
836
|
+
# _:root_:: The root directory of the filesystem you want to
|
837
|
+
# serve (typically an SDirectory instance)
|
838
|
+
# _:log_:: A Logger object where server-generated log messages
|
839
|
+
# are stored.
|
840
|
+
# _:debug_:: Debug level, which is assigned to the logger's level
|
841
|
+
# Set this to Logger::DEBUG if you want full debugging
|
842
|
+
# messages to appear.
|
843
|
+
#
|
749
844
|
def initialize(config)
|
750
845
|
@root = config[:root]
|
751
846
|
@log = config[:log] || Logger.new(STDERR)
|
@@ -754,6 +849,9 @@ module RStyx
|
|
754
849
|
|
755
850
|
protected
|
756
851
|
|
852
|
+
##
|
853
|
+
# Start a new server. This is overriden by subclasses.
|
854
|
+
#
|
757
855
|
def start_server
|
758
856
|
end
|
759
857
|
|
@@ -762,6 +860,7 @@ module RStyx
|
|
762
860
|
##
|
763
861
|
# Start the Styx server, returning the thread of the
|
764
862
|
# running Styx server instance.
|
863
|
+
#
|
765
864
|
def run
|
766
865
|
t = Thread.new do
|
767
866
|
@log.info("starting")
|
@@ -773,6 +872,14 @@ module RStyx
|
|
773
872
|
end # class Server
|
774
873
|
|
775
874
|
class TCPServer < Server
|
875
|
+
##
|
876
|
+
# Create a new TCP-based server. In addition to the options described
|
877
|
+
# in the Server superclass, the following further options are also
|
878
|
+
# available:
|
879
|
+
#
|
880
|
+
# _:bindaddr_:: The address that the Styx server should listen on
|
881
|
+
# _:port_:: The port that the Styx server should listen on
|
882
|
+
#
|
776
883
|
def initialize(config)
|
777
884
|
@bindaddr = config[:bindaddr]
|
778
885
|
@port = config[:port]
|
@@ -780,6 +887,10 @@ module RStyx
|
|
780
887
|
end
|
781
888
|
|
782
889
|
protected
|
890
|
+
|
891
|
+
##
|
892
|
+
# Start a TCP-based server using EventMachine.
|
893
|
+
#
|
783
894
|
def start_server
|
784
895
|
EventMachine::run do
|
785
896
|
@log.info("TCP server on #{@bindaddr}:#{@port}")
|
@@ -798,16 +909,31 @@ module RStyx
|
|
798
909
|
# a client opens a file.
|
799
910
|
#
|
800
911
|
class SFileClient
|
801
|
-
|
802
|
-
|
912
|
+
##
|
913
|
+
# The session for which this SFileClient was created
|
914
|
+
attr_reader :session
|
915
|
+
##
|
916
|
+
# The fid which the client used to open the file in question
|
917
|
+
attr_reader :fid
|
918
|
+
##
|
919
|
+
# The mode under which the client opened the file in question
|
920
|
+
attr_reader :mode
|
921
|
+
##
|
922
|
+
# When a client reads from or writes to file, this records the
|
923
|
+
# new offset
|
924
|
+
attr_accessor :offset
|
925
|
+
##
|
926
|
+
# Used when reading a directory: stores the index of the next
|
927
|
+
# child of an SFile to include in an RreadMessage.
|
928
|
+
attr_accessor :next_file_to_read
|
803
929
|
|
804
930
|
##
|
805
931
|
# Create a new SFileClient.
|
806
932
|
#
|
807
|
-
#
|
808
|
-
#
|
933
|
+
# _session_:: The session object associated with the client.
|
934
|
+
# _fid_:: The client's handle to the file. Note that clients may
|
809
935
|
# use many fids opened representing the same file.
|
810
|
-
#
|
936
|
+
# _mode_:: The mode field as received from the client's Topen
|
811
937
|
# message (including the OTRUNC and ORCLOSE bits)
|
812
938
|
#
|
813
939
|
def initialize(session, fid, mode)
|
@@ -816,18 +942,22 @@ module RStyx
|
|
816
942
|
@truncate = ((mode & OTRUNC) == OTRUNC)
|
817
943
|
@orclose = ((mode & ORCLOSE) == ORCLOSE)
|
818
944
|
@mode = mode & 0x03 # mask off all but the last two bits
|
819
|
-
# When a client reads from or writes to file, this records the
|
820
|
-
# new offset
|
821
945
|
@offset = 0
|
822
|
-
# Used when reading a directory: stores the index of the next
|
823
|
-
# child of an SFile to include in an RreadMessage.
|
824
946
|
@next_file_to_read = 0
|
825
947
|
end
|
826
948
|
|
949
|
+
##
|
950
|
+
# Returns true if the Styx file was opened by the client in the
|
951
|
+
# OTRUNC mode.
|
952
|
+
#
|
827
953
|
def truncate?
|
828
954
|
return(@truncate)
|
829
955
|
end
|
830
956
|
|
957
|
+
##
|
958
|
+
# Returns true if the Styx file was opened by the client in the
|
959
|
+
# ORCLOSE mode (i.e. the client wants the file deleted on clunk).
|
960
|
+
#
|
831
961
|
def orclose?
|
832
962
|
return(@orclose)
|
833
963
|
end
|
@@ -836,7 +966,7 @@ module RStyx
|
|
836
966
|
|
837
967
|
##
|
838
968
|
# Check to see if the client can read the file (i.e. the client
|
839
|
-
# opened it with read access mode)
|
969
|
+
# opened it with read access mode).
|
840
970
|
#
|
841
971
|
def readable?
|
842
972
|
return(mode == OREAD || mode == ORDWR)
|
@@ -864,23 +994,51 @@ module RStyx
|
|
864
994
|
# underlying operating system cannot be represented.
|
865
995
|
#
|
866
996
|
class SFile < Monitor
|
867
|
-
|
868
|
-
|
869
|
-
|
997
|
+
##
|
998
|
+
# File name; must be / if the file is the root directory of the server
|
999
|
+
#
|
1000
|
+
attr_reader :name
|
1001
|
+
##
|
1002
|
+
# Owner name
|
1003
|
+
attr_reader :uid
|
1004
|
+
##
|
1005
|
+
# Group name
|
1006
|
+
#
|
1007
|
+
attr_reader :gid
|
1008
|
+
##
|
1009
|
+
# Name of the user who last modified the file
|
1010
|
+
#
|
1011
|
+
attr_reader :muid
|
1012
|
+
##
|
1013
|
+
# Last modification time
|
1014
|
+
attr_reader :mtime
|
1015
|
+
##
|
1016
|
+
# Permissions and flags
|
1017
|
+
attr_accessor :permissions
|
1018
|
+
##
|
1019
|
+
# Last access time
|
1020
|
+
attr_accessor :atime
|
1021
|
+
##
|
1022
|
+
# Parent directory which contains this file
|
1023
|
+
#
|
1024
|
+
attr_accessor :parent
|
1025
|
+
##
|
1026
|
+
# Version number for the given path
|
1027
|
+
attr_accessor :version
|
870
1028
|
|
871
1029
|
##
|
872
|
-
# Create a new file object with
|
873
|
-
#
|
1030
|
+
# Create a new file object with name _name_. This accepts a hash
|
1031
|
+
# _argv_ for the file's other parameters with the following keys:
|
874
1032
|
#
|
875
|
-
#
|
876
|
-
#
|
877
|
-
#
|
878
|
-
#
|
879
|
-
# client at a time may open.
|
880
|
-
#
|
881
|
-
# gets the value from
|
882
|
-
#
|
883
|
-
# gets the value from
|
1033
|
+
# _:permissions_:: The permissions of the file (e.g. 0755 in octal).
|
1034
|
+
# the default is 0666.
|
1035
|
+
# _:apponly_:: true if the file is append only. Default is false.
|
1036
|
+
# _:excl_:: true if the file is for exclusive use, i.e. only one
|
1037
|
+
# client at a time may open. Default is false.
|
1038
|
+
# _:user_:: the username of the owner of the file. If not specified
|
1039
|
+
# gets the value from the environment variable USER.
|
1040
|
+
# _:group_:: the group name of the owner of the file. If not specified
|
1041
|
+
# gets the value from the environment variable GROUP.
|
884
1042
|
#
|
885
1043
|
#
|
886
1044
|
def initialize(name, argv={ :permissions => 0666, :apponly => false,
|
@@ -1017,7 +1175,7 @@ module RStyx
|
|
1017
1175
|
# of the file to be changed. This is called when the server receives
|
1018
1176
|
# a Twstat message. This default implementation does nothing.
|
1019
1177
|
#
|
1020
|
-
#
|
1178
|
+
# _newmode_: the new mode of the file (permissions plus any other flags
|
1021
1179
|
# such as DMDIR, etc.)
|
1022
1180
|
#
|
1023
1181
|
def can_setmode?(newmode)
|
@@ -1036,12 +1194,18 @@ module RStyx
|
|
1036
1194
|
return(newmode)
|
1037
1195
|
end
|
1038
1196
|
|
1197
|
+
##
|
1198
|
+
# Returns the Qid of this file.
|
1199
|
+
#
|
1039
1200
|
def qid
|
1040
1201
|
t = filetype() >> 24 & 0xff
|
1041
1202
|
q = Message::Qid.new(t, @version, self.uuid)
|
1042
1203
|
return(q)
|
1043
1204
|
end
|
1044
1205
|
|
1206
|
+
##
|
1207
|
+
# Returns a Stat object for this file.
|
1208
|
+
#
|
1045
1209
|
def stat
|
1046
1210
|
s = Message::Stat.new
|
1047
1211
|
s.dtype = s.dev = 0
|
@@ -1076,14 +1240,32 @@ module RStyx
|
|
1076
1240
|
def length=(newlength)
|
1077
1241
|
end
|
1078
1242
|
|
1243
|
+
##
|
1244
|
+
# Check to see if the modification time of this file can be changed
|
1245
|
+
# to the given value. If this does not throw an exception then
|
1246
|
+
# set_mtime should always succeed.
|
1079
1247
|
def can_setmtime?(nmtime)
|
1248
|
+
return(true)
|
1080
1249
|
end
|
1081
1250
|
|
1251
|
+
##
|
1252
|
+
# Sets the mtime of the file. The usual disclaimers about permissions
|
1253
|
+
# and SFile#can_setmtime? apply. Default implementation will simply
|
1254
|
+
# set the modification time to _nmtime_ and the muid to _uid_.
|
1255
|
+
#
|
1082
1256
|
def set_mtime(nmtime, uid)
|
1083
1257
|
@mtime = nmtime
|
1084
1258
|
@muid = uid
|
1085
1259
|
end
|
1086
1260
|
|
1261
|
+
##
|
1262
|
+
# Rename the file to _newname_. This will raise a StyxException if
|
1263
|
+
#
|
1264
|
+
# 1. An attempt is made to rename a file representing the root
|
1265
|
+
# directory.
|
1266
|
+
# 2. An attempt is made to rename a file to a name of some other
|
1267
|
+
# file already present in the same directory.
|
1268
|
+
#
|
1087
1269
|
def rename(newname)
|
1088
1270
|
if @parent == nil
|
1089
1271
|
raise StyxException.new("Cannot change the name of the root directory")
|
@@ -1094,6 +1276,7 @@ module RStyx
|
|
1094
1276
|
@name = newname
|
1095
1277
|
end
|
1096
1278
|
|
1279
|
+
##
|
1097
1280
|
# Gets the unique numeric ID for the path of this file (generated from
|
1098
1281
|
# the low-order bytes of the creation time and the hashcode of the full
|
1099
1282
|
# path). If the file is deleted and re-created the unique ID will
|
@@ -1113,6 +1296,11 @@ module RStyx
|
|
1113
1296
|
# should override this to provide the desired behavior when the
|
1114
1297
|
# file is read.
|
1115
1298
|
#
|
1299
|
+
# _client_:: the SFileClient object representing the client reading
|
1300
|
+
# from this file.
|
1301
|
+
# _offset_:: the offset the client wants to read from
|
1302
|
+
# _count_:: the number of bytes that the client wishes to read
|
1303
|
+
#
|
1116
1304
|
def read(client, offset, count)
|
1117
1305
|
raise StyxException.new("Cannot read from this file")
|
1118
1306
|
end
|
@@ -1122,22 +1310,39 @@ module RStyx
|
|
1122
1310
|
# Writes data to this file. This method should be overriden by
|
1123
1311
|
# subclasses to provide the desired behavior when the file is
|
1124
1312
|
# written to. It should return the number of bytes actually
|
1125
|
-
#
|
1313
|
+
# written.
|
1314
|
+
#
|
1315
|
+
# _client_:: the SFileClient object representing the client writing
|
1316
|
+
# to this file
|
1317
|
+
# _offset_:: the offset the client wants to write to
|
1318
|
+
# _data_:: the data that the client wishes to write
|
1319
|
+
# _truncate_:: true or false depending on whether the file is to be
|
1320
|
+
# truncated.
|
1126
1321
|
#
|
1127
1322
|
def write(client, offset, data, truncate)
|
1128
1323
|
raise StyxException.new("Cannot write to this file")
|
1129
1324
|
end
|
1130
1325
|
|
1326
|
+
##
|
1327
|
+
# Remove the file from the Styx server. This will simply remove
|
1328
|
+
# the file from the parent directory.
|
1131
1329
|
#
|
1132
|
-
# Remove the file from the Styx server
|
1133
1330
|
def remove
|
1134
1331
|
self.delete
|
1135
1332
|
self.parent.remove_child(self)
|
1136
1333
|
end
|
1137
1334
|
|
1335
|
+
##
|
1336
|
+
# Any pre-deletion actions must be performed in this method.
|
1337
|
+
#
|
1138
1338
|
def delete
|
1139
1339
|
end
|
1140
1340
|
|
1341
|
+
##
|
1342
|
+
# Add a client to the list of clients reading this file.
|
1343
|
+
#
|
1344
|
+
# _cl_:: an SFileClient instance representing the client reading
|
1345
|
+
# the file
|
1141
1346
|
def add_client(cl)
|
1142
1347
|
@clients.synchronize { @clients << cl }
|
1143
1348
|
self.client_connected(cl)
|
@@ -1149,6 +1354,11 @@ module RStyx
|
|
1149
1354
|
##
|
1150
1355
|
# Get the client connection to this file
|
1151
1356
|
#
|
1357
|
+
# _sess_:: the client session in question
|
1358
|
+
# _fid_:: the fid that the client is using to access the file.
|
1359
|
+
# returns:: the SFileClient instance representing that file access,
|
1360
|
+
# or nil if there is no such client connection.
|
1361
|
+
#
|
1152
1362
|
def client(sess, fid)
|
1153
1363
|
@clients.synchronize do
|
1154
1364
|
@clients.each do |cl|
|
@@ -1160,13 +1370,20 @@ module RStyx
|
|
1160
1370
|
return(nil)
|
1161
1371
|
end
|
1162
1372
|
|
1373
|
+
##
|
1374
|
+
# Return the number of clients accessing this file.
|
1375
|
+
#
|
1163
1376
|
def num_clients
|
1164
1377
|
@clients.synchronize do
|
1165
1378
|
remove_dead_clients
|
1166
1379
|
return(@clients.length)
|
1167
1380
|
end
|
1168
1381
|
end
|
1169
|
-
|
1382
|
+
|
1383
|
+
##
|
1384
|
+
# Remove clients which are no longer really using the file.
|
1385
|
+
# If a client session is either gone or the session is no longer
|
1386
|
+
# connected, it removes the client from the list.
|
1170
1387
|
def remove_dead_clients
|
1171
1388
|
@clients.synchronize do
|
1172
1389
|
@clients.each do |clnt|
|
@@ -1177,6 +1394,9 @@ module RStyx
|
|
1177
1394
|
end
|
1178
1395
|
end
|
1179
1396
|
|
1397
|
+
##
|
1398
|
+
# Remove the client _cl_ from the list of clients accessing the file.
|
1399
|
+
#
|
1180
1400
|
def remove_client(cl)
|
1181
1401
|
unless cl.nil?
|
1182
1402
|
@clients.delete(cl)
|
@@ -1184,13 +1404,30 @@ module RStyx
|
|
1184
1404
|
end
|
1185
1405
|
end
|
1186
1406
|
|
1407
|
+
##
|
1408
|
+
# Add any custom behavior for the file that has to happen whenever
|
1409
|
+
# a client disconnects from the file here.
|
1410
|
+
#
|
1187
1411
|
def client_disconnected(cl)
|
1188
1412
|
end
|
1189
1413
|
|
1414
|
+
##
|
1415
|
+
# Add a change listener to the list of change listeners of this
|
1416
|
+
# file. The change listener will execute whenever some modification
|
1417
|
+
# is made to the file, and is passed the SFile instance to which it
|
1418
|
+
# is attached as a parameter.
|
1419
|
+
#
|
1420
|
+
# _block_:: the change listener block
|
1421
|
+
#
|
1190
1422
|
def add_changelistener(&block)
|
1191
1423
|
@changelisteners << block
|
1192
1424
|
end
|
1193
1425
|
|
1426
|
+
##
|
1427
|
+
# Method executed whenever the contents of the file change. This
|
1428
|
+
# increments the file's version on the server, and executes any
|
1429
|
+
# change listeners active on the file.
|
1430
|
+
#
|
1194
1431
|
def contents_changed
|
1195
1432
|
version_incr
|
1196
1433
|
@changelisteners.each do |listener|
|
@@ -1198,29 +1435,72 @@ module RStyx
|
|
1198
1435
|
end
|
1199
1436
|
end
|
1200
1437
|
|
1438
|
+
##
|
1439
|
+
# Increment the file's version. This wraps after the version goes
|
1440
|
+
# above 2^64.
|
1441
|
+
#
|
1201
1442
|
def version_incr
|
1202
1443
|
@version = ((@version + 1) & 0xffffffffffffffff)
|
1203
1444
|
end
|
1204
1445
|
|
1446
|
+
##
|
1447
|
+
# Return a Message::Rwrite for a successful write of _count_ bytes
|
1448
|
+
# from _session_. A write method for a subclass should use this
|
1449
|
+
# method (which updates mtime, atime, and calls contents_changed
|
1450
|
+
# callbacks) instead of manually returning a Message::Rwrite
|
1451
|
+
#
|
1205
1452
|
def reply_write(count, session)
|
1206
|
-
|
1453
|
+
@atime = Time.now
|
1454
|
+
self.set_mtime(@atime, session.user)
|
1207
1455
|
self.contents_changed
|
1208
1456
|
return(Message::Rwrite.new(:count => count))
|
1209
1457
|
end
|
1210
1458
|
|
1459
|
+
##
|
1460
|
+
# Return a Message::Rread for a successful read of _data_. This
|
1461
|
+
# updates access time and should be used instead of manually
|
1462
|
+
# returning a Message::Rread.
|
1463
|
+
#
|
1464
|
+
def reply_read(data)
|
1465
|
+
@atime = Time.now
|
1466
|
+
return(Message::Rread.new(:data => data))
|
1467
|
+
end
|
1468
|
+
|
1469
|
+
##
|
1470
|
+
# Refreshes this file (if it represents another entity, such as a
|
1471
|
+
# file on disk, this method is used to make sure that the file
|
1472
|
+
# metadata (length, access time, etc.) are up to date. This default
|
1473
|
+
# implementation does nothing; subclasses must override this to
|
1474
|
+
# provide the correct functionality.
|
1475
|
+
#
|
1211
1476
|
def refresh
|
1212
1477
|
end
|
1213
1478
|
end # class SFile
|
1214
1479
|
|
1480
|
+
##
|
1481
|
+
# Class representing a directory on the Styx server.
|
1482
|
+
#
|
1215
1483
|
class SDirectory < SFile
|
1216
|
-
|
1217
|
-
|
1484
|
+
##
|
1485
|
+
# Create a new directory with name _name_. Permissions are obtained
|
1486
|
+
# from the _argv_ hash as with SFile. The default permissions are
|
1487
|
+
# 0777 though. In addition to the usual file arguments in _argv_,
|
1488
|
+
# SDirectory instances also recognize a _:filemaker_ key which
|
1489
|
+
# specifies a block that may be called whenever a file is created.
|
1490
|
+
# The block receives as parameters the SDirectory instance, the
|
1491
|
+
# name of the file to create, and the permissions of the file. It
|
1492
|
+
# should return an SFile subclass instance, which becomes the new
|
1493
|
+
# file on success, or raise a StyxException if there is some problem.
|
1494
|
+
#
|
1495
|
+
def initialize(name, argv={ :permissions => 0777, :uid => ENV["USER"],
|
1496
|
+
:gid => ENV["GROUP"], :filemaker => nil })
|
1218
1497
|
# directories cannot be append-only, exclusive, or auth files
|
1219
1498
|
argv.merge({:apponly => false, :excl => false})
|
1220
1499
|
super(name, argv)
|
1221
1500
|
@directory = true
|
1222
1501
|
@children = []
|
1223
1502
|
@children.extend(MonitorMixin)
|
1503
|
+
@filemaker = argv[:filemaker]
|
1224
1504
|
end
|
1225
1505
|
|
1226
1506
|
def child_exists?(name)
|
@@ -1235,8 +1515,8 @@ module RStyx
|
|
1235
1515
|
end
|
1236
1516
|
|
1237
1517
|
##
|
1238
|
-
# Add
|
1239
|
-
# already exists, throws a FileExists exception.
|
1518
|
+
# Add an SFile _child_ to this directory. If a file with the
|
1519
|
+
# same name already exists, throws a FileExists exception.
|
1240
1520
|
#
|
1241
1521
|
def <<(child)
|
1242
1522
|
@children.synchronize do
|
@@ -1250,12 +1530,16 @@ module RStyx
|
|
1250
1530
|
end
|
1251
1531
|
|
1252
1532
|
##
|
1253
|
-
# Get the child with the name
|
1533
|
+
# Get the child with the name _name_, or nil if no such file is
|
1534
|
+
# present in this directory.
|
1254
1535
|
#
|
1255
1536
|
def [](name)
|
1256
1537
|
if name == "."
|
1257
1538
|
return(self)
|
1258
1539
|
end
|
1540
|
+
if name == ".."
|
1541
|
+
return(self.parent)
|
1542
|
+
end
|
1259
1543
|
@children.synchronize do
|
1260
1544
|
@children.each do |c|
|
1261
1545
|
if c.name == name
|
@@ -1274,7 +1558,21 @@ module RStyx
|
|
1274
1558
|
end
|
1275
1559
|
|
1276
1560
|
##
|
1277
|
-
# Read the contents of the directory
|
1561
|
+
# Read the contents of the directory.
|
1562
|
+
#
|
1563
|
+
# _client_:: the SFileClient object representing the client reading
|
1564
|
+
# from this directory.
|
1565
|
+
# _offset_:: the offset the client wants to read from. Nonzero
|
1566
|
+
# offsets are only valid if the client has read from
|
1567
|
+
# the directory before, and may only be the values
|
1568
|
+
# obtained from a previous read.
|
1569
|
+
# _count_:: the number of bytes that the client wishes to read.
|
1570
|
+
# The read will always return the nearest integral
|
1571
|
+
# number of directory entries whose length is less than
|
1572
|
+
# the count (i.e. if five entries take 300 bytes and
|
1573
|
+
# the sixth entry takes 40 bytes, and _count_ was
|
1574
|
+
# set to 320, only five entries and 300 bytes will be
|
1575
|
+
# returned).
|
1278
1576
|
#
|
1279
1577
|
def read(client, offset, count)
|
1280
1578
|
# Check that the offset is valid; zero offsets are always valid,
|
@@ -1300,13 +1598,30 @@ module RStyx
|
|
1300
1598
|
end
|
1301
1599
|
client.next_file_to_read = nextfile
|
1302
1600
|
client.offset += str.length
|
1303
|
-
return(
|
1601
|
+
return(reply_read(str))
|
1602
|
+
end
|
1304
1603
|
|
1604
|
+
##
|
1605
|
+
# Create a new file with name _name_ and permissions _perm_ in this
|
1606
|
+
# directory.
|
1607
|
+
#--
|
1608
|
+
# FIXME: make this method actually DO something!
|
1609
|
+
#++
|
1610
|
+
#
|
1611
|
+
def newfile(name, perm)
|
1612
|
+
raise StyxException.new("cannot create files in this directory")
|
1305
1613
|
end
|
1306
1614
|
|
1307
1615
|
end # class SDirectory
|
1308
1616
|
|
1617
|
+
##
|
1618
|
+
# An SFile whose underlying data are stored as a String in memory.
|
1619
|
+
# This string may grow to arbitrary size.
|
1620
|
+
#
|
1309
1621
|
class InMemoryFile < SFile
|
1622
|
+
##
|
1623
|
+
# The contents of the file, as a string.
|
1624
|
+
#
|
1310
1625
|
attr_accessor :contents
|
1311
1626
|
|
1312
1627
|
def initialize(name, argv={ :permissions => 0666, :apponly => false,
|
@@ -1316,12 +1631,31 @@ module RStyx
|
|
1316
1631
|
@contentslock = Mutex.new
|
1317
1632
|
end
|
1318
1633
|
|
1634
|
+
##
|
1635
|
+
# Read data from the file.
|
1636
|
+
#
|
1637
|
+
# _client_:: the SFileClient object representing the client reading
|
1638
|
+
# from this file.
|
1639
|
+
# _offset_:: the offset the client wants to read from
|
1640
|
+
# _count_:: the number of bytes that the client wishes to read
|
1641
|
+
#
|
1319
1642
|
def read(client, offset, count)
|
1320
1643
|
data = @contents[offset..(offset+count)]
|
1321
1644
|
data ||= ""
|
1322
|
-
return(
|
1645
|
+
return(reply_read(data))
|
1323
1646
|
end
|
1324
1647
|
|
1648
|
+
##
|
1649
|
+
# Writes data to this file. Raises a StyxException if the
|
1650
|
+
# offset is past the end of the file.
|
1651
|
+
#
|
1652
|
+
# _client_:: the SFileClient object representing the client writing
|
1653
|
+
# to this file
|
1654
|
+
# _offset_:: the offset the client wants to write to
|
1655
|
+
# _data_:: the data that the client wishes to write
|
1656
|
+
# _truncate_:: true or false depending on whether the file is to be
|
1657
|
+
# truncated.
|
1658
|
+
#
|
1325
1659
|
def write(client, offset, data, truncate)
|
1326
1660
|
@contentslock.synchronize do
|
1327
1661
|
# First write to the file
|
@@ -1330,12 +1664,11 @@ module RStyx
|
|
1330
1664
|
end
|
1331
1665
|
|
1332
1666
|
if offset > @contents.length
|
1333
|
-
raise StyxException.new("attempt to write past the end of the file")
|
1334
|
-
end
|
1667
|
+
raise StyxException.new("attempt to write past the end of the file") end
|
1335
1668
|
|
1336
1669
|
@contents[offset..(offset + data.length)] = data
|
1337
1670
|
if (truncate)
|
1338
|
-
@contents = @contents[0..(offset+data.length)]
|
1671
|
+
@contents = @contents[0..(offset+data.length)] = data
|
1339
1672
|
end
|
1340
1673
|
return(reply_write(data.length, client.session))
|
1341
1674
|
end
|
@@ -1343,6 +1676,205 @@ module RStyx
|
|
1343
1676
|
|
1344
1677
|
end
|
1345
1678
|
|
1679
|
+
class FileOnDisk < SFile
|
1680
|
+
##
|
1681
|
+
# Create a new FileOnDisk whose path on the local filesystem is _path_,
|
1682
|
+
# whose name as it appears on the server's namespace is _name_, whose
|
1683
|
+
# base permissions (which are ANDed with the file's real permissions
|
1684
|
+
# mask on the underlying filesystem) are _perm_. The file must
|
1685
|
+
# already exist on the local filesystem. If _name_ is not specified,
|
1686
|
+
# it defaults to the basename of _path_. If _perm_ is not specified,
|
1687
|
+
# it defaults to 0666. If the path specified is actually a directory,
|
1688
|
+
# returns a DirectoryOnDisk instance instead.
|
1689
|
+
#
|
1690
|
+
#--
|
1691
|
+
# FIXME: should create a DirectoryOnDisk instance instead if _path_
|
1692
|
+
# actually represents a directory.
|
1693
|
+
#++
|
1694
|
+
#
|
1695
|
+
def self.new(path, name=nil, perm=nil)
|
1696
|
+
unless File.exists?(path)
|
1697
|
+
raise StyxException.new("file #{path} does not exist on local filesystem")
|
1698
|
+
end
|
1699
|
+
|
1700
|
+
if name.nil?
|
1701
|
+
name = File.basename(path)
|
1702
|
+
end
|
1703
|
+
|
1704
|
+
if File.directory?(path)
|
1705
|
+
perm ||= 0777
|
1706
|
+
return(DirectoryOnDisk.new(path, name, perm))
|
1707
|
+
end
|
1708
|
+
|
1709
|
+
perm ||= 0666
|
1710
|
+
obj = allocate
|
1711
|
+
obj.send(:initialize, path, name, perm)
|
1712
|
+
return(obj)
|
1713
|
+
end
|
1714
|
+
|
1715
|
+
##
|
1716
|
+
# Hidden initialize method.
|
1717
|
+
# See self.new for the real thing.
|
1718
|
+
#
|
1719
|
+
def initialize(path, name, perm)
|
1720
|
+
@name = name
|
1721
|
+
@path = File.expand_path(path)
|
1722
|
+
|
1723
|
+
s = File.stat(@path)
|
1724
|
+
pwent = Etc.getpwuid(s.uid)
|
1725
|
+
grent = Etc.getgrgid(s.gid)
|
1726
|
+
argv = { :permissions => perm & s.mode, :apponly => false,
|
1727
|
+
:excl => false, :uid => pwent.name, :gid => grent.name }
|
1728
|
+
super(@name, argv)
|
1729
|
+
end
|
1730
|
+
|
1731
|
+
##
|
1732
|
+
# Read data from the file.
|
1733
|
+
#
|
1734
|
+
# _client_:: the SFileClient object representing the client reading
|
1735
|
+
# from this file.
|
1736
|
+
# _offset_:: the offset the client wants to read from
|
1737
|
+
# _count_:: the number of bytes that the client wishes to read
|
1738
|
+
#
|
1739
|
+
def read(client, offset, count)
|
1740
|
+
begin
|
1741
|
+
File.open(@path, "r") do |fp|
|
1742
|
+
fp.seek(offset)
|
1743
|
+
data = fp.read(count)
|
1744
|
+
if data.nil?
|
1745
|
+
data = ""
|
1746
|
+
end
|
1747
|
+
return(reply_read(data))
|
1748
|
+
end
|
1749
|
+
rescue Exception => e
|
1750
|
+
raise StyxException.new("An error of class #{e.class} occurred when trying to read from #{@path}: #{e.message}")
|
1751
|
+
end
|
1752
|
+
|
1753
|
+
end
|
1754
|
+
|
1755
|
+
##
|
1756
|
+
# Writes data to the file.
|
1757
|
+
#
|
1758
|
+
# _client_:: the SFileClient object representing the client writing
|
1759
|
+
# to this file
|
1760
|
+
# _offset_:: the offset the client wants to write to
|
1761
|
+
# _data_:: the data that the client wishes to write
|
1762
|
+
# _truncate_:: true or false depending on whether the file is to be
|
1763
|
+
# truncated.
|
1764
|
+
#
|
1765
|
+
def write(client, offset, data, truncate)
|
1766
|
+
unless File.exists?(@path)
|
1767
|
+
# The underlying file was removed out from under us!
|
1768
|
+
self.remove
|
1769
|
+
raise StyxException.new("The file #{@path} was removed")
|
1770
|
+
end
|
1771
|
+
|
1772
|
+
begin
|
1773
|
+
File.open(@path, "r+") do |fp|
|
1774
|
+
fp.seek(offset)
|
1775
|
+
count = fp.write(data)
|
1776
|
+
if truncate
|
1777
|
+
fp.truncate(offset + data.length)
|
1778
|
+
end
|
1779
|
+
reply_write(count, client.session)
|
1780
|
+
end
|
1781
|
+
rescue Exception => e
|
1782
|
+
raise StyxException.new("An error of class #{e.class} occurred when trying to write to #{@path}: #{e.message}")
|
1783
|
+
end
|
1784
|
+
end
|
1785
|
+
|
1786
|
+
##
|
1787
|
+
# Refreshes the file's stat information based on a real stat call
|
1788
|
+
def refresh
|
1789
|
+
s = File.stat(@path)
|
1790
|
+
@mtime = s.mtime
|
1791
|
+
@atime = s.atime
|
1792
|
+
end
|
1793
|
+
|
1794
|
+
##
|
1795
|
+
# Deletes the underlying file from the disk.
|
1796
|
+
#
|
1797
|
+
def delete
|
1798
|
+
if File.exists?(@path)
|
1799
|
+
File.delete(@path)
|
1800
|
+
end
|
1801
|
+
end
|
1802
|
+
|
1803
|
+
end
|
1804
|
+
|
1805
|
+
##
|
1806
|
+
# Class representing a directory on the host filesystem. While there's
|
1807
|
+
# no real problem with using this class directly, it's probably better
|
1808
|
+
# to use FileOnDisk instead (it will return a DirectoryOnDisk instance
|
1809
|
+
# when passed a directory.
|
1810
|
+
#
|
1811
|
+
class DirectoryOnDisk < SDirectory
|
1812
|
+
##
|
1813
|
+
# Create a new DirectoryOnDisk instance. This
|
1814
|
+
def initialize(path, name, perm)
|
1815
|
+
@name = name
|
1816
|
+
@path = File.expand_path(path)
|
1817
|
+
|
1818
|
+
s = File.stat(@path)
|
1819
|
+
pwent = Etc.getpwuid(s.uid)
|
1820
|
+
grent = Etc.getgrgid(s.gid)
|
1821
|
+
argv = { :permissions => perm & s.mode, :apponly => false,
|
1822
|
+
:excl => false, :uid => pwent.name, :gid => grent.name }
|
1823
|
+
super(@name, argv)
|
1824
|
+
self.refresh
|
1825
|
+
end
|
1826
|
+
|
1827
|
+
##
|
1828
|
+
# Read all metadata from the underlying directory. If _update_children_
|
1829
|
+
# is true, all immediate children of this directory will be refreshed
|
1830
|
+
# as well.
|
1831
|
+
#--
|
1832
|
+
# TODO: check for files deleted in the host filesystem
|
1833
|
+
#++
|
1834
|
+
#
|
1835
|
+
def refresh(update_children=true)
|
1836
|
+
s = File.stat(@path)
|
1837
|
+
@mtime = s.mtime
|
1838
|
+
@atime = s.atime
|
1839
|
+
unless update_children
|
1840
|
+
return
|
1841
|
+
end
|
1842
|
+
Dir.foreach(@path) do |file|
|
1843
|
+
# do not treat . or .. as valid files
|
1844
|
+
if file == '.' || file == '..'
|
1845
|
+
next
|
1846
|
+
end
|
1847
|
+
# Check if a file with this name is already known
|
1848
|
+
sf = self[file]
|
1849
|
+
if sf.nil?
|
1850
|
+
begin
|
1851
|
+
filepath = @path + File::SEPARATOR + file
|
1852
|
+
sf = FileOnDisk.new(filepath)
|
1853
|
+
if sf.is_a?(SDirectory)
|
1854
|
+
sf.permissions = @permissions
|
1855
|
+
else
|
1856
|
+
# This is an SFile (a FileOnDisk). Set to the same permissions
|
1857
|
+
# as the host directory without the execute flags.
|
1858
|
+
sf.permissions = @permissions & 0666
|
1859
|
+
end
|
1860
|
+
self << sf
|
1861
|
+
rescue Exception => e
|
1862
|
+
# This should be impossible
|
1863
|
+
end
|
1864
|
+
else
|
1865
|
+
# The file is already known to us. Refresh the file metadata
|
1866
|
+
# but do not descend into subdirectories (could lead to deep
|
1867
|
+
# recursion).
|
1868
|
+
if (sf.is_a?(SDirectory))
|
1869
|
+
sf.refresh(false)
|
1870
|
+
else
|
1871
|
+
sf.refresh
|
1872
|
+
end
|
1873
|
+
end
|
1874
|
+
end
|
1875
|
+
end
|
1876
|
+
end
|
1877
|
+
|
1346
1878
|
end # module Server
|
1347
1879
|
|
1348
1880
|
end # module RStyx
|