Class: RStyx::Server::SFile

Inherits:
Monitor
  • Object
show all
Defined in:
lib/rstyx/server.rb

Overview

Class representing a file (or directory) on a Styx server. There may be different types of file: a file might map directly to a file on disk, or it may be a synthetic file representing a program interface. This class creates a Styx file which does nothing useful: returning errors when reading from or writing to it. Subclasses should override the SFile#read, SFile#write and SFile#length methods to implement the desired behavior. Each Styx file has exactly one parent, the directory which contains it, thus symbolic links on the underlying operating system cannot be represented.

Direct Known Subclasses

FileOnDisk, InMemoryFile, SDirectory

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name, argv = { :permissions => 0666, :apponly => false, :excl => false, :uid => ENV["USER"], :gid => ENV["GROUP"] }) ⇒ SFile

Create a new file object with name name. This accepts a hash argv for the file’s other parameters with the following keys:

:permissions

The permissions of the file (e.g. 0755 in octal). the default is 0666.

:apponly

true if the file is append only. Default is false.

:excl

true if the file is for exclusive use, i.e. only one

client at a time may open.  Default is false.
:user

the username of the owner of the file. If not specified

gets the value from the environment variable USER.
:group

the group name of the owner of the file. If not specified

gets the value from the environment variable GROUP.


1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
# File 'lib/rstyx/server.rb', line 1241

def initialize(name, argv={ :permissions => 0666, :apponly => false,
                 :excl => false, :uid => ENV["USER"],
                 :gid => ENV["GROUP"] })
  super()
  if name == "" || name == "." || name == ".."
    raise StyxException.new("Illegal file name")
  end
  # The parent directory of the file.
  @parent = nil
  # The name of the file.
  @name = name
  # True if this is a directory
  @directory = false
  # True if this is an append-only file
  @appendonly = argv[:apponly]
  # True if this file may be opened by only one client at a time
  @exclusive = argv[:excl]
  # True if this is a file to be used by the authentication mechanism (normally false)
  @auth = false
  # Permissions represented as a number, e.g. 0755 in octal
  @permissions = argv[:permissions]
  # Version number of the file, incremented whenever the file is
  # modified
  @version = 0
  # Time of creation
  @ctime = Time.now
  # Last access time
  @atime = Time.now
  # Last modification time
  @mtime = Time.now
  # Owner name
  @uid = argv[:user]
  # Group name
  @gid = argv[:group]
  # User who last modified the file
  @muid = ""
  # The clients who have a connection to the file
  @clients = []
  @clients.extend(MonitorMixin)
  # Change listeners
  @changelisteners = []
end

Instance Attribute Details

#atimeObject

Last access time



1217
1218
1219
# File 'lib/rstyx/server.rb', line 1217

def atime
  @atime
end

#gidObject (readonly)

Group name



1204
1205
1206
# File 'lib/rstyx/server.rb', line 1204

def gid
  @gid
end

#mtimeObject (readonly)

Last modification time



1211
1212
1213
# File 'lib/rstyx/server.rb', line 1211

def mtime
  @mtime
end

#muidObject (readonly)

Name of the user who last modified the file



1208
1209
1210
# File 'lib/rstyx/server.rb', line 1208

def muid
  @muid
end

#nameObject (readonly)

File name; must be / if the file is the root directory of the server



1197
1198
1199
# File 'lib/rstyx/server.rb', line 1197

def name
  @name
end

#parentObject

Parent directory which contains this file



1221
1222
1223
# File 'lib/rstyx/server.rb', line 1221

def parent
  @parent
end

#permissionsObject

Permissions and flags



1214
1215
1216
# File 'lib/rstyx/server.rb', line 1214

def permissions
  @permissions
end

#uidObject (readonly)

Owner name



1200
1201
1202
# File 'lib/rstyx/server.rb', line 1200

def uid
  @uid
end

#versionObject

Version number for the given path



1224
1225
1226
# File 'lib/rstyx/server.rb', line 1224

def version
  @version
end

Instance Method Details

#add_changelistener(&block) ⇒ Object

Add a change listener to the list of change listeners of this file. The change listener will execute whenever some modification is made to the file, and is passed the SFile instance to which it is attached as a parameter.

block

the change listener block



1619
1620
1621
# File 'lib/rstyx/server.rb', line 1619

def add_changelistener(&block)
  @changelisteners << block
end

#add_client(cl) ⇒ Object

Add a client to the list of clients reading this file.

cl

an SFileClient instance representing the client reading the file



1543
1544
1545
1546
# File 'lib/rstyx/server.rb', line 1543

def add_client(cl)
  @clients.synchronize { @clients << cl }
  self.client_connected(cl)
end

#appendonly?Boolean

Check if the file is append-only

Returns:

  • (Boolean)


1302
1303
1304
# File 'lib/rstyx/server.rb', line 1302

def appendonly?
  return(@appendonly)
end

#auth?Boolean

Check if the file is an authenticator

Returns:

  • (Boolean)


1316
1317
1318
# File 'lib/rstyx/server.rb', line 1316

def auth?
  return(@auth)
end

#can_setlength?(newlength) ⇒ Boolean

Check to see if the length of this file can be changed to the given value. If this does not throw an exception then SFile#length= should always succeed. The default implementation always throws an exception; subclasses should override this method if they want the length of the file to be changeable.

Returns:

  • (Boolean)

Raises:



1428
1429
1430
# File 'lib/rstyx/server.rb', line 1428

def can_setlength?(newlength)
  raise StyxException.new("Cannot change the length of this file directly")
end

#can_setmode?(newmode) ⇒ Boolean

Checks to see if this file allows the mode (permissions and flags) of the file to be changed. This is called when the server receives a Twstat message. This default implementation does nothing.

newmode: the new mode of the file (permissions plus any other flags

such as DMDIR, etc.)

Returns:

  • (Boolean)


1378
1379
# File 'lib/rstyx/server.rb', line 1378

def can_setmode?(newmode)
end

#can_setmtime?(nmtime) ⇒ Boolean

Check to see if the modification time of this file can be changed to the given value. If this does not throw an exception then set_mtime should always succeed.

Returns:

  • (Boolean)


1444
1445
1446
# File 'lib/rstyx/server.rb', line 1444

def can_setmtime?(nmtime)
  return(true)
end

#can_setname?(name) ⇒ Boolean

Check if the name may be changed. Raises a StyxException if this is not possible.

Returns:

  • (Boolean)


1288
1289
# File 'lib/rstyx/server.rb', line 1288

def can_setname?(name)
end

#client(sess, fid) ⇒ Object

Get the client connection to this file

sess

the client session in question

fid

the fid that the client is using to access the file.

returns

the SFileClient instance representing that file access, or nil if there is no such client connection.



1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
# File 'lib/rstyx/server.rb', line 1559

def client(sess, fid)
  @clients.synchronize do
    @clients.each do |cl|
      if cl.session == sess && cl.fid == fid
        return(cl)
      end
    end
  end
  return(nil)
end

#client_connected(cl) ⇒ Object



1548
1549
# File 'lib/rstyx/server.rb', line 1548

def client_connected(cl)
end

#client_disconnected(cl) ⇒ Object

Add any custom behavior for the file that has to happen whenever a client disconnects from the file here.



1608
1609
# File 'lib/rstyx/server.rb', line 1608

def client_disconnected(cl)
end

#contents_changedObject

Method executed whenever the contents of the file change. This increments the file’s version on the server, and executes any change listeners active on the file.



1628
1629
1630
1631
1632
1633
# File 'lib/rstyx/server.rb', line 1628

def contents_changed
  version_incr
  @changelisteners.each do |listener|
    listener.call(self)
  end
end

#deleteObject

Any pre-deletion actions must be performed in this method.



1535
1536
# File 'lib/rstyx/server.rb', line 1535

def delete
end

#directory?Boolean

Check if the File is a directory (should always be the same as Object#instance_of?(Directory).

Returns:

  • (Boolean)


1295
1296
1297
# File 'lib/rstyx/server.rb', line 1295

def directory?
  return(@directory)
end

#exclusive?Boolean

Check if the file is marked as exclusive use

Returns:

  • (Boolean)


1309
1310
1311
# File 'lib/rstyx/server.rb', line 1309

def exclusive?
  return(@exclusive)
end

#filetypeObject

Gets the type of the file as a number representing the OR of DMDIR, DMAPPEND, DMEXCL, and DMAUTH as appropriate, used to create the Qid.



1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
# File 'lib/rstyx/server.rb', line 1342

def filetype
  type = 0
  if @directory
    type |= DMDIR
  end

  if @appendonly
    type |= DMAPPEND
  end

  if @exclusive
    type |= DMEXCL
  end

  if @auth
    type |= DMAUTH
  end

  return(type)
end

#full_pathObject

Get the full path relative to the root of the filesystem.



1323
1324
1325
1326
1327
1328
# File 'lib/rstyx/server.rb', line 1323

def full_path
  if auth? || @parent.nil?
    return(@name)
  end
  return(@parent.full_path + @name)
end

#lengthObject

Get the length of the file. This default implementation returns zero: subclasses must override this method.



1334
1335
1336
# File 'lib/rstyx/server.rb', line 1334

def length
  return(0)
end

#length=(newlength) ⇒ Object

Sets the length of the file. The usual disclaimers about permissions and SFile#can_setlength? apply. Default implementation does nothing and it should be overriden by subclasses.



1437
1438
# File 'lib/rstyx/server.rb', line 1437

def length=(newlength)
end

#modeObject

Gets the mode of the file (permissions and flags)



1366
1367
1368
# File 'lib/rstyx/server.rb', line 1366

def mode
  return(self.filetype | @permissions)
end

#mode=(newmode) ⇒ Object

Sets the mode of the file (permissions plus other flags). Must check all the relevant permissions and call SFile#can_setmode? before calling this method, as the assumption is that this method will always succeed.



1386
1387
1388
1389
1390
1391
1392
# File 'lib/rstyx/server.rb', line 1386

def mode=(newmode)
  @appendonly = (newmode & DMAPPEND == DMAPPEND)
  @exclusive = (newmode & DMEXCL == DMEXCL)
  @auth = (newmode & DMAUTH == DMAUTH)
  @permissions = newmode & 0x03fff
  return(newmode)
end

#num_clientsObject

Return the number of clients accessing this file.



1573
1574
1575
1576
1577
1578
# File 'lib/rstyx/server.rb', line 1573

def num_clients
  @clients.synchronize do
    remove_dead_clients
    return(@clients.length)
  end
end

#qidObject

Returns the Qid of this file.



1397
1398
1399
1400
1401
# File 'lib/rstyx/server.rb', line 1397

def qid
  t = filetype() >> 24 & 0xff
  q = Message::Qid.new(t, @version, self.uuid)
  return(q)
end

#read(client, offset, count) ⇒ Object

Reads data from this file. This method should be overridden by subclasses and should return an Rread with the data read. This default implementation simply throws a StyxException, which results in an Rerror being returned to the client. Subclasses should override this to provide the desired behavior when the file is read.

client

the SFileClient object representing the client reading from this file.

offset

the offset the client wants to read from

count

the number of bytes that the client wishes to read

Raises:



1501
1502
1503
# File 'lib/rstyx/server.rb', line 1501

def read(client, offset, count)
  raise StyxException.new("Cannot read from this file")
end

#refresh(update_children) ⇒ Object

Refreshes this file (if it represents another entity, such as a file on disk, this method is used to make sure that the file metadata (length, access time, etc.) are up to date. This default implementation does nothing; subclasses must override this to provide the correct functionality.



1673
1674
# File 'lib/rstyx/server.rb', line 1673

def refresh(update_children)
end

#removeObject

Remove the file from the Styx server. This will simply remove the file from the parent directory.



1527
1528
1529
1530
# File 'lib/rstyx/server.rb', line 1527

def remove
  self.delete
  self.parent.remove_child(self)
end

#remove_client(cl) ⇒ Object

Remove the client cl from the list of clients accessing the file.



1597
1598
1599
1600
1601
1602
# File 'lib/rstyx/server.rb', line 1597

def remove_client(cl)
  unless cl.nil?
    @clients.delete(cl)
    client_disconnected(cl)
  end
end

#remove_dead_clientsObject

Remove clients which are no longer really using the file. If a client session is either gone or the session is no longer connected, it removes the client from the list.



1584
1585
1586
1587
1588
1589
1590
1591
1592
# File 'lib/rstyx/server.rb', line 1584

def remove_dead_clients
  @clients.synchronize do
    @clients.each do |clnt|
      if clnt.session.nil? || !clnt.session.connected?
        remove_client(clnt)
      end
    end
  end
end

#rename(newname) ⇒ Object

Rename the file to newname. This will raise a StyxException if

  1. An attempt is made to rename a file representing the root directory.

  2. An attempt is made to rename a file to a name of some other file already present in the same directory.



1466
1467
1468
1469
1470
1471
1472
1473
1474
# File 'lib/rstyx/server.rb', line 1466

def rename(newname)
  if @parent == nil
    raise StyxException.new("Cannot change the name of the root directory")
  end
  if @parent.has_child?(newname)
    raise StyxException.new("A file with name #{newname} already exists in this directory")
  end
  @name = newname
end

#reply_read(data) ⇒ Object

Return a Message::Rread for a successful read of data. This updates access time and should be used instead of manually returning a Message::Rread.



1661
1662
1663
1664
# File 'lib/rstyx/server.rb', line 1661

def reply_read(data)
  @atime = Time.now
  return(Message::Rread.new(:data => data))
end

#reply_write(count, session) ⇒ Object

Return a Message::Rwrite for a successful write of count bytes from session. A write method for a subclass should use this method (which updates mtime, atime, and calls contents_changed callbacks) instead of manually returning a Message::Rwrite



1649
1650
1651
1652
1653
1654
# File 'lib/rstyx/server.rb', line 1649

def reply_write(count, session)
  @atime = Time.now
  self.set_mtime(@atime, session.user)
  self.contents_changed
  return(Message::Rwrite.new(:count => count))
end

#set_mtime(nmtime, uid) ⇒ Object

Sets the mtime of the file. The usual disclaimers about permissions and SFile#can_setmtime? apply. Default implementation will simply set the modification time to nmtime and the muid to uid.



1453
1454
1455
1456
# File 'lib/rstyx/server.rb', line 1453

def set_mtime(nmtime, uid)
  @mtime = nmtime
  @muid = uid
end

#statObject

Returns a Stat object for this file.



1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
# File 'lib/rstyx/server.rb', line 1406

def stat
  s = Message::Stat.new
  s.dtype = s.dev = 0
  s.qid = self.qid
  s.mode = self.mode
  s.atime = @atime.to_i
  s.mtime = @mtime.to_i
  s.length = self.length
  s.name = @name
  s.uid = @uid
  s.gid = @gid
  s.muid = @muid
  return(s)
end

#uuidObject

Gets the unique numeric ID for the path of this file (generated from the low-order bytes of the creation time and the hashcode of the full path). If the file is deleted and re-created the unique ID will change (except for the extremely unlikely case in which the low-order bytes of the creation time happen to be the same in the new file and the old file).



1483
1484
1485
1486
# File 'lib/rstyx/server.rb', line 1483

def uuid
  tbytes = @ctime.to_i & 0xffffffff
  return((self.full_path.hash << 32) | tbytes)
end

#version_incrObject

Increment the file’s version. This wraps after the version goes above 2^64.



1639
1640
1641
# File 'lib/rstyx/server.rb', line 1639

def version_incr
  @version = ((@version + 1) & 0xffffffffffffffff)
end

#write(client, offset, data, truncate) ⇒ Object

Writes data to this file. This method should be overriden by subclasses to provide the desired behavior when the file is written to. It should return the number of bytes actually written.

client

the SFileClient object representing the client writing to this file

offset

the offset the client wants to write to

data

the data that the client wishes to write

truncate

true or false depending on whether the file is to be truncated.

Raises:



1519
1520
1521
# File 'lib/rstyx/server.rb', line 1519

def write(client, offset, data, truncate)
  raise StyxException.new("Cannot write to this file")
end