This page gives some information about the differences in the Plan 9 I/O facilities, compared to the regular I/O package.
One feature which is notably absent from Plan 9 is the poll
system call (and its cousin, select
). In fact, there are no non-blocking I/O facilities at all. This is a rather painful omission, and the only way around it is to fork a different process for the potentially blocking I/O calls. The Plan 9 version of Object Icon provides some classes which assist with this, namely FileWorker
and NonBlockStream
.
FileWorker
takes an ordinary FileStream
instance, dup
s its file descriptor, forks a background process, and performs I/O operations on the file in that process as instructed by the client. A buffer is allocated for read and write data. At any time, the foreground (client) process can query the FileWorker
to check whether it is busy carrying out an operation. For example, the client may wish to read some data from the file. The op_read
method is called; firstly, this waits until the worker is no longer busy. Then it causes the background process to start a read()
call; the worker is now busy, and the op_read
method returns. The client can check at intervals to see if the operation is complete, and then read the error status and/or data.
Other than reading, FileWorker
can carry out write
and close
operations. It can also open
and create
a file, in which case an initial file is not passed to the constructor. The resulting file descriptor can be dup
ed and used elsewhere by the client, or used in further operations in the FileWorker
.
An important method in FileWorker
is await()
. This waits for the background operation (if any) to finish. Then, if the result code of the last operation indicates an error, it fails, setting &why
. Otherwise it succeeds, returning the result code.
Here is an example program which opens a file and performs five background read operations, outputting the results.
import io
procedure main(a)
local w, n
# Create a worker
w := FileWorker()
# Start an open operation in the background
w.op_open(a[1], FileOpt.RDONLY)
# Wait for it to finish, and stop if there was an error
w.await() | stop(&why)
every 1 to 5 do {
# Start a read into the buffer
w.op_read()
# Print some dots whilst we're waiting
while w.is_running() do {
writes(".")
delay(100)
}
# Get the result (await won't block since the worker isn't
# running now).
if n := w.await() then {
if n = 0 then
write(" EOF")
else
write(" -> ", w.get_buff(n))
} else
write(" failed ", &why)
}
w.close()
end
An example result of running it on a file from the tickfs example :-
% testfileworker /n/tick/4
...... -> *
...... -> *
...... -> *
...... -> *
...... -> *
This class builds upon FileWorker
. It too wraps another file, and utilizes two separate FileWorker
s to operate on it - one for reading and one for writing. (In fact, if the file is read-only or write-only, then only one FileWorker
is used). It also has a read buffer so that one background read result can be consumed by several calls to in()
.
Here is the above example re-written to use a NonBlockStream
.
Download testnonblockstream.icn
import io
procedure main(a)
local f, nb, s
# Open a file for reading.
f := FileStream(a[1]) | stop(&why)
# Create a read-only NonBlockStream operating on f
nb := NonBlockStream(f)
every 1 to 5 do {
# Print some dots whilst we're waiting
while nb.get_in_status() == NonBlockStream.READING do {
writes(".")
delay(100)
}
# Get the result
if s := nb.in(32) then {
if /s then
write(" EOF")
else
write(" -> ", s)
} else
write(" failed ", &why)
}
# This will close f too.
nb.close()
end
The output is just the same as testfileworker
above.
Note how it is not necessary to start a read operation explicitly; this is done automatically at startup and whenever the read buffer is emptied by in()
.
Both FileWorker
and NonBlockStream
can be used with the DescStream.poll
method, which in Plan 9 is implemented as a simple loop instead of the usual native method. The test program can be altered to use poll
as follows :-
import io
procedure main(a)
local f, nb, s, l
# Open a file for reading.
f := FileStream(a[1]) | stop(&why)
# Create a read-only NonBlockStream operating on f
nb := NonBlockStream(f)
every 1 to 5 do {
repeat {
# Poll with a zero timeout (ie return immediately).
l := DescStream.poll([nb, Poll.IN], 0) | stop(&why)
if /l then {
# A null result means timeout, so no data
writes(".")
delay(100)
} else {
# Otherwise print the result value and break
writes(" ", l[1])
break
}
}
# Get the result
if s := nb.in(32) then {
if /s then
write(" EOF")
else
write(" -> ", s)
} else
write(" failed ", &why)
}
# This will close f too.
nb.close()
end
The output will again be the same as above, except that this program also prints out the value returned by poll
. But the main point is that this program is almost identical to a non-Plan 9 program; the only Plan 9 specific part is the wrapping of the FileStream
with a NonBlockStream
. This makes is quite easy to get non-Plan 9 programs which use poll
to work.
Plan 9 doesn’t have sockets in the Unix sense, so there is no SocketStream
implementation. Instead, there is a NetStream
class which provides similar functionality. To illustrate this, here is the Plan 9 version of the simple server program shown on the co-expressions page.
import io
global sched, x_flag
procedure handle(a)
local t, s
t := Task(sched, create {
# Service the client with a 30s timeout
a := TaskStream(NonBlockStream(a), t, 30000)
write("\tHandling...")
repeat {
s := a.read() | {
write("\tRead failed ", &why)
break
}
# An input of "x" shuts down the server
if s == "x" then {
x_flag := 1
break
}
# An input of "q" ends this client's session
if s == "q" then
break
a.write(map(s))
}
write("\tEnd of handling.")
a.close()
t.revert()
})
t.start()
end
procedure main()
local f, x, lf
# Use a 50ms delay in the Scheduler's call to poll.
sched := Scheduler(50)
x := Task(sched, create {
f := NetStream.announce("tcp!*!7000") | stop(&why)
write("Listening...")
repeat {
lf := f.listen(, x) | stop(&why)
lf.accept()
handle(lf)
}
})
x.start()
until \x_flag do {
sched.work()
# Could do some extra work between polls.
}
f.close()
write("Exiting")
end
The io.Files
class provides some additional methods which are Plan 9 specific. Files.unmount_windows
unmounts unmounts any Object Icon window mount directories in /n
; this is useful to avoid inadvertently creating a reference to the mount on a fork; such a reference will keep the window open after it is closed by the program.
The Files
class also includes bind
, mount
and unmount
methods which do what you would expect. Mount option flags can be found in io.MountOpt
.
The Files.wstat
method (and DescStream.wstat
too) is slightly different in Plan 9, allowing the length and name to be set too. The Stat
class has some Plan 9 additions, including the file name.
FileStream
has two static methods, open9
and create9
, which match more exactly the Plan 9 open
and create
system calls. Option flags can be found in io.FileOpt9
. The pipe()
call is also extended to take option flags for the pipe files.