A RegularFile.T
, or regular file handle, provides access to a
persistent extensible sequence of bytes.
INTERFACERegularFile ; IMPORT File, OSError; TYPE T <: Public; Public = File.T OBJECT METHODS seek(origin: Origin; offset: INTEGER): INTEGER RAISES {OSError.E}; flush() RAISES {OSError.E}; lock(): BOOLEAN RAISES {OSError.E}; unlock() RAISES {OSError.E} END; Origin = {Beginning, Current, End}; VAR (*CONST*) FileType: File.Type;
Equal to {\tt Atom.FromText("RegularFile").}
END RegularFile.Like every
File.T
, a regular file handle h
has the components
type(h) an atom, equal to FileType readable(h) a boolean writable(h) a booleanA regular file handle
h
also has the components
cur(h) an integer, the index of the next byte to read or write file(h) the identity of a regular fileThere may be distinct regular file handles
h1
and h2
with
file(h1)
equal to file(h2)
, and more than one process may hold
a single regular file handle (see Process.Create
).
A regular file (not a handle) f
has the components
buffer(f) an extensible byte sequence stable(f) an extensible byte sequence mtime(f) a Time.T, the last modification time locked(f) a Process.IDThe sequences
buffer(f)
and stable(f)
are zero-based and always
have the same length. stable(f)
represents the contents of the
file on the disk or other persistent storage medium, while
buffer(f)
represents write-behind caching performed by the
operating system. From time to time, a daemon performs
WITH i = some integer i in the range [0..len(buffer(f))-1] DO stable(f)[i] := buffer(f)[i] ENDThe methods described in this interface are atomic with respect to the daemon.
The meaning of the call
h.read(b, mayBlock)is given by the specification of
File.T.read
together with
these definitions, where f = file(h)
:
src(h) = buffer(f) srcCur(h) = cur(h) srcEof(h) = TRUEBecause
srcEof(h)
is always TRUE
, read
never blocks.
However, a subsequent read can return more data if an interleaved
write extends buffer(f)
. If cur(h)
is negative (because of a
prior seek), read
raises OSError.E
.
The meaning of the call
h.write(b)is given by the specification of
File.T.write
together with these
definitions, where f = file(h)
:
snk(h) = buffer(f) snkCur(h) = cur(h)In addition,
write
sets mtime(file(h))
to the current time. If
write
is called when cur(h) > size(f)
(because of a prior
seek), it extends f
with bytes of undefined value. If cur(h)
is negative, write
raises OSError.E
.
The call
h.status(stat)is equivalent to the following, in which
stat
is a local variable
of type Status
:
stat.type := FileType; stat.modificationTime := mtime(file(h)); stat.size := len(buffer(file(h))); RETURN statThe call
h.seek(origin, offset)is equivalent to
CASE origin OF Origin.Beginning => cur(h) := offset | Origin.Current => cur(h) := cur(h)+offset | Origin.End => cur(h) := len(buffer(file(h)))+offset END; RETURN cur(h)Note that
seek
never changes the length of the file, although a
subsequent write may do so. Use the call h.seek(Origin.Current,
0)
to determine cur(h)
without changing it.
The call
h.flush()is equivalent to
WITH f = file(h) DO FOR i := 0 TO len(buffer(f))-1 DO stable(f)[i] := buffer(f)[i] END ENDThe call
h.close()extends the normal action of the
close
method with
IF locked(file(h) = Process.GetMyID() THEN locked(file(h)) := Process.NullID ENDIf the file h is not already locked by the calling process (i.e., if locked(file(h)) # Process.GetMyID()), the call
h.lock()is equivalent to:
IF locked(file(h)) = Process.NullID THEN locked(file(h)) := Process.GetMyID(); RETURN TRUE ELSIF locked(file(h)) = Process.GetMyID() THEN RETURN TRUE END; RETURN FALSEIn the event that h is already locked by the calling process, the result of h.lock() is implementation-dependent. However, clients can work around the undefined nature of the operation in this case by keeping track of locked(file(h)) explicitly.
The call
h.unlock()is equivalent to:
IF locked(file(h)) # Process.GetMyID() THEN RAISE OSError.E END; locked(file(h)) := Process.NullIDSome implementations raise an exception if a process tries to read or write a file locked by another process. You should treat this as a checked runtime error rather than writing code to catch and recover from the exception; the same applies to unlocking a file that you didn't lock.
You lock a file with code like
CONST MaxTry = 3; RetryInterval = 5.0D0; VAR try := 1; BEGIN WHILE NOT h.lock() DO IF try=MaxTry THEN Give up END; INC(try); Time.Pause(RetryInterval) END; TRY Read or write h FINALLY h.unlock() END ENDThe regular file underlying a regular file handle is monitored, thus allowing concurrent operations. We leave unspecified the unit of atomicity for reads and writes, so a set of processes sharing a file that needs to be updated should use the
lock
and unlock
methods. A regular file handle itself should be treated as
unmonitored. A client thread typically needs to perform a seek
followed by a read
or write
as an atomic unit, which can be
implemented with a mutex in the client.