Funes El Memorioso
Funes El Memorioso
A Scheme Shell
Olin Shivers
[email protected]
1 Introduction 1
2 Unix shells 2
3 Process notation 6
3.1 Extended process forms and i/o redirections : : : : : : : : : 6
3.2 Process forms : : : : : : : : : : : : : : : : : : : : : : : : : : : 8
3.3 Using extended process forms in Scheme : : : : : : : : : : : 8
3.4 Procedures and special forms : : : : : : : : : : : : : : : : : : 11
3.5 Interfacing process output to Scheme : : : : : : : : : : : : : 12
4 System calls 14
6 I/O 16
7 Lexical issues 18
8 Implementation 19
9 Size 20
12 Future work 29
12.1 Command language features : : : : : : : : : : : : : : : : : : 30
12.2 Little languages : : : : : : : : : : : : : : : : : : : : : : : : : 30
13 Conclusion 31
14 Acknowledgements 31
References 33
Notes 35
1 Introduction
The central artifact of this paper is a new Unix shell called scsh. However,
I have a larger purpose beyond simply giving a description of the new
system. It has become fashionable recently to claim that “language doesn’t
matter.” After twenty years of research, operating systems and systems
applications are still mainly written in C and its complex successor, C++.
Perhaps advanced programming languages offer too little for the price they
demand in efficiency and formal rigor.
I disagree strongly with this position, and I would like to use scsh, in
comparison to other Unix systems programming languages, to make the
point that language does matter. After presenting scsh in the initial sec-
tions of the paper, I will describe its design principles, and make a series of
points concerning the effect language design has upon systems program-
ming. I will use scsh, C, and the traditional shells as linguistic exemplars,
and show how their various notational and semantic tradeoffs affect the
programmer’s task. In particular, I wish to show that a functional language
such as Scheme is an excellent tool for systems programming. Many of the
linguistic points I will make are well-known to the members of the systems
programming community that employ modern programming languages,
such as DEC SRC’s Modula-3 [Nelson]. In this respect, I will merely be
serving to recast these ideas in a different perspective, and perhaps diffuse
them more widely.
The rest of this paper is divided into four parts:
In part one, I will motivate the design of scsh (section 2), and then
give a brief tutorial on the system (3, 4).
In part two, I discuss the design issues behind scsh, and cover some
of the relevant implementation details (5–9).
1
2 Unix shells
2
int fork_foobar(void) /* foo | bar in C */
{
int pid1 = fork();
int pid2, fds[2];
if( pid1 == -1 ) {
perror("foo|bar");
return -1;
}
if( !pid1 ) {
int status;
if( -1 == waitpid(pid1, &status, 0) ) {
perror("foo|bar");
return -1;
}
return status;
}
if( -1 == pipe(fds) ) {
perror("foo|bar");
exit(-1);
}
pid2 = fork();
if( pid2 == -1 ) {
perror("foo|bar");
exit(-1);
}
if( !pid2 ) {
close(fds[1]);
dup2(fds[0], 1);
execlp("foo", "foo", NULL);
perror("foo|bar");
exit(-1);
}
close(fds[0]);
dup2(fds[1], 0);
execlp("bar", "bar", NULL);
perror("foo|bar");
exit(-1);
}
3
demands of shell programming move the programmer out into the
dustier recesses of the language’s definition.)
4
What programming language would make a good base? We would
want a language that was powerful and high-level. It should allow for im-
plementations based on interactive interpreters, for ease of debugging and
to keep programs small. Since we want to add new notation to the language,
it would help if the language was syntactically extensible. High-level fea-
tures such as automatic storage allocation would help keep programs small
and simple. Scheme is an obvious choice. It has all of the desired features,
and its weak points, such as it lack of a module system or its poor perfor-
mance relative to compiled C on certain classes of program, do not apply
to the writing of shell scripts.
I have designed and implemented a Unix shell called scsh that is em-
bedded inside Scheme. I had the following design goals and non-goals:
5
The result design, scsh, has two dependent components, embedded
within a very portable Scheme system:
3 Process notation
Scsh has a notation for controlling Unix processes that takes the form of s-
expressions; this notation can then be embedded inside of standard Scheme
code. The basic elements of this notation are process forms, extended process
forms, and redirections.
6
The subforms of a redirection are implicitly backquoted, and symbols
stand for their print-names. So (> ,x) means “output to the file named
by Scheme variable x,” and (< /usr/shivers/.login) means “read from
/usr/shivers/.login.” This implicit backquoting is an important feature
of the process notation, as we’ll see later (sections 5 and 10.6).
Here are two more examples of i/o redirection:
(< ,(vector-ref fv i))
(>> 2 /tmp/buf)
These two redirections cause the file fv[i] to be opened on stdin, and
/tmp/buf to be opened for append writes on stderr.
The redirection (<< object) causes input to come from the printed rep-
resentation of object. For example,
(<< "The quick brown fox jumped over the lazy dog.")
causes reads from stdin to produce the characters of the above string.
The object is converted to its printed representation using the display
procedure, so
(<< (A five element list))
is the same as
(<< "(A five element list)")
is the same as
(<< ,(reverse '(list element five A))).
(Here we use the implicit backquoting feature to compute the list to be
printed.)
The redirection (= fdes fdes/port) causes fdes/port to be dup’d into file
descriptor fdes. For example, the redirection
(= 2 1)
causes stderr to be the same as stdout. fdes/port can also be a port, for
example:
(= 2 ,(current-output-port))
causes stderr to be dup’d from the current output port. In this case, it is an
error if the port is not a file port (e.g., a string port). fNote No port syncg
More complex redirections can be accomplished using the begin process
form, discussed below, which gives the programmer full control of i/o
redirection from Scheme.
7
3.2 Process forms
A process form specifies a computation to perform as an independent Unix
process. It can be one of the following:
(begin . scheme-code) ; Run scheme-code in a fork.
(| pf 1 : : : pf n ) ; Simple pipeline
(|+ connect-list pf 1 : : : pf n ) ; Complex pipeline
(epf . epf) ; An extended process form.
(prog arg 1 : : : arg n ) ; Default: exec the program.
The default case (prog arg 1 : : : arg n ) is also implicitly backquoted. That
is, it is equivalent to:
(begin (apply exec-path `(prog arg 1 : : : arg n )))
Exec-path is the version of the exec() system call that uses scsh’s path list
to search for an executable. The program and the arguments must be either
strings, symbols, or integers. Symbols and integers are coerced to strings.
A symbol’s print-name is used. Integers are converted to strings in base
10. Using symbols instead of strings is convenient, since it suppresses the
clutter of the surrounding ": : : " quotation marks. To aid this purpose, scsh
reads symbols in a case-sensitive manner, so that you can say
(more Readme)
and get the right file. (See section 7 for further details on lexical issues.)
A connect-list is a specification of how two processes are to be wired
together by pipes. It has the form ((from 1 from 2 : : : to) : : : ) and is im-
plicitly backquoted. For example,
(|+ ((1 2 0) (3 3)) pf 1 pf 2)
runs pf 1 and pf 2 . The first clause (1 2 0) causes pf 1 ’s stdout (1) and stderr
(2) to be connected via pipe to pf 2 ’s stdin (0). The second clause (3 3)
causes pf 1 ’s file descriptor 3 to be connected to pf 2 ’s file descriptor 3.
8
Scheme forms that use extended process forms: exec-epf, &, and run:
(exec-epf . epf) ; Nuke the current process.
(& . epf) ; Run epf in background; return pid.
(run . epf) ; Run epf; wait for termination.
; Returns exit status.
These special forms are macros that expand into the equivalent series of
system calls. The definition of the exec-epf macro is non-trivial, as it
produces the code to handle i/o redirections and set up pipelines. However,
the definitions of the & and run macros are very simple:
(& . epf) ) (fork ( () (exec-epf . epf)))
(run . epf) ) (wait (& . epf))
Figures 2 and 3 show a series of examples employing a mix of the
process notation and the syscall library. Note that regular Scheme is used
to provide the control structure, variables, and other linguistic machinery
needed by the script fragments.
;; Dump the output from ls, fortune, and from into log.txt.
(run (begin (run (ls))
(run (fortune))
(run (from)))
(> log.txt))
9
;; M4 preprocess each file in the current directory, then pipe
;; the input into cc. Errlog is foo.err, binary is foo.exe.
;; Run compiles in parallel.
(for-each ( (file)
(let ((outfile (replace-extension file ".exe"))
(errfile (replace-extension file ".err")))
(& (| (m4) (cc -o ,outfile))
(< ,file)
(> 2 ,errfile))))
(directory-files))
10
3.4 Procedures and special forms
It is a general design principle in scsh that all functionality made available
through special syntax is also available in a straightforward procedural
form. So there are procedural equivalents for all of the process notation. In
this way, the programmer is not restricted by the particular details of the
syntax. Here are some of the syntax/procedure equivalents:
Notation Procedure
| fork/pipe
|+ fork/pipe+
exec-epf exec-path
redirection open, dup
& fork
run +
wait fork
Having a solid procedural foundation also allows for general notational
experimentation using Scheme’s macros. For example, the programmer
can build his own pipeline notation on top of the fork and fork/pipe
procedures.
11
pipe a | b, we write:
(fork ( () (fork/pipe a) (b)))
which returns the pid of b’s process.
To create a background three-process pipe a | b | c, we write:
(fork ( () (fork/pipe a)
(fork/pipe b)
(c)))
Run/port returns immediately after forking off the process; other forms
wait for either the process to die (run/file), or eof on the communicat-
ing pipe (run/string, run/strings, run/sexps). These special forms just
expand into calls to the following analogous procedures:
(run/port* thunk) procedure
(run/file* thunk) procedure
(run/string* thunk) procedure
(run/strings* thunk) procedure
(run/sexp* thunk) procedure
(run/sexps* thunk) procedure
12
For example, (run/port . epf) expands into
(run/port* ( () (exec-epf . epf))).
These procedures can be used to manipulate the output of Unix pro-
grams with Scheme code. For example, the output of the xhost(1) program
can be manipulated with the following code:
The following procedures are also of utility for generally parsing input
streams in scsh:
(port->string port) procedure
(port->sexp-list port) procedure
(port->string-list port) procedure
(port->list reader port) procedure
Port->string reads the port until eof, then returns the accumulated string.
Port->sexp-list repeatedly reads data from the port until eof, then re-
turns the accumulated list of items. Port->string-list repeatedly reads
newline-terminated strings from the port until eof, then returns the accu-
mulated list of strings. The delimiting newlines are not part of the returned
strings. Port->list generalises these two procedures. It uses reader to
repeatedly read objects from a port. It accumulates these objects into a list,
which is returned upon eof. The port->string-list and port->sexp-list
procedures are trivial to define, being merely port->list curried with the
appropriate parsers:
13
4 System calls
We’ve just seen scsh’s high-level process-form notation, for running pro-
grams, creating pipelines, and performing I/O redirection. This notation
is at roughly the same level as traditional Unix shells. The process-form
notation is convenient, but does not provide detailed, low-level access to
the operating system. This is provided by the second component of scsh:
its system-call library.
Scsh’s system-call library is a nearly-complete set of POSIX bindings,
with some extras, such as symbolic links. As of this writing, network and
terminal i/o controls have still not yet been implemented; work on them
is underway. Scsh also provides a convenient set of systems program-
ming utility procedures, such as routines to perform pattern matching on
file-names and general strings, manipulate Unix environment variables,
and parse file pathnames. Although some of the procedures have been
described in passing, a detailed description of the system-call library is
beyond the scope of this note. The reference manual [refman] contains the
full details.
14
Unix: Computational agents are processes,
communicate via byte streams.
models are fundamentally different; any attempt to gloss over the distinc-
tions would have made the semantics ugly and inconsistent.
There are two computational worlds here (figure 4), where the basic
computational agents are procedures or processes. These agents are com-
posed differently. In the world of applicative-order procedures, agents
execute serially, and are composed with function composition: (g (f x)).
In the world of processes, agents execute concurrently and are composed
with pipes, in a data-flow network: f | g. A language with both of these
computational structures, such as scsh, must provide a way to interface
them. fNote Normal orderg In scsh, we have “adapters” for crossing be-
tween these paradigms:
Scheme Unix
Scheme (g (f x)) (<< ,x)
Unix run/string,: : : f | g
The run/string form and its cousins (section 3.5) map process output to
procedure input; the << i/o redirection maps procedure output to process
input. For example:
By separating the two worlds, and then providing ways for them to cross-
connect, scsh can cleanly accommodate the two paradigms within one
notational framework.
15
6 I/O
Perhaps the most difficult part of the design of scsh was the integration of
Scheme ports and Unix file descriptors. Dealing with Unix file descriptors
in a Scheme environment is difficult. In Unix, open files are part of the
process state, 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() and
exec() calls.
Scheme, on the other hand, uses ports for specifying i/o sources. Ports
are anonymous, garbage-collected Scheme objects, not integers. When a
port is collected, it is also closed. Because file descriptors are just integers,
it’s impossible to garbage collect them—in order to close file descriptor 3,
you must prove that the process will never again pass a 3 as a file descriptor
to a system call doing I/O, and that it will never exec() a program that
will refer to file descriptor 3.
This is difficult at best.
If a Scheme program only used Scheme ports, and never directly used
file descriptors, this would not be a problem. But Scheme code must
descend to the file-descriptor level in at least two circumstances:
16
file descriptors. When the user does an i/o redirection (e.g., with dup2())
that must allocate a particular file descriptor fd, there is a chance that fd
has already been inadvertently allocated to a port by a prior operation (e.g.,
an open-input-file call). If so, fd’s original port will be shifted to some
new file descriptor with a dup(fd) operation, freeing up fd for use. The
port machinery is allowed to do this as it does not in general reveal which
file descriptors are allocated to particular Scheme ports. Not revealing the
particular file descriptors allocated to Scheme ports allows the system two
important freedoms:
When the user explicitly allocates a particular file descriptor, the run-
time system is free to shuffle around the port/file-descriptor associa-
tions as required to free up that descriptor.
When all pointers to an unrevealed file port have been dropped, the
run-time system is free to close the underlying file descriptor. If the
user doesn’t know which file descriptor was associated with the port,
then there is no way he could refer to that i/o channel by its file-
descriptor name. This allows scsh to close file descriptors during gc
or when performing an exec().
17
7 Lexical issues
“-” and “+” are allowed to begin symbols. So the following are
legitimate symbols:
-O2 -geometry +Wn
“|” and “.” are symbol constituents. This allows | for the pipe
symbol, and .. for the parent-directory symbol. (Of course, “.”
alone is not a symbol, but a dotted-pair marker.)
The lexical details of scsh are perhaps a bit contentious. Extending the
symbol syntax remains backwards compatible with existing correct R4RS
code. Since flags to Unix programs always begin with a dash, not extending
the syntax would have required the user to explicitly quote every flag to a
program, as in
(run (cc "-O" "-o" "-c" main.c)).
This is unacceptably obfuscatory, so the change was made to cover these
sorts of common Unix flags.
More serious was the decision to make symbols read case-sensitively,
which introduces a true backwards incompatibility with R4RS Scheme.
18
This was a true case of clashing world-views: Unix’s tokens are case-
sensitive; Scheme’s, are not.
It is also unfortunate that the single-dot token, “.”, is both a funda-
mental Unix file name and a deep, primitive syntactic token in Scheme—it
means the following will not parse correctly in scsh:
(run/strings (find . -name *.c -print))
You must instead quote the dot:
(run/strings (find "." -name *.c -print))
8 Implementation
Before running even the smallest shell script, the Scheme 48 vm must
first load in a 1.4Mb heap image. This i/o load adds a few seconds to
the startup time of even trivial shell scripts.
Since the entire Scheme 48 and scsh runtime is in the form of byte-
code data in the Scheme heap, the heap is fairly large. As the Scheme
48 vm uses a non-generational gc, all of this essentially permanent
data gets copied back and forth by the collector.
19
(run (| (zcat paper.tex.Z)
(detex)
(spell)
(enscript -2r)))
then, for a brief instant, you could have up to five copies of scsh forked
into existence. This would briefly quintuple the virtual memory
demand placed by a single scsh heap, which is fairly large to begin
with. Since all the code is actually in the data pages of the process,
the OS can’t trivially share pages between the processes. Even if the
OS is clever enough to do copy-on-write page sharing, it may insist
on reserving enough backing store on disk for worst-case swapping
requirements. If disk space is limited, this may overflow the paging
area, causing the fork() operations to fail.
9 Size
Scsh can justifiably be criticised for being a florid design. There are a lot of
features—perhaps too many. The optional arguments to many procedures,
20
the implicit backquoting, and the syntax/procedure equivalents are all eas-
ily synthesized by the user. For example, port->strings, run/strings*,
run/sexp*, and run/sexps* are all trivial compositions and curries of other
base procedures. The run/strings and run/sexps forms are easily writ-
ten as macros, or simply written out by hand. Not only does scsh pro-
vide the basic file-attributes procedure (i.e., the stat() system call),
it also provides a host of derived procedures: file-owner, file-mode,
file-directory?, and so forth. Still, my feeling is that it is easier and
clearer to read
(filter file-directory? (directory-files))
than
(filter ( (fname)
(eq? 'directory
(fileinfo:type (file-attributes fname))))
(directory-files))
exceptions
real strings
21
higher-order procedures
22
(read-symlink fname)
succeed anyway,
dump core,
report an error,
It all depends.
(b) C definition of readlink
23
lines of code to correctly handle the operation, the programmer can write
a single function call and get on with his task.
/* C style: */
g(x,&y);
: : : f(y): : :
Procedures that compute composite data structures for a result commonly
return them by storing them into a data structure passed by-reference as a
parameter. If g does this, we cannot nest calls, but must write the code as
shown.
In fact, the above code is not quite what we want; we forgot to check g
for an error return. What we really wanted was:
/* Worse/better: */
err=g(x,&y);
if( err ) f
<handle error on g call>
g
: : : f(y): : :
The person who writes this code has to remember to check for the error;
the person who reads it has to visually link up the data flow by connecting
y’s def and use points. This is the data-flow equivalent of goto’s, with
equivalent effects on program clarity.
In Scheme, none of this is necessary. We simply write
(f (g x)) ; Scheme
Easy to write; easy to read and understand. Figure 6 shows an example of
this problem, where the task is determining if a given file is owned by root.
24
(if (zero? (fileinfo:owner (file-attributes fname)))
:::)
Scheme
if( stat(fname,&statbuf) ) {
perror(progname);
exit(-1);
}
if( statbuf.st_uid == 0 ) ...
10.4 Strings
Having a true string datatype turns out to be surprisingly valuable in
making systems programs simpler and more robust. The programmer
never has to expend effort to make sure that a string length kept in a
variable matches the actual length of the string; never has to expend effort
wondering how it will affect his program if a nul byte gets stored into his
string. This is a minor feature, but like garbage collection, it eliminates a
whole class of common C programming bugs.
25
procedure. This is the essential Scheme ability to capture abstraction in
a procedure definition. If the user wants to read a list of objects written
in some syntax from an i/o source, he need only write a parser capable
of parsing a single object. The port->list procedure can work with the
user’s parser as easily as it works with read or read-line. fNote On-line
streamsg
First-class procedures also allow iterators such as for-each and filter
to loop over lists of data. For example, to build the list of all my files in
/usr/tmp, I write:
26
Backquote and reliable argument lists
Scsh’s use of implicit backquoting in the process notation is a particularly
nice feature of the s-expression syntax. Most Unix shells provide the user
with a way to take a computed string, split it into pieces, and pass them
as arguments to a program. This usually requires the introduction of some
sort of $IFS separator variable to control how the string is parsed into
separate arguments. This makes things error prone in the cases where a
single argument might contain a space or other parser delimiter. Worse
than error prone, $IFS rescanning is in fact the source of a famous security
hole in Unix [Reeds].
In scsh, data are used to construct argument lists using the implicit
backquote feature of process forms, e.g.:
(run (cc ,file -o ,binary ,@flags)).
Backquote completely avoids the parsing issue because it deals with pre-
parsed data: it constructs expressions from lists, not character strings.
When the programmer computes a list of arguments, he has complete
confidence that they will be passed to the program exactly as is, without
running the risk of being re-parsed by the shell.
Having seen the design of scsh, we can now compare it to other approaches
in some detail.
27
11.2 Shells
Traditional Unix shells, such as sh, have no advantage at all as scripting
languages.
28
still be spawned off to other programs if necessary.
12 Future work
29
12.1 Command language features
The primary design effort of scsh was for programming. We are now
designing and implementing features to make scsh a better interactive
command language, such as job control. A top-level parser for an sh-like
notation has been designed; the parser will allow the user to switch back
to Scheme notation when desired.
We are also considering a display-oriented interactive shell, to be cre-
ated by merging the edwin screen editor and scsh. The user will inter-
act with the operating system using single-keystroke commands, defining
these commands using scsh, and reverting to Scheme when necessary for
complex tasks. Given a reasonable set of GUI widgets, the same trick could
be played directly in X.
30
functionality significantly improved. Some examples under consideration
are:
13 Conclusion
Scsh is a system with several faces. From one perspective, it is not much
more than a system-call library and a few macros. Yet, there is power in this
minimalist description—it points up the utility of embedding systems in
languages such as Scheme. Scheme is at core what makes scsh a successful
design. Which leads us to three final thoughts on the subject of scsh and
systems programming in Unix:
14 Acknowledgements
John Ellis’ 1980 SIGPLAN Notices paper [Ellis] got me thinking about this en-
tire area. Some of the design for the system calls was modeled after Richard
Stallman’s emacs [emacs], Project MAC’s MIT Scheme [MIT Scheme], and
COMMON LISP [CLtL2]. Tom Duff’s Unix shell, rc, was also inspirational;
31
his is the only elegant Unix shell I’ve seen [rc]. Flames with Bennet Yee and
Scott Draves drove me to design scsh in the first place; polite discussions
with John Ellis and Scott Nettles subsequently improved it. Douglas Orr
was my private Unix kernel consultant. Richard Kelsey and Jonathan Rees
provided me with twenty-four hour turnaround time on requested modifi-
cations to Scheme 48, and spent a great deal of time explaining the internals
of the implementation to me. Their elegant Scheme implementation was a
superb platform for development. The design and the major portion of the
implementation of scsh were completed while I was visiting on the faculty
of the University of Hong Kong in 1992. It was very pleasant to work in
such a congenial atmosphere. Doug Kwan was a cooperative sounding-
board during the design phase. Hsu Suchu has patiently waited quite a
while for this document to be finished. Members of the MIT LCS and AI
Lab community encouraged me to polish the research prototype version
of the shell into something releasable to the net. Henry Minsky and Ian
Horswill did a lot of the encouraging; my students Dave Albertz and Brian
Carlstrom did a lot of the polishing.
Finally, the unix-haters list helped a great deal to maintain my perspec-
tive.
32
References
33
[rc] Tom Duff.
Rc—A shell for Plan 9 and Unix systems.
In Proceedings of the Summer 1990 UKUUG Conference,
pages 21–33, July 1990, London. (A revised version
is reprinted in “Plan 9: The early papers,” Com-
puting Science Technical Report 158, AT&T Bell Lab-
oratories. Also available in Postscript form as URL
ftp://research.att.com/dist/plan9doc/7.)
[Reeds] J. Reeds.
/bin/sh: the biggest UNIX security loophole.
11217-840302-04TM, AT&T Bell Laboratories (1988).
34
Notes
fNote Agendag
In fact, I have an additional hidden agenda. I do believe that computational
agents should be expressed as procedures or procedure libraries, not as
programs. Scsh is intended to be an incremental step in this direction, one
that is integrated with Unix. Writing a program as a Scheme 48 module
should allow the user to make it available as a both a subroutine library
callable from other Scheme 48 programs or the interactive read-eval-print
loop, and, by adding a small top-level, as a standalone Unix program. So
Unix programs written this way will also be useable as linkable subroutine
libraries—giving the programmer module interfaces superior to Unix’s
“least common denominator” of ASCII byte streams sent over pipes.
35
fNote Normal orderg
Having to explicitly shift between processes and functions in scsh is in part
due to the arbitrary-size nature of a Unix stream. A better, more integrated
approach might be to use a lazy, normal-order language as the glue or shell
language. Then files and process output streams could be regarded as first-
class values, and treated like any other sequence in the language. However,
I suspect that the realities of Unix, such as side-effects, will interfere with
this simple model.
36
generalised with the procedure
(temp-file-iterate maker [template])
This procedure can be used to perform atomic transactions on the file
system involving filenames, e.g.:
Linking a file to a fresh backup temporary name.
Creating and opening an unused, secure temporary file.
37
To create a unique temporary directory, we write:
(temp-file-iterate ( (dir) (create-directory dir) dir))
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).
38