System Calls
Scsh provides (almost) complete access to the basic Unix kernel services: processes, files, signals and so forth. These procedures comprise a Scheme binding for POSIX, with a few of the more standard extensions thrown in (e.g., symbolic links, fchown, fstat, sockets).
3.1 Errors
Scsh syscalls never return error codes, and do not use a global errno variable to report errors. Errors are consistently reported by raising exceptions. This frees up the procedures to return useful values, and allows the programmer to assume that if a syscall returns, it succeeded. This greatly simplifies the flow of the code from the programmer's point of view.
Since Scheme does not yet have a standard exception system, the scsh definition remains somewhat vague on the actual form of exceptions and exception handlers. When a standard exception system is defined, scsh will move to it. For now, scsh uses the Scheme 48 exception system, with a simple sugaring on top to hide the details in the common case.
System call error exceptions contain the Unix errno code reported by the system call. Unlike C, the errno value is a part of the exception packet, it is not accessed through a global variable.
For reference purposes, the Unix errno numbers are bound to the variables errno/perm, errno/noent, etc. System calls never return error/intr -- they automatically retry.
Raises a Unix error exception for Unix error number errno. The syscall and data arguments are packaged up in the exception packet passed to the exception handler.
Unix syscalls raise error exceptions by calling errno-error. Programs can use with-errno-handler* to establish handlers for these exceptions.If a Unix error arises while thunk is executing, handler is called on two arguments like this:
(handler errno packet)packet is a list of the form
packet = (errno-msg syscall . data), where errno-msg is the standard Unix error message for the error, syscall is the procedure that generated the error, and data is a list of information generated by the error, which varies from syscall to syscall.
If handler returns, the handler search continues upwards. Handler can acquire the exception by invoking a saved continuation. This procedure can be sugared over with the following syntax:
This form executes the body forms with a particular errno handler installed. When an errno error is raised, the handler search machinery will bind variable errno to the error's integer code, and variable packet to the error's auxiliary data packet. Then, the clauses will be checked for a match. The first clause that matches is executed, and its value is the value of the entire with-errno-handler form. If no clause matches, the handler search continues.
(with-errno-handler
((errno packet) clause ...)
body1
body2
...)Error clauses have two forms
In the first type of clause, the errno forms are integer expressions. They are evaluated and compared to the error's errno value. An else clause matches any errno value. Note that the errno and data variables are lexically visible to the error clauses.
((errno ...) body ...)
(else body ...)Example:
It is not defined what dynamic context the handler executes in, so fluid variables cannot reliably be referenced.
(with-errno-handler
((errno packet) ; Only handle 3 particular errors.
((errno/wouldblock errno/again)
(loop))
((errno/acces)
(format #t "Not allowed access!")
#f))
(foo frobbotz)
(blatz garglemumph))Note that Scsh system calls always retry when interrupted, so that the errno/intr exception is never raised. If the programmer wishes to abort a system call on an interrupt, he should have the interrupt handler explicitly raise an exception or invoke a stored continuation to throw out of the system call.
3.1.1 Interactive mode and error handling
Scsh runs in two modes: interactive and script mode. It starts up in interactive mode if the scsh interpreter is started up with no script argument. Otherwise, scsh starts up in script mode. The mode determines whether scsh prints prompts in between reading and evaluating forms, and it affects the default error handler. In interactive mode, the default error handler will report the error, and generate an interactive breakpoint so that the user can interact with the system to examine, fix, or dismiss from the error. In script mode, the default error handler causes the scsh process to exit.
When scsh forks a child with (fork), the child resets to script mode. This can be overridden if the programmer wishes.
3.2 I/O
3.2.1 Standard R5RS I/O procedures
In scsh, most standard R5RS I/O operations (such as display or read-char) work on both integer file descriptors and Scheme ports. When doing I/O with a file descriptor, the I/O operation is done directly on the file, bypassing any buffered data that may have accumulated in an associated port. Note that character-at-a-time operations such as read-char are likely to be quite slow when performed directly upon file descriptors.
The standard R5RS procedures read-char, char-ready?, write, display, newline, and write-char are all generic, accepting integer file descriptor arguments as well as ports. Scsh also mandates the availability of format, and further requires format to accept file descriptor arguments as well as ports.
The procedures peek-char and read do not accept file descriptor arguments, since these functions require the ability to read ahead in the input stream, a feature not supported by Unix I/O.
3.2.2 Port manipulation and standard ports
Returns (consumer port), but closes the port on return. No dynamic-wind magic.Remark: Is there a less-awkward name?
This procedure is analogous to current-output-port, but produces a port used for error messages -- the scsh equivalent of stderr.
These procedures install port as the current input, current output, and error output port, respectively, for the duration of a call to thunk.
These special forms are simply syntactic sugar for the with-current-input-port* procedure and friends.
These procedures alter the dynamic binding of the current I/O port procedures to new values.
Close the port or file descriptor.If fd/port is a file descriptor, and it has a port allocated to it, the port is shifted to a new file descriptor created with (dup fd/port) before closing fd/port. The port then has its revealed count set to zero. This reflects the design criteria that ports are not associated with file descriptors, but with open files.
To close a file descriptor, and any associated port it might have, you must instead say one of (as appropriate):
(close (fdes->inport fd))
(close (fdes->outport fd))The procedure returns true if it closed an open port. If the port was already closed, it returns false; this is not an error.
These two procedures are used to synchronise Unix' standard I/O file descriptors and Scheme's current I/O ports.(stdports->stdio) causes the standard I/O file descriptors (0, 1, and 2) to take their values from the current I/O ports. It is exactly equivalent to the series of redirections:3
stdio->stdports causes the bindings of the current I/O ports to be changed to ports constructed over the standard I/O file descriptors. It is exactly equivalent to the series of assignments
(dup (current-input-port) 0)
(dup (current-output-port) 1)
(dup (error-output-port) 2)However, you are more likely to find the dynamic-extent variant, with-stdio-ports*, below, to be of use in general programming.
(set-current-input-port! (fdes->inport 0))
(set-current-output-port! (fdes->outport 1))
(set-error-output-port! (fdes->outport 2))
with-stdio-ports* binds the standard ports (current-input-port), (current-output-port), and (error-output-port) to be ports on file descriptors 0, 1, 2, and then calls thunk. It is equivalent to:The with-stdio-ports special form is merely syntactic sugar.
(with-current-input-port (fdes->inport 0)
(with-current-output-port (fdes->inport 1)
(with-error-output-port (fdes->outport 2)
(thunk))))
3.2.3 String ports
Scheme 48 has string ports, which you can use. Scsh has not committed to the particular interface or names that Scheme 48 uses, so be warned that the interface described herein may be liable to change.
Returns a port that reads characters from the supplied string.
A string output port is a port that collects the characters given to it into a string. The accumulated string is retrieved by applying string-output-port-output to the port.
The procedure value is called on a port. When it returns, call-with-string-output-port returns a string containing the characters that were written to that port during the execution of procedure.
3.2.4 Revealed ports and file descriptors
The material in this section and the following one is not critical for most applications. You may safely skim or completely skip this section on a first reading.
Dealing with Unix file descriptors in a Scheme environment is difficult. In Unix, open files are part of the process environment, and are referenced by small integers called file descriptors. Open file descriptors are the fundamental way I/O redirections are passed to subprocesses, since file descriptors are preserved across fork's and exec's.
Scheme, on the other hand, uses ports for specifying I/O sources. Ports are garbage-collected Scheme objects, not integers. Ports can be garbage collected; when a port is collected, it is also closed. Because file descriptors are just integers, it's impossible to garbage collect them -- you wouldn't be able to close file descriptor 3 unless there were no 3's in the system, and you could further prove that your program would never again compute a 3. This is difficult at best.
If a Scheme program only used Scheme ports, and never actually used file descriptors, this would not be a problem. But Scheme code must descend to the file descriptor level in at least two circumstances:
when interfacing to foreign code
when interfacing to a subprocess.
This causes a problem. Suppose we have a Scheme port constructed on top of file descriptor 2. We intend to fork off a program that will inherit this file descriptor. If we drop references to the port, the garbage collector may prematurely close file 2 before we fork the subprocess. The interface described below is intended to fix this and other problems arising from the mismatch between ports and file descriptors.
The Scheme kernel maintains a port table that maps a file descriptor to the Scheme port allocated for it (or, #f if there is no port allocated for this file descriptor). This is used to ensure that there is at most one open port for each open file descriptor.
The port data structure for file ports has two fields besides the descriptor: revealed and closed?. When a file port is closed with (close port), the port's file descriptor is closed, its entry in the port table is cleared, and the port's closed? field is set to true.
When a file descriptor is closed with (close fdes), any associated port is shifted to a new file descriptor created with (dup fdes). The port has its revealed count reset to zero (and hence becomes eligible for closing on GC). See discussion below. To really put a stake through a descriptor's heart without waiting for associated ports to be GC'd, you must say one of
(close (fdes->inport fdes))
(close (fdes->output fdes))
The revealed field is an aid to garbage collection. It is an integer semaphore. If it is zero, the port's file descriptor can be closed when the port is collected. Essentially, the revealed field reflects whether or not the port's file descriptor has escaped to the Scheme user. If the Scheme user doesn't know what file descriptor is associated with a given port, then he can't possibly retain an ``integer handle'' on the port after dropping pointers to the port itself, so the garbage collector is free to close the file.
Ports allocated with open-output-file and open-input-file are unrevealed ports -- i.e., revealed is initialised to 0. No one knows the port's file descriptor, so the file descriptor can be closed when the port is collected.
The functions fdes->output-port, fdes->input-port, port->fdes are used to shift back and forth between file descriptors and ports. When port->fdes reveals a port's file descriptor, it increments the port's revealed field. When the user is through with the file descriptor, he can call (release-port-handle port), which decrements the count. The function (call/fdes fd/port proc) automates this protocol. call/fdes uses dynamic-wind to enforce the protocol. If proc throws out of the call/fdes application, the unwind handler releases the descriptor handle; if the user subsequently tries to throw back into proc's context, the wind handler raises an error. When the user maps a file descriptor to a port with fdes->outport or fdes->inport, the port has its revealed field incremented.
Not all file descriptors are created by requests to make ports. Some are inherited on process invocation via exec(2), and are simply part of the global environment. Subprocesses may depend upon them, so if a port is later allocated for these file descriptors, is should be considered as a revealed port. For example, when the Scheme shell's process starts up, it opens ports on file descriptors 0, 1, and 2 for the initial values of (current-input-port), (current-output-port), and (error-output-port). These ports are initialised with revealed set to 1, so that stdin, stdout, and stderr are not closed even if the user drops the port.
Unrevealed file ports have the nice property that they can be closed when all pointers to the port are dropped. This can happen during gc, or at an exec() -- since all memory is dropped at an exec(). No one knows the file descriptor associated with the port, so the exec'd process certainly can't refer to it.
This facility preserves the transparent close-on-collect property for file ports that are used in straightforward ways, yet allows access to the underlying Unix substrate without interference from the garbage collector. This is critical, since shell programming absolutely requires access to the Unix file descriptors, as their numerical values are a critical part of the process interface.
A port's underlying file descriptor can be shifted around with dup(2) when convenient. That is, the actual file descriptor on top of which a port is constructed can be shifted around underneath the port by the scsh kernel when necessary. This is important, because when the user is setting up file descriptors prior to a exec(2), he may explicitly use a file descriptor that has already been allocated to some port. In this case, the scsh kernel just shifts the port's file descriptor to some new location with dup, freeing up its old descriptor. This prevents errors from happening in the following scenario. Suppose we have a file open on port f. Now we want to run a program that reads input on file 0, writes output to file 1, errors to file 2, and logs execution information on file 3. We want to run this program with input from f. So we write:
Now, suppose by ill chance that, unbeknownst to us, when the operating system opened f's file, it allocated descriptor 3 for it. If we blindly redirect trace.log into file descriptor 3, we'll clobber f! However, the port-shuffling machinery saves us: when the run form tries to dup trace.log's file descriptor to 3, dup will notice that file descriptor 3 is already associated with an unrevealed port (i.e., f). So, it will first move f to some other file descriptor. This keeps f alive and well so that it can subsequently be dup'd into descriptor 0 for prog's stdin.
(run (/usr/shivers/bin/prog)
(> 1 output.txt)
(> 2 error.log)
(> 3 trace.log)
(= 0 ,f))
The port-shifting machinery makes the following guarantee: a port is only moved when the underlying file descriptor is closed, either by a close() or a dup2() operation. Otherwise a port/file-descriptor association is stable.
Under normal circumstances, all this machinery just works behind the scenes to keep things straightened out. The only time the user has to think about it is when he starts accessing file descriptors from ports, which he should almost never have to do. If a user starts asking what file descriptors have been allocated to what ports, he has to take responsibility for managing this information.
3.2.5 Port-mapping machinery
The procedures provided in this section are almost never needed. You may safely skim or completely skip this section on a first reading.
Here are the routines for manipulating ports in scsh. The important points to remember are:
A file port is associated with an open file, not a particular file descriptor.
The association between a file port and a particular file descriptor is never changed except when the file descriptor is explicitly closed. ``Closing'' includes being used as the target of a dup2, so the set of procedures below that close their targets are close, two-argument dup, and move->fdes. If the target file descriptor of one of these routines has an allocated port, the port will be shifted to another freshly-allocated file descriptor, and marked as unrevealed, thus preserving the port but freeing its old file descriptor.
These rules are what is necessary to ``make things work out'' with no surprises in the general case.
These increment the port's revealed count.
Return the port's revealed count if positive, otherwise #f.
Decrement the port's revealed count.
Calls consumer on a file descriptor; takes care of revealed bookkeeping. If fd/port is a file descriptor, this is just (consumer fd/port). If fd/port is a port, calls consumer on its underlying file descriptor. While consumer is running, the port's revealed count is incremented.When call/fdes is called with port argument, you are not allowed to throw into consumer with a stored continuation, as that would violate the revealed-count bookkeeping.
Maps fd-->fd and port-->port.If fd/port is a file-descriptor not equal to target-fd, dup it to target-fd and close it. Returns target-fd.
If fd/port is a port, it is shifted to target-fd, by duping its underlying file-descriptor if necessary. Fd/port's original file descriptor is closed (if it was different from target-fd). Returns the port. This operation resets fd/port's revealed count to 1.
In all cases when fd/port is actually shifted, if there is a port already using target-fd, it is first relocated to some other file descriptor.
3.2.6 Unix I/O
These procedures provide the functionality of C's dup() and dup2(). The different routines return different types of values: dup->inport, dup->outport, and dup->fdes return input ports, output ports, and integer file descriptors, respectively. dup's return value depends on on the type of fd/port -- it maps fd-->fd and port-->port.These procedures use the Unix dup() syscall to replicate the file descriptor or file port fd/port. If a newfd file descriptor is given, it is used as the target of the dup operation, i.e., the operation is a dup2(). In this case, procedures that return a port (such as dup->inport) will return one with the revealed count set to one. For example, (dup (current-input-port) 5) produces a new port with underlying file descriptor 5, whose revealed count is 1. If newfd is not specified, then the operating system chooses the file descriptor, and any returned port is marked as unrevealed.
If the newfd target is given, and some port is already using that file descriptor, the port is first quietly shifted (with another dup) to some other file descriptor (zeroing its revealed count).
Since Scheme doesn't provide read/write ports, dup->inport and dup->outport can be useful for getting an output version of an input port, or vice versa. For example, if p is an input port open on a tty, and we would like to do output to that tty, we can simply use (dup->outport p) to produce an equivalent output port for the tty. Be sure to open the file with the open/read+write flag for this.
Reposition the I/O cursor for a file descriptor or port. whence is one of {seek/set, seek/delta, seek/end}, and defaults to seek/set. If seek/set, then offset is an absolute index into the file; if seek/delta, then offset is a relative offset from the current I/O cursor; if seek/end, then offset is a relative offset from the end of file. The fd/port argument may be a port or an integer file descriptor. Not all such values are seekable; this is dependent on the OS implementation. The return value is the resulting position of the I/O cursor in the I/O stream.Oops: The current implementation doesn't handle offset arguments that are not immediate integers (i.e., representable in 30 bits).Oops: The current implementation doesn't handle buffered ports.
Returns the position of the I/O cursor in the the I/O stream. Not all file descriptors or ports support cursor-reporting; this is dependent on the OS implementation.
Perms defaults to #o666. Flags is an integer bitmask, composed by or'ing together constants listed in table 1 (page 4). You must use exactly one of the open/read, open/write, or open/read+write flags. The returned port is an input port if the flags permit it, otherwise an output port. R5RS/Scheme 48/scsh do not have input/output ports, so it's one or the other. This should be fixed. (You can hack simultaneous I/O on a file by opening it r/w, taking the result input port, and duping it to an output port with dup->outport.)
These are equivalent to open-file, after first setting the read/write bits of the flags argument to open/read or open/write, respectively. Flags defaults to zero for open-input-file, and(bitwise-ior open/create open/truncate)for open-output-file. These defaults make the procedures backwards-compatible with their unary R5RS definitions.
Returns a file descriptor.
These procedures allow reading and writing of an open file's flags. The only such flag defined by POSIX is fdflags/close-on-exec; your Unix implementation may provide others.These procedures should not be particularly useful to the programmer, as the scsh runtime already provides automatic control of the close-on-exec property. Unrevealed ports always have their file descriptors marked close-on-exec, as they can be closed when the scsh process execs a new program. Whenever the user reveals or unreveals a port's file descriptor, the runtime automatically sets or clears the flag for the programmer. Programmers that manipulate this flag should be aware of these extra, automatic operations.
These procedures allow reading and writing of an open file's status flags (table 1).
Allowed operations Status flag Open+Get+Set These flags can be used in open-file, fdes-status, and set-fdes-status calls.
open/append open/non-blocking open/async (Non-POSIX) open/fsync (Non-POSIX) Open+Get These flags can be used in open-file and fdes-status calls, but are ignored by set-fdes-status.
open/read open/write open/read+write open/access-mask Open These flags are only relevant in open-file calls; they are ignored by fdes-status and set-fdes-status calls.
open/create open/exclusive open/no-control-tty open/truncate Table 1: Status flags for open-file, fdes-status and set-fdes-status. Only POSIX flags are guaranteed to be present; your operating system may define others. The open/access-mask value is not an actual flag, but a bit mask used to select the field for the open/read, open/write and open/read+write bits.
Note that this file-descriptor state is shared between file descriptors created by dup -- if you create port b by applying dup to port a, and change b's status flags, you will also have changed a's status flags.
Returns two ports, the read and write end-points of a Unix pipe.
These calls read exactly as much data as you requested, unless there is not enough data (eof). read-string! reads the data into string str at the indices in the half-open interval [start,end); the default interval is the whole string: start = 0 and end = (string-length string). They will persistently retry on partial reads and when interrupted until (1) error, (2) eof, or (3) the input request is completely satisfied. Partial reads can occur when reading from an intermittent source, such as a pipe or tty.read-string returns the string read; read-string! returns the number of characters read. They both return false at eof. A request to read zero bytes returns immediately, with no eof check.
The values of start and end must specify a well-defined interval in str, i.e., 0 < start < end < (string-length str).
Any partially-read data is included in the error exception packet. Error returns on non-blocking input are considered an error.
These are atomic best-effort/forward-progress calls. Best effort: they may read less than you request if there is a lesser amount of data immediately available (e.g., because you are reading from a pipe or a tty). Forward progress: if no data is immediately available (e.g., empty pipe), they will block. Therefore, if you request an n>0 byte read, while you may not get everything you asked for, you will always get something (barring eof).There is one case in which the forward-progress guarantee is cancelled: when the programmer explicitly sets the port to non-blocking I/O. In this case, if no data is immediately available, the procedure will not block, but will immediately return a zero-byte read.
read-string/partial reads the data into a freshly allocated string, which it returns as its value. read-string!/partial reads the data into string str at the indices in the half-open interval [start,end); the default interval is the whole string: start = 0 and end = (string-length string). The values of start and end must specify a well-defined interval in str, i.e., 0 < start < end < (string-length str). It returns the number of bytes read.
A request to read zero bytes returns immediatedly, with no eof check.
In sum, there are only three ways you can get a zero-byte read: (1) you request one, (2) you turn on non-blocking I/O, or (3) you try to read at eof.
These are the routines to use for non-blocking input. They are also useful when you wish to efficiently process data in large blocks, and your algorithm is insensitive to the block size of any particular read operation.
The select procedure allows a process to block and wait for events on multiple I/O channels. The rvec and evec arguments are vectors of input ports and integer file descriptors; wvec is a vector of output ports and integer file descriptors. The procedure returns three vectors whose elements are subsets of the corresponding arguments. Every element of rvec' is ready for input; every element of wvec' is ready for output; every element of evec' has an exceptional condition pending.The select call will block until at least one of the I/O channels passed to it is ready for operation. For an input port this means that it either has data sitting its buffer or that the underlying file descriptor has data waiting. For an output port this means that it either has space available in the associated buffer or that the underlying file descriptor can accept output. For file descriptors, no buffers are checked, even if they have associated ports.
The timeout value can be used to force the call to time-out after a given number of seconds. It defaults to the special value #f, meaning wait indefinitely. A zero value can be used to poll the I/O channels.
If an I/O channel appears more than once in a given vector -- perhaps occuring once as a Scheme port, and once as the port's underlying integer file descriptor -- only one of these two references may appear in the returned vector. Buffered I/O ports are handled specially -- if an input port's buffer is not empty, or an output port's buffer is not yet full, then these ports are immediately considered eligible for I/O without using the actual, primitive select system call to check the underlying file descriptor. This works pretty well for buffered input ports, but is a little problematic for buffered output ports.
The select! procedure is similar, but indicates the subset of active I/O channels by side-effecting the argument vectors. Non-active I/O channels in the argument vectors are overwritten with #f values. The call returns the number of active elements remaining in each vector. As a convenience, the vectors passed in to select! are allowed to contain #f values as well as integers and ports.
Remark: Select and select! do not call their POSIX counterparts directly -- there is a POSIX select sitting at the very heart of the Scheme 48/scsh I/O system, so all multiplexed I/O is really select-based. Therefore, you cannot expect a performance increase from writing a single-threaded program using select and select! instead of writing a multi-threaded program where each thread handles one I/O connection.The moral of this story is that select and select! make sense in only two situations: legacy code written for an older version of scsh, and programs which make inherent use of select/select! which do not benefit from multiple threads. Examples are network clients that send requests to multiple alternate servers and discard all but one of them.
In any case, the select-ports and select-port-channels procedures described below are usually a preferable alternative to select/select!: they are much simpler to use, and also have a slightly more efficient implementation.
The select-ports call will block until at least one of the ports passed to it is ready for operation or until the timeout has expired. For an input port this means that it either has data sitting its buffer or that the underlying file descriptor has data waiting. For an output port this means that it either has space available in the associated buffer or that the underlying file descriptor can accept output.The timeout value can be used to force the call to time out after a given number of seconds. A value of #f means to wait indefinitely. A zero value can be used to poll the ports.
Select-ports returns a list of the ports ready for operation. Note that this list may be empty if the timeout expired before any ports became ready.
Select-port-channels is like select-ports, except that it only looks at the operating system objects the ports refer to, ignoring any buffering performed by the ports.
Remark: Select-port-channels should be used with care: for example, if an input port has data in the buffer but no data available on the underlying file descriptor, select-port-channels will block, even though a read operation on the port would be able to complete without blocking.Select-port-channels is intended for situations where the program is not checking for available data, but rather for waiting until a port has established a connection -- for example, to a network port.
This procedure writes all the data requested. If the procedure cannot perform the write with a single kernel call (due to interrupts or partial writes), it will perform multiple write operations until all the data is written or an error has occurred. A non-blocking I/O error is considered an error. (Error exception packets for this syscall include the amount of data partially transferred before the error occurred.)The data written are the characters of string in the half-open interval [start,end). The default interval is the whole string: start = 0 and end = (string-length string). The values of start and end must specify a well-defined interval in str, i.e., 0 < start < end < (string-length str). A zero-byte write returns immediately, with no error.
Output to buffered ports: write-string's efforts end as soon as all the data has been placed in the output buffer. Errors and true output may not happen until a later time, of course.
This routine is the atomic best-effort/forward-progress analog to write-string. It returns the number of bytes written, which may be less than you asked for. Partial writes can occur when (1) we write off the physical end of the media, (2) the write is interrrupted, or (3) the file descriptor is set for non-blocking I/O.If the file descriptor is not set up for non-blocking I/O, then a successful return from these procedures makes a forward progress guarantee -- that is, a partial write took place of at least one byte:
If we are at the end of physical media, and no write takes place, an error exception is raised. So a return implies we wrote something.
If the call is interrupted after a partial transfer, it returns immediately. But if the call is interrupted before any data transfer, then the write is retried.
If we request a zero-byte write, then the call immediately returns 0. If the file descriptor is set for non-blocking I/O, then the call may return 0 if it was unable to immediately write anything (e.g., full pipe). Barring these two cases, a write either returns nwritten > 0, or raises an error exception.
Non-blocking I/O is only available on file descriptors and unbuffered ports. Doing non-blocking I/O to a buffered port is not well-defined, and is an error (the problem is the subsequent flush operation).
Oops: write-string/partial is currently not implemented. Consider using threads to achive the same functionality.
3.2.7 Buffered I/O
Scheme 48 ports use buffered I/O -- data is transferred to or from the OS in blocks. Scsh provides control of this mechanism: the programmer may force saved-up output data to be transferred to the OS when he chooses, and may also choose which I/O buffering policy to employ for a given port (or turn buffering off completely).
It can be useful to turn I/O buffering off in some cases, for example when an I/O stream is to be shared by multiple subprocesses. For this reason, scsh allocates an unbuffered port for file descriptor 0 at start-up time. Because shells frequently share stdin with subprocesses, if the shell does buffered reads, it might ``steal'' input intended for a subprocess. For this reason, all shells, including sh, csh, and scsh, read stdin unbuffered. Applications that can tolerate buffered input on stdin can reset (current-input-port) to block buffering for higher performance.
{Note So support peek-char a Scheme implementation has to maintain a buffer for all input ports. In scsh, for ``unbuffered'' input ports the buffer size is one. As you cannot request less then one character there is no unrequested reading so this can still be called ``unbuffered input''.}
This procedure allows the programmer to assign a particular I/O buffering policy to a port, and to choose the size of the associated buffer. It may only be used on new ports, i.e., before I/O is performed on the port. There are three buffering policies that may be chosen:The line buffering policy flushes output whenever a newline is output; whenever the buffer is full; or whenever an input is read from stdin. Line buffering is the default for ports open on terminal devices.
bufpol/block General block buffering (general default) bufpol/line Line buffering (tty default) bufpol/none Direct I/O -- no buffering4 Oops: The current implementation doesn't support bufpol/line.The size argument requests an I/O buffer of size bytes. For output ports, size must be non-negative, for input ports size must be positve. If not given, a reasonable default is used. For output ports, if given and zero, buffering is turned off (i.e., size = 0 for any policy is equivalent to policy = bufpol/none). For input ports, setting the size to one corresponds to unbuffered input as defined above. If given, size must be zero respectively one for bufpol/none.
This procedure does nothing when applied to an integer file descriptor or unbuffered port. It flushes buffered output when applied to a buffered port, and raises a write-error exception on error. Returns no value.
This procedure flushes all open output ports with buffered data.
3.2.8 File locking
Scsh provides POSIX advisory file locking. Advisory locks are locks that can be checked by user code, but do not affect other I/O operations. For example, if a process has an exclusive lock on a region of a file, other processes will not be able to obtain locks on that region of the file, but they will still be able to read and write the file with no hindrance. Using advisory locks requires cooperation amongst the agents accessing the shared resource.
Remark: Unfortunately, POSIX file locks are associated with actual files, not with associated open file descriptors. Once a process locks a file, using some file descriptor fd, the next time any file descriptor referencing that file is closed, all associated locks are released. This severely limits the utility of POSIX advisory file locks, and we'd recommend caution when using them. It is not without reason that the FreeBSD man pages refer to POSIX file locking as ``completely stupid.''Scsh moves Scheme ports from file descriptor to file descriptor with dup() and close() as required by the runtime, so it is impossible to keep file locks open across one of these shifts. Hence we can only offer POSIX advisory file locking directly on raw integer file descriptors; regrettably, there are no facilities for locking Scheme ports.
Note that once a Scheme port is revealed in scsh, the runtime will not shift the port around with dup() and close(). This means the file-locking procedures can then be applied to the port's associated file descriptor.
POSIX allows the user to lock a region of a file with either an exclusive or shared lock. Locked regions are described by the lock-region record:
(define-record lock-region
exclusive?
start
len
whence
proc)
The exclusive? field is true if the lock is exclusive; false if it is shared.
The whence field is one of the values from the seek call: seek/set, seek/delta, or seek/end, and determines the interpretation of the start field:
If seek/set, the start value is simply an absolute index into the file.
If seek/delta, the start value is an offset from the file descriptor's current position in the file.
If seek/end, the start value is an offset from the end of the file.
The region of the file being locked is given by the start and len fields; if len is zero, it means ``infinity,'' that is, the region extends from the starting point through the end of the file, even as the file is extended by subsequent write operations.
The proc field gives the process object for the process holding the region lock, when relevant (see get-lock-region below).
This procedure makes a lock-region record. The whence field defaults to seek/set.
These procedures lock a region of the file referenced by file descriptor fdes. The lock-region procedure blocks until the lock is granted; the non-blocking variant returns a boolean indicating whether or not the lock was granted. To take an exclusive (write) lock, you must have the file descriptor open with write access; to take a shared (read) lock, you must have the file descriptor open with read access.
Return the first lock region on fdes that would conflict with lock region lock. If there is no such lock region, return false. This procedure fills out the proc field of the returned lock region, and is the only procedure that has anything to do with this field. (See section 3.4.1 for a description of process objects.) Note that if you apply this procedure to a file system that is shared across multiple operating systems (i.e., an NFS file system), the proc field may be ambiguous. We note, again, that POSIX advisory file locking is not a terribly useful or well-designed facility.
Release a lock from a file.
This procedure obtains the requested lock, and then calls (thunk). When thunk returns, the lock is released. A non-local exit (e.g., throwing to a saved continuation or raising an exception) also causes the lock to be released.After a normal return from thunk, its return values are returned by with-region-lock*. The with-region-lock special form is equivalent syntactic sugar.
3.3 File system
Besides the following procedures, which allow access to the computer's file system, scsh also provides a set of procedures which manipulate file names. These string-processing procedures are documented in section 5.1.
These procedures create objects of various kinds in the file system.
The override? argument controls the action if there is already an object in the file system with the new name:
#f signal an error (default) 'query prompt the user other delete the old object (with delete-file or delete-directory, as appropriate) before creating the new object.
Perms defaults to #o777 (but is masked by the current umask).
Remark: Currently, if you try to create a hard or symbolic link from a file to itself, you will error out with override? false, and simply delete your file with override? true. Catching this will require some sort of true-name procedure, which I currently do not have.
These procedures delete objects from the file system. The delete-filesys-object procedure will delete an object of any type from the file system: files, (empty) directories, symlinks, fifos, etc..If the object being deleted doesn't exist, delete-directory and delete-file raise an error, while delete-filesys-object simply returns.
Return the filename referenced by symbolic link fname.
If you override an existing object, then old-fname and new-fname must type-match -- either both directories, or both non-directories. This is required by the semantics of Unix rename().
Remark: There is an unfortunate atomicity problem with the rename-file procedure: if you specify no-override, but create file new-fname sometime between rename-file's existence check and the actual rename operation, your file will be clobbered with old-fname. There is no way to fix this problem, given the semantics of Unix rename(); at least it is highly unlikely to occur in practice.
These procedures set the permission bits, owner id, and group id of a file, respectively. The file can be specified by giving the file name, or either an integer file descriptor or a port open on the file. Setting file user ownership usually requires root privileges.
This procedure sets the access and modified times for the file fname to the supplied values (see section 3.10 for the scsh representation of time). If neither time argument is supplied, they are both taken to be the current time. You must provide both times or neither. If the procedure completes successfully, the file's time of last status-change (ctime) is set to the current time.
Calling sync-file causes Unix to update the disk data structures for a given file. If fd/port is a port, any buffered data it may have is first flushed. Calling sync-file-system synchronises the kernel's entire file system with the disk.These procedures are not POSIX. Interestingly enough, sync-file-system doesn't actually do what it is claimed to do. We just threw it in for humor value. See the sync(2) man page for Unix enlightenment.
The specified file is truncated to len bytes in length.
The file-info procedure returns a record structure containing everything there is to know about a file. If the chase? flag is true (the default), then the procedure chases symlinks and reports on the files to which they refer. If chase? is false, then the procedure checks the actual file itself, even if it's a symlink. The chase? flag is ignored if the file argument is a file descriptor or port.The value returned is a file-info record, defined to have the following structure:
The uid field of a file-info record is accessed with the procedure
(define-record file-info
type ; {block-special, char-special, directory,
; fifo, regular, socket, symlink}
device ; Device file resides on.
inode ; File's inode.
mode ; File's mode bits: permissions, setuid, setgid
nlinks ; Number of hard links to this file.
uid ; Owner of file.
gid ; File's group id.
size ; Size of file, in bytes.
atime ; Time of last access.
mtime ; Time of last mod.
ctime) ; Time of last status change.(file-info:uid x)and similarly for the other fields. The type field is a symbol; all other fields are integers. A file-info record is discriminated with the file-info? predicate.The following procedures all return selected information about a file; they are built on top of file-info, and are called with the same arguments that are passed to it.
Example:
;; All my files in /usr/tmp:
(filter (lambda (f) (= (file-owner f) (user-uid)))
(directory-files "/usr/tmp")))
Remark: file-info was named file-attributes in releases of scsh prior to release 0.4. We changed the name to file-info for consistency with the other information-retrieval procedures in scsh: user-info, group-info, host-info, network-info , service-info, and protocol-info.The file-attributes binding is still supported in the current release of scsh, but is deprecated, and may go away in a future release.
These procedures are file-type predicates that test the type of a given file. They are applied to the same arguments to which file-info is applied; the sole exception is file-symlink?, which does not take the optional chase? second argument.For example,
(file-directory? "/usr/dalbertz") ===> #t
There are variants of these procedures which work directly on file-info records:
The following set of procedures are a convenient means to work on the permission bits of a file:
Returns:A file is considered writeable if either (1) it exists and is writeable or (2) it doesn't exist and the directory is writeable. Since symlink permission bits are ignored by the filesystem, these calls do not take a chase? flag.
Value meaning #f Access permitted 'search-denied
Can't stat -- a protected directory is blocking access. 'permission Permission denied. 'no-directory Some directory doesn't exist. 'nonexistent File doesn't exist. Note that these procedures use the process' effective user and group ids for permission checking. POSIX defines an access() function that uses the process' real uid and gids. This is handy for setuid programs that would like to find out if the actual user has specific rights; scsh ought to provide this functionality (but doesn't at the current time).
There are several problems with these procedures. First, there's an atomicity issue. In between checking permissions for a file and then trying an operation on the file, another process could change the permissions, so a return value from these functions guarantees nothing. Second, the code special-cases permission checking when the uid is root -- if the file exists, root is assumed to have the requested permission. However, not even root can write a file that is on a read-only file system, such as a CD ROM. In this case, file-not-writable? will lie, saying that root has write access, when in fact the opening the file for write access will fail. Finally, write permission confounds write access and create access. These should be disentangled.
Some of these problems could be avoided if POSIX had a real-uid variant of the access() call we could use, but the atomicity issue is still a problem. In the final analysis, the only way to find out if you have the right to perform an operation on a file is to try and open it for the desired operation. These permission-checking functions are mostly intended for script-writing, where loose guarantees are tolerated.
These procedures are the logical negation of the preceding file-not-...? procedures. Refer to them for a discussion of their problems and limitations.
There are variants which work directly on file-info records.
Returns:
#f Exists. #t Doesn't exist. 'search-denied Some protected directory is blocking the search.
This is simply (not (file-not-exists? fname [chase?]))
Return the list of files in directory dir, which defaults to the current working directory. The dotfiles? flag (default #f) causes dot files to be included in the list. Regardless of the value of dotfiles?, the two files . and .. are never returned.The directory dir is not prepended to each file name in the result list. That is,
(directory-files "/etc")returns("chown" "exports" "fstab" ...)not("/etc/chown" "/etc/exports" "/etc/fstab" ...)To use the files in returned list, the programmer can either manually prepend the directory:(map (lambda (f) (string-append dir "/" f)) files)or cd to the directory before using the file names:or use the glob procedure, defined below.
(with-cwd dir
(for-each delete-file (directory-files)))A directory list can be generated by (run/strings (ls)), but this is unreliable, as filenames with whitespace in their names will be split into separate entries. Using directory-files is reliable.
These functions implement a direct interface to the opendir()/ readdir()/ closedir() family of functions for processing directory streams. (open-directory-stream dir) creates a stream of files in the directory dir. (read-directory-stream directory-stream) returns the next file in the stream or #fif no such file exists. Finally, (close-directory-stream directory-stream) closes the stream.
Glob each pattern against the filesystem and return the sorted list. Duplicates are not removed. Patterns matching nothing are not included literally.5 C shell{a,b,c}patterns are expanded. Backslash quotes characters, turning off the special meaning of{,}, *,[,], and?.Note that the rules of backslash for Scheme strings and glob patterns work together to require four backslashes in a row to specify a single literal backslash. Fortunately, it is very rare that a backslash occurs in a Unix file name.
A glob subpattern will not match against dot files unless the first character of the subpattern is a literal ``.''. Further, a dot subpattern will not match the files . or .. unless it is a constant pattern, as in (glob "../*/*.c"). So a directory's dot files can be reliably generated with the simple glob pattern ".*".
Some examples:
If the first character of the pattern (after expanding braces) is a slash, the search begins at root; otherwise, the search begins in the current working directory.(glob "*.c" "*.h") ;; All the C and #include files in my directory. (glob "*.c" "*/*.c") ;; All the C files in this directory and ;; its immediate subdirectories. (glob "lexer/*.c" "parser/*.c") (glob "{lexer,parser}/*.c") ;; All the C files in the lexer and parser dirs. (glob "\\{lexer,parser\\}/*.c") ;; All the C files in the strange ;; directory "{lexer,parser}". (glob "*\\*") ;; All the files ending in "*", e.g. ;; ("foo*" "bar*") (glob "*lexer*") ("mylexer.c" "lexer1.notes") ;; All files containing the string "lexer". (glob "lexer") ;; Either ("lexer") or ().
If the last character of the pattern (after expanding braces) is a slash, then the result matches must be directories, e.g.,
(glob "/usr/man/man?/") ==>
("/usr/man/man1/" "/usr/man/man2/" ...)Globbing can sometimes be useful when we need a list of a directory's files where each element in the list includes the pathname for the file. Compare:
(directory-files "../include") ==>
("cig.h" "decls.h" ...)
(glob "../include/*") ==>
("../include/cig.h" "../include/decls.h" ...)
Returns a constant glob pattern that exactly matches str. All wild-card characters in str are quoted with a backslash.
(glob-quote "Any *.c files?")
==> "Any \*.c files\?"
{Note This procedure is deprecated, and will probably either go away or be substantially altered in a future release. New code should not call this procedure. The problem is that it relies upon Posix-notation regular expressions; the rest of scsh has been converted over to the new SRE notation.}file-match provides a more powerful file-matching service, at the expense of a less convenient notation. It is intermediate in power between most shell matching machinery and recursive find(1).
Each pattern is a regexp. The procedure searches from root, matching the first-level files against pattern pat1, the second-level files against pat2, and so forth. The list of files matching the whole path pattern is returned, in sorted order. The matcher uses Spencer's regular expression package.
The files . and .. are never matched. Other dot files are only matched if the dot-files? argument is #t.
A given pati pattern is matched as a regexp, so it is not forced to match the entire file name. E.g., pattern "t" matches any file containing a ``t'' in its name, while pattern
"^t$"matches only a file whose entire name is ``t''.The pati patterns can be more general than stated above.
A single pattern can specify multiple levels of the path by embedding / characters within the pattern. For example, the pattern "a/b/c" gives a match equivalent to the list of patterns "a" "b" "c".
A pati pattern can be a procedure, which is used as a match predicate. It will be repeatedly called with a candidate file-name to test. The file-name will be the entire path accumulated. If the procedure raises an error condition, file-match will catch the error and treat it as a failed match. This keeps file-match from being blown out of the water by applying tests to dangling symlinks and other similar situations.
Some examples:
(file-match "/usr/lib" #f "m$" "^tab") ==>
("/usr/lib/term/tab300" "/usr/lib/term/tab300-12" ...)
[0]
(file-match "." #f "^lex|parse|codegen$" "\\.c$") ==>
("lex/lex.c" "lex/lexinit.c" "lex/test.c"
"parse/actions.c" "parse/error.c" parse/test.c"
"codegen/io.c" "codegen/walk.c")
[0]
(file-match "." #f "^lex|parse|codegen$/\\.c$")
;; The same.
[0]
(file-match "." #f file-directory?)
;; Return all subdirs of the current directory.
[0]
(file-match "/" #f file-directory?) ==>
("/bin" "/dev" "/etc" "/tmp" "/usr")
;; All subdirs of root.
[0]
(file-match "." #f "\\.c")
;; All the C files in my directory.
[0]
(define (ext extension)
(lambda (fn) (string-suffix? fn extension)))
[0]
(define (true . x) #t)
[0]
(file-match "." #f "./\\.c")
(file-match "." #f "" "\\.c")
(file-match "." #f true "\\.c")
(file-match "." #f true (ext "c"))
;; All the C files of all my immediate subdirs.
[0]
(file-match "." #f "lexer") ==>
("mylexer.c" "lexer.notes")
;; Compare with (glob "lexer"), above.Note that when root is the current working directory ("."), when it is converted to directory form, it becomes "", and doesn't show up in the result file-names.
It is regrettable that the regexp wild card char, ``.'', is such an important file name literal, as dot-file prefix and extension delimiter.
Create-temp-file creates a new temporary file and return its name. The optional argument specifies the filename prefix to use, and defaults to the value of "$TMPDIR/pid" if $TMPDIR is set and to "/var/tmp/pid" otherwise, where pid is the current process' id. The procedure generates a sequence of filenames that have prefix as a common prefix, looking for a filename that doesn't already exist in the file system. When it finds one, it creates it, with permission #o600 and returns the filename. (The file permission can be changed to a more permissive permission with set-file-mode after being created).This file is guaranteed to be brand new. No other process will have it open. This procedure does not simply return a filename that is very likely to be unused. It returns a filename that definitely did not exist at the moment create-temp-file created it.
It is not necessary for the process' pid to be a part of the filename for the uniqueness guarantees to hold. The pid component of the default prefix simply serves to scatter the name searches into sparse regions, so that collisions are less likely to occur. This speeds things up, but does not affect correctness.
Security note: doing I/O to files created this way in /var/tmp/ is not necessarily secure. General users have write access to /var/tmp/, so even if an attacker cannot access the new temp file, he can delete it and replace it with one of his own. A subsequent open of this filename will then give you his file, to which he has access rights. There are several ways to defeat this attack,
Use temp-file-iterate, below, to return the file descriptor allocated when the file is opened. This will work if the file only needs to be opened once.
If the file needs to be opened twice or more, create it in a protected directory, e.g.,
$HOME.Ensure that /var/tmp has its sticky bit set. This requires system administrator privileges.
The actual default prefix used is controlled by the dynamic variable *temp-file-template*, and can be overridden for increased security. See temp-file-iterate.
This procedure can be used to perform certain atomic transactions on the file system involving filenames. Some examples:
Linking a file to a fresh backup temp name.
Creating and opening an unused, secure temp file.
Creating an unused temporary directory.
This procedure uses template to generate a series of trial file names. Template is a format control string, and defaults to
"$TMPDIR/pid.~a"if $TMPDIR is set and"/var/tmp/pid.~a"otherwise where pid is the current process' process id. File names are generated by calling format to instantiate the template's~afield with a varying string.Maker is a procedure which is serially called on each file name generated. It must return at least one value; it may return multiple values. If the first return value is #f or if maker raises the errno/exist errno exception, temp-file-iterate will loop, generating a new file name and calling maker again. If the first return value is true, the loop is terminated, returning whatever value(s) maker returned.
After a number of unsuccessful trials, temp-file-iterate may give up and signal an error.
Thus, if we ignore its optional prefix argument, create-temp-file could be defined as:
(define (create-temp-file)
(let ((flags (bitwise-ior open/create open/exclusive)))
(temp-file-iterate
(lambda (f)
(close (open-output-file f flags #o600))
f))))To rename a file to a temporary name:
Recall that scsh reports syscall failure by raising an error exception, not by returning an error code. This is critical to to this example -- the programmer can assume that if the temp-file-iterate call returns, it returns successully. So the following delete-file call can be reliably invoked, safe in the knowledge that the backup link has definitely been established.
(temp-file-iterate (lambda (backup)
(create-hard-link old-file backup)
backup)
".#temp.~a") ; Keep link in cwd.
(delete-file old-file)To create a unique temporary directory:
Similar operations can be used to generate unique symlinks and fifos, or to return values other than the new filename (e.g., an open file descriptor or port).
(temp-file-iterate (lambda (dir) (create-directory dir) dir)
"/var/tmp/tempdir.~a")The default template is in fact taken from the value of the dynamic variable *temp-file-template*, which itself defaults to "$TMPDIR/pid.~a" if $TMPDIR is set and "/usr/tmp/pid.~a" otherwise, where pid is the scsh process' pid. For increased security, a user may wish to change the template to use a directory not allowing world write access (e.g., his home directory).
This procedure can be used to provide an interprocess communications channel with arbitrary-sized buffering. It returns two values, an input port and an output port, both open on a new temp file. The temp file itself is deleted from the Unix file tree before temp-file-channel returns, so the file is essentially unnamed, and its disk storage is reclaimed as soon as the two ports are closed.Temp-file-channel is analogous to port-pipe with two exceptions:
If the writer process gets ahead of the reader process, it will not hang waiting for some small pipe buffer to drain. It will simply buffer the data on disk. This is good.
If the reader process gets ahead of the writer process, it will also not hang waiting for data from the writer process. It will simply see and report an end of file. This is bad.
In order to ensure that an end-of-file returned to the reader is legitimate, the reader and writer must serialise their I/O. The simplest way to do this is for the reader to delay doing input until the writer has completely finished doing output, or exited.
3.4 Processes
The .../env variants take an environment specified as a string-->string alist. An environment of #t is taken to mean the current process' environment (i.e., the value of the external char **environ).
[Rationale: #f is a more convenient marker for the current environment than #t, but would cause an ambiguity on Schemes that identify #f and ().]
The path-searching variants search the directories in the list exec-path-list for the program. A path-search is not performed if the program name contains a slash character -- it is used directly. So a program with a name like "bin/prog" always executes the program bin/prog in the current working directory. See
$pathandexec-path-list, below.Note that there is no analog to the C function execv(). To get the effect just do
(apply exec prog arglist)All of these procedures flush buffered output and close unrevealed ports before executing the new binary. To avoid flushing buffered output, see
%execbelow.Note that the C exec() procedure allows the zeroth element of the argument vector to be different from the file being executed, e.g.
The scsh exec, exec-path, exec/env, and exec-path/env procedures do not give this functionality -- element 0 of the arg vector is always identical to the prog argument. In the rare case the user wishes to differentiate these two items, he can use the low-levelchar *argv[] = {"-", "-f", 0}; exec("/bin/csh", argv, envp);
%execandexec-path-searchprocedures. These procedures never return under any circumstances. As with any other system call, if there is an error, they raise an exception.
The %exec procedure is the low-level interface to the system call. The arglist parameter is a list of arguments; env is either a string-->string alist or #t. The new program's argv[0] will be taken from (car arglist), not from prog. An environment of #t means the current process' environment.%execdoes not flush buffered output (see flush-all-ports).All exec procedures, including
%exec, coerce the prog and arg values to strings using the usual conversion rules: numbers are converted to decimal numerals, and symbols converted to their print-names.exec-path-search searches the directories of pathlist looking for an occurrence of file fname. If no executable file is found, it returns #f. If fname contains a slash character, the path search is short-circuited, but the procedure still checks to ensure that the file exists and is executable -- if not, it still returns #f. Users of this procedure should be aware that it invites a potential race condition: between checking the file with exec-path-search and executing it with %exec, the file's status might change. The only atomic way to do the search is to loop over the candidate file names, exec'ing each one and looping when the exec operation fails.
See $path and exec-path-list, below.
These procedures terminate the current process with a given exit status.
The default exit status is 0.
The low-level %exit procedure immediately terminates the process
without flushing buffered output.
call-terminally calls its thunk. When the thunk returns, the process exits. Although call-terminally could be implemented as(lambda (thunk) (thunk) (exit 0))an implementation can take advantage of the fact that this procedure never returns. For example, the runtime can start with a fresh stack and also start with a fresh dynamic environment, where shadowed bindings are discarded. This can allow the old stack and dynamic environment to be collected (assuming this data is not reachable through some live continuation).
Suspend the current process with a SIGSTOP signal.
fork with no arguments or #f instead of a thunk is like C fork(). In the parent process, it returns the child's process object (see below for more information on process objects). In the child process, it returns #f.fork with an argument only returns in the parent process, returning the child's process object. The child process calls thunk and then exits.
fork flushes buffered output before forking, and sets the child process to non-interactive.
%forkdoes not perform this bookkeeping; it simply forks.The optional boolean argument continue-threads? specifies whether the currently active threads continue to run in the child or not. The default is #f.
Like fork and %fork, but the parent and child communicate via a pipe connecting the parent's stdin to the child's stdout. These procedures side-effect the parent by changing his stdin.In effect, fork/pipe splices a process into the data stream immediately upstream of the current process. This is the basic function for creating pipelines. Long pipelines are built by performing a sequence of fork/pipe calls. For example, to create a background two-process pipe a | b, we write:
which returns the process object for b's process.
(fork (lambda () (fork/pipe a) (b)))To create a background three-process pipe a | b | c, we write:
which returns the process object for c's process.
(fork (lambda () (fork/pipe a)
(fork/pipe b)
(c)))Note that these procedures affect file descriptors, not ports. That is, the pipe is allocated connecting the child's file descriptor 1 to the parent's file descriptor 0. Any previous Scheme port built over these affected file descriptors is shifted to a new, unused file descriptor with dup before allocating the I/O pipe. This means, for example, that the ports bound to (current-input-port) and (current-output-port) in either process are not affected -- they still refer to the same I/O sources and sinks as before. Remember the simple scsh rule: Scheme ports are bound to I/O sources and sinks, not particular file descriptors.
If the child process wishes to rebind the current output port to the pipe on file descriptor 1, it can do this using with-current-output-port or a related form. Similarly, if the parent wishes to change the current input port to the pipe on file descriptor 0, it can do this using set-current-input-port! or a related form. Here is an example showing how to set up the I/O ports on both sides of the pipe:
None of this is necessary when the I/O is performed by an exec'd program in the child or parent process, only when the pipe will be referenced by Scheme code through one of the default current I/O ports.
(fork/pipe (lambda ()
(with-current-output-port (fdes->outport 1)
(display "Hello, world.\n"))))
(set-current-input-port! (fdes->inport 0))
(read-line) ; Read the string output by the child.