170: POSIX API

by Olin Shivers (original author), John Cowan (editor and shepherd), Harold Ancell (implementer and editor)

Status

This SRFI is currently in final status. Here is an explanation of each status that a SRFI can hold. To provide input on this SRFI, please send email to srfi-170@nospamsrfi.schemers.org. To subscribe to the list, follow these instructions. You can access previous messages via the mailing list archive.

Abstract

The host environment is the set of resources, such as the filesystem, network and processes, that are managed by the operating system on top of which a Scheme program is executing. This SRFI specifies some of the ways the host environment can be accessed from within a Scheme program. It does so by leveraging widespread support for POSIX, the Portable Operating System Interface standardized by the IEEE. Not all of the functions of this SRFI are available on all operating systems.

Rationale

The I/O and other environmental procedures provided by the various Scheme standards were designed at a time when operating systems were far more diverse than they are today, and therefore portability was difficult or impossible to achieve. In addition, Scheme has historically focused on programming-language features rather than the practical needs of mainstream software development. Consequently, none of the standards provide more than a limited set of operations. Individual implementations often provide much more, but in incompatible ways.

This SRFI uses the IEEE 1003 POSIX.1-2017 standard to provide maximally portable access to the services of the operating system on which typical Scheme implementations run. Almost all operating systems today support all or part of POSIX, so the use of this SRFI is mostly portable, but implementations are definitely not portable. However, an implementation of this SRFI can be layered over many existing implementation-specific interfaces, or directly over a C FFI. It is even possible to implement it on top of the JVM and CLR virtual machines.

This SRFI describes a specific POSIX API for Scheme. Rather than attempting to compromise between existing implementations, the scsh system call specification was chosen as a base document. Consequently, this SRFI is a reduced and heavily edited version of Chapter 3, "System Calls" of version 0.6.7 of the Scsh Reference Manual. The numbered headers are aligned with those used in the Reference Manual.

Scsh 0.6.7 was chosen for two main reasons. It is fairly old, so most of its operations, even those which were non-POSIX at the time (2006) are now included in POSIX, and it has few or no operations that aren't POSIX at all. In addition, it is politically fairly neutral, being tied to an obsolete version of Scheme 48, an implementation which is not being actively developed. Scsh 0.7 exists and runs on the current version of Scheme 48 (see Implementation section), but was not used in designing this SRFI because it is incompletely documented.

This SRFI makes no effort to provide all 81 headers, 1191 interfaces, and 51 data types of full POSIX. Instead it provides access to a reasonable number of highly portable interfaces (many of them even available on Windows) with wrappers to make them more Scheme-like. In particular, this SRFI excludes:

The use of colons to join record names and fields into the name of a record accessor is a convention of Scheme 48, on which scsh is built.

Note: This SRFI is already very long, and adding detailed examples would make it even longer. However, the Chibi test suite is a good source of usage examples.

Specification

Implementations of this SRFI on non-POSIX systems, especially Windows, must provide all the procedure names and syntax keywords. However, if the specified action is not possible, the procedure should either take no action and return some reasonable default value, or signal an exception.

A Scheme implementation that supports both this SRFI and multiple threads of control must ensure that when a thread invokes a blocking procedure such as an R[4567]RS I/O operation, only that thread is blocked and not any other concurrently running ones. Because a user-visible thread may be multiplexed on a single POSIX thread, be in a 1:1 relationship with POSIX threads, or run on different POSIX threads at different times in its life cycle, there can be no guarantees that a POSIX function which is MT-Safe (that is, thread-safe in the sense of POSIX threads) is safe for user-visible threads.

Several of this SRFI's procedures either accept or return a time object that is fully compatible with the time objects of SRFI 19. It contains at least three values: the number of elapsed seconds since a given epoch, the number of elapsed nanoseconds since the beginning of the specified second, and a symbol that represents the epoch and is the same as the value of either time-utc or time-monotonic as exported by SRFI 19. The system clock is not required to report time at full nanosecond resolution, nor is anything guaranteed about accuracy.

3.1  Error handling

The C binding of POSIX places an error number in the global variable errno to report an error, along with (in most cases) returning a sentinel value such as -1. However, the procedures of this SRFI work differently. Rather than reporting errors as return values, they report errors by signaling condition objects satisfying the predicate posix-error? defined below.

This SRFI provides three procedures which will typically be shims over whatever the implementation uses to report such errors:

(posix-error? obj)  → boolean
This procedure returns #t if obj is a condition object that describes a POSIX error, and #f otherwise.
(posix-error-name posix-error)  → symbol

This procedure returns a symbol that is the name associated with the value of errno when the POSIX function reported an error. This can be used to provide programmatic recovery when a POSIX function can return more than one value of errno.

Because the errno codes are not standardized across different POSIX systems, but the associated names (bound by a #define in the file /usr/include/errno.h) are the same for the most part, this function returns the name rather than the code.

For example, ENOENT (a reference was made to a file or a directory that does not exist) almost always corresponds to an errno value of 2. But although ETIMEDOUT (meaning that a TCP connection has been unresponsive for too long) is standardized by POSIX, it has a errno value of 110 on Linux, 60 on FreeBSD, and 116 on Cygwin.

(posix-error-message posix-error)  → string
This procedure returns a string that is an error message reflecting the value of errno when the POSIX function reported an error. This string may be, or may include, the output of the strerror() function, and is useful for reporting the cause of the error to the user. It may or may not be localized.

This SRFI also recommends (but does not require) that the following additional information be retrievable by other means:

3.2  I/O

Dealing with POSIX file descriptors in a Scheme environment is difficult. In POSIX, open files are part of the process environment, and are referenced by small exact integers called file descriptors. Open file descriptors are the fundamental way I/O redirections are passed to subprocesses and executed programs, since file descriptors are preserved across fork and exec operations.

Scheme, on the other hand, uses ports for specifying I/O sources and sinks. Ports are garbage-collected Scheme objects, not integers. When a port is garbage collected, it is effectively closed, but whether the underlying file descriptor is closed is left as an implementation detail. Because file descriptors are just integers, it's impossible to garbage collect them.

Ideally, a Scheme program could only use ports and not file descriptors. But code written in any language, including Scheme, needs to descend to the file descriptor level in at least two circumstances: when interfacing with foreign code, and when interfacing with a subprocess.

This causes a problem. Suppose we have a Scheme port constructed on top of file descriptor 3. We intend to execute a successor program that will expect this file descriptor. If we drop references to the port, the garbage collector may prematurely close file 3 before the successor program starts.

Unfortunately, there is no even vaguely portable solution to the general problem. Scsh and Guile undertake heroic measures to open new file descriptors for ports when the old file descriptors are repurposed for something else, and to track when closing a port implies closing its file descriptor or not. But doing so involves more changes than an implementation should have to make in order to provide this SRFI.

Consequently, this SRFI assumes that file descriptors will only be used at the edges of the program, and that most I/O operations will be performed on ports. As an exception, open-file is provided, because it allows arguments that the Scheme standard does not. It returns a port of a specified type.

binary-input
textual-input
binary-output
textual-output
binary-input/output
Constants whose values represent the type of port to be returned by open-file or fd->port. The textual ports use the same character encoding applied by default in the underlying implementation. The value of binary-input/output represents a binary port that allows both input and output operations, as discussed in SRFI 181.
buffer-none
buffer-block
buffer-line
Constants whose values represent, respectively: the absence of port buffering, where bytes are intended to appear from the source or at the destination as soon as possible; buffering with a block of implementation-dependent size; and buffering line by line, where a line is terminated by a newline byte #xA. The default is implementation-dependent.
(open-file fname port-type flags [permission-bits [buffer-mode] ])  → port   POSIX open()
Opens the file named by fname and returns a port of the type specified by port-type. Flags is an integer bitmask, composed by adding together any of the following constants: open/append, open/create, open/exclusive, open/nofollow, open/truncate. (The POSIX flags O_RDONLY, R_WRONLY, and O_RDWR are inferred from the port type.) Permission-bits defaults to #o666, but are masked by the current umask.
(fd->port fd port-type [buffer-mode])  → port

This procedure wraps a newly created port around the specified file descriptor, effectively importing it into the Scheme world. The most common use of this procedure is for a file descriptor other than 0, 1, 2 (standard input, standard output, standard error) that is already open when the Scheme program starts. It is an error if a port already exists that encapsulates fd, or if an attempt is made to use fd->port twice on the same fd.

3.3  File system

The following procedures allow access to the computer's file system.

(create-directory fname [permission-bits])  → undefined   POSIX mkdir()
(create-fifo fname [permission-bits])  → undefined   POSIX mkfifo()
(create-hard-link old-fname new-fname)  → undefined   POSIX link()
(create-symlink old-fname new-fname)  → undefined   POSIX symlink()

These procedures create objects of various kinds in the file system. If an object with the same name already exists, an exception is signaled.

The permission-bits for create-directory default to #o775, and for create-fifo to #o664, but are masked by the current umask.

(read-symlink fname)  → string   POSIX readlink()
Return the filename referenced by the symlink fname.
(rename-file old-fname new-fname)  → undefined   POSIX rename()
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 POSIX rename().

Calling rename-file on a symbolic link will rename the symbolic link, not the file it refers to.

Remark: There is an unfortunate atomicity problem with the rename-file procedure: if you 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 prevent this problem; at least it is highly unlikely to occur in practice.

(delete-directory fname)  → undefined   POSIX rmdir()
This procedure deletes directories from the file system. An error is signaled if fname is not a directory or is not empty.
(set-file-owner fname uid gid)  → undefined   POSIX chown()
This procedure sets the owner and group of a file specified by supplying the filename. If the uid argument is the constant owner/unchanged, the owner is not changed; if the gid argument is the constant group/unchanged, the group is not changed. Setting file ownership usually requires root privileges. This procedure follows symlinks and changes the files to which they refer.

(set-file-times fname [access-time-object modify-time-object])  → undefined   POSIX utimensat()
This procedure sets the access and modified times for the file fname to the supplied time object values. It is an error if they are not of type time-utc. If neither time argument is supplied, they are both taken to be the current time. The constants time/now and time/unchanged are bound to values used to specify the current time and an unchanged time respectively. It is an error if exactly one time is provided. This procedure will follow symlinks and set the times of the file to which it refers. If the procedure completes successfully, the file's time of last status-change (ctime) is set to the current time.

(truncate-file fname/port len)  → undefined   POSIX truncate()
The specified file is truncated to len bytes in length.

(file-info fname/port follow?)  → file-info-record   POSIX stat()
The file-info procedure returns a file-info record containing useful information about a file. If the follow? flag is true the procedure will follow symlinks and report on the file to which they refer. If follow? is false the procedure checks the actual file itself, even if it's a symlink. The follow? flag is ignored if the file argument is a port.
(file-info? obj)  → boolean  
Returns #t if obj is a file-info record and #f otherwise.
(file-info:device file-info)  → integer  
(file-info:inode file-info)  → integer  
(file-info:mode file-info)  → integer  
(file-info:nlinks file-info)  → integer  
(file-info:uid file-info)  → integer  
(file-info:gid file-info)  → integer  
(file-info:rdev file-info)  → integer  
(file-info:size file-info)  → integer  
(file-info:blksize file-info)  → integer  
(file-info:blocks file-info)  → integer  
(file-info:atime file-info)  → time-object  
(file-info:mtime file-info)  → time-object  
(file-info:ctime file-info)  → time-object  

Returns the device number, inode number, mode (permission and file type bits), number of hard links, user id, group id, device ID if file is special, size in bytes, optimal blocksize for I/O, number of 512B blocks allocated, last access time, last modification time, and last change of status times (using time objects of type time-utc) stored in file-info respectively.

Although POSIX does not standardize bit positions in the file mode, the following assignments are de facto standards (all except the socket, symlink, and fifo values have been present and unchanged since the Sixth Edition of Research Unix):

#o140000   socket file
#o120000   symbolic link file
#o100000   regular file
#o60000    block device file
#o40000    directory file
#o20000    character device file
#o10000    fifo or pipe file
#o4000     setuid file
#o2000     setgid file
#o1000     sticky directory (restrictions on deletion)
#o400      user read permission
#o200      user write permission
#o100      user execute permission
#o40       group read permission
#o20       group write permission
#o10       group execute permission
#o4        other user read permission
#o2        other user write permission
#o1        other user execute permission

Note that to distinguish between file types it is necessary to examine several bits.

(file-info-directory? file-info)  → boolean   POSIX S_ISDIR()
(file-info-fifo? file-info)  → boolean   POSIX S_ISFIFO()
(file-info-symlink? file-info)  → boolean   POSIX S_ISLNK()
(file-info-regular? file-info)  → boolean   POSIX S_ISREG()
(file-info-socket? file-info)  → boolean   POSIX S_IFIFO()
(file-info-device? file-info)  → boolean   POSIX S_ISCHR() || S_ISBLK()
These procedures are file-type predicates that test the file type stored in file-info.

This SRFI does not provide a special means for checking the permission bits in a file-info record, though they are available in file-info:mode. There are several problems with such 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, POSIX special-cases permission checking when the uid is 0 (root) — if the file exists, root is assumed to have the requested permission. However, not even root can write a file stored on a read-only file system, such as a CD-ROM.

(set-file-mode fname mode-bits)  → undefined   POSIX chmod()
This procedure sets the mode bits of a file specified by supplying the filename. This procedure follows symlinks and changes the files to which they refer.

(directory-files dir [dotfiles?])  → string list  
Return a list of filenames in directory dir. The dotfiles? flag (default #f) causes files beginning with . 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 filename in the result list. That is,

(directory-files "/etc")
returns
("chown" "exports" "fstab" ...)
not
("/etc/chown" "/etc/exports" "/etc/fstab" ...)
To use the filenames in the returned list, the programmer can either manually prepend the directory, or change to the directory before using the filenames.
(make-directory-files-generator dir [dotfiles?])  → generator  
Return a SRFI 158 generator of the filenames in directory dir. The dotfiles? flag (default #f) causes files beginning with . to be included in the list. Regardless of the value of dotfiles?, the two files . and .. are never returned.

Like directory-files above, the directory dir is not prepended to each filename in the results the generator returns.

The generator approach is particularly useful when the number of items in a directory might be "huge", which has been a common paradigm when using a file system as a document database.

Note that the generator must be run to exhaustion to close the underlying open directory object.

(open-directory dir [dot-files?])  → directory-object   POSIX opendir()
(read-directory directory-object)  → string or eof-object   POSIX readdir()
(close-directory directory-object)  → undefined   POSIX closedir()

These procedures implement an interface to the opendir()/ readdir()/ closedir() family of functions for processing directories.

The open-directory procedure opens the directory with the specified pathname for reading, returning an opaque directory object. Then read-directory returns the name of the next available file, or the end-of-file object if there are no more files. The dot-files? argument controls whether filenames beginning with "." are returned. If it is #f, which is the default, they are not. The filenames . and .. are never returned. Finally, close-directory closes a directory object.

(real-path path)  → string   POSIX realpath()
Returns an absolute pathname derived from pathname that names the same file and whose resolution does not involve dot (.), dot-dot (..), or symlinks.
(file-space path-or-port)  → exact integer   POSIX statvfs(), fstatvfs()
Returns the amount of free space in bytes on the same volume as the file path (if it is a string) or the file open on port (if it is a port). This allows the application to detect if the disk is getting full. Use path if the file has not yet been created, or port if it is already open.
temp-file-prefix string parameter

SRFI 39 or R7RS parameter that returns a string when invoked. Its initial value is the value of the environment variable TMPDIR concatenated with "/pid" if TMPDIR is set and to "/tmp/pid" otherwise, where pid is the id of the current process. On Windows, the temporary directory's name is not fixed, and must be obtained by the GetTempPath() API function.
(create-temp-file [prefix])  → string  
Creates a new temporary file and returns its name. The optional argument specifies the filename prefix to use, and defaults to the result of invoking temp-file-prefix. 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's 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.

(call-with-temporary-filename maker [prefix])  → object+  
This procedure can be used to perform certain atomic transactions on the file system involving filenames. Some examples:

This procedure uses prefix to generate a series of trial filenames. Prefix is a string, and defaults to the value of invoking temp-file-prefix. File names are generated by concatenating prefix with a varying string.

The maker procedure is called serially on each filename generated. It must return at least one value; it may return multiple values. If the first return value is #f or if maker signals an exception indicating that the file exists, call-with-temporary-filename will loop, generating a new filename 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, call-with-temporary-filename may give up, in which case an exception is signaled or propagated.

To rename a file to a temporary name:

(call-with-temporary-filename
  (lambda (backup)
    (create-hard-link old-file backup)
    backup)
  ".temp.") ; Keep link in current working directory
(delete-file old-file)
Recall that this SRFI reports procedure failure by signaling an error. This is critical for this example — the programmer can assume that if the call-with-temporary-filename call returns, it returns successfully. So the following delete-file call can be reliably invoked, safe in the knowledge that the backup link has definitely been established.

To create a unique temporary directory:

(call-with-temporary-filename
  (lambda (dir)
    (create-directory dir)
    dir)
  "/tmp/tempdir.")
Similar operations can be used to generate unique fifos, or to return values other than the new filename (for example, an open port).

3.4  [Intentionally omitted]

3.5  Process state

(umask)  → exact integer  POSIX umask()
Returns the current file protection mask, or umask, as an exact integer. Whenever a file is created, the specified or default permissions are bitwise-anded with the complement of the umask before they are used.
(set-umask! umask)  → unspecified  POSIX umask()

Sets the file protection mask to the exact integer umask and returns an unspecified value.

Warning: Although POSIX specifies that changing the umask affects all threads in the current process, some Scheme implementations maintain a separate simulated umask for each thread. As a result, the effects of this procedure in a multi-threaded program are only partly predictable. This SRFI recommends (but does not require) that in multi-threaded programs the mask be set in the primordial thread before any other threads are created and never changed again.

(current-directory)  → string   POSIX getcwd()
Returns the current directory as a string containing an absolute pathname. Whenever a file is referenced with a relative path, it is interpreted as relative to this directory.
(set-current-directory! new-directory)  → unspecified   POSIX chdir()

Sets the current directory to new-directory and returns an unspecified value.

Warning: Although POSIX specifies that changing the current directory affects all threads in the current process, some Scheme implementations maintain a separate simulated current directory for each thread. As a result, the effects of this procedure in a multi-threaded program are only partly predictable. This SRFI recommends (but does not require) that in multi-threaded programs the current directory be set in the primordial thread before any other threads are created and never changed again.

(pid)  → exact integer   POSIX getpid()
Retrieves the process id for the current process.
(nice [delta])exact integer        POSIX nice()

Increments the niceness of the current process by delta. The lower the niceness value is, the more the process is favored during scheduling. If delta is not specified, the increment is 1.

Real-time processes are not affected by nice.

(user-uid)  → exact integer   POSIX getuid()
(user-gid)  → exact integer   POSIX getgid()
(user-effective-uid)  → exact integer   POSIX geteuid()
(user-effective-gid)  → exact integer   POSIX getegid()
(user-supplementary-gids)  → exact integer list   POSIX getgroups()
For the calling process, these routines get the specified data. The scsh procedure user-login-name, which uses getlogin() or getlogin_r(), can be simulated with (user-info:name (user-info (user-uid))), using procedures that are described in the next section.

3.6  User and group database access

These procedures are used to access the user and group databases (for example, the ones traditionally stored in /etc/passwd and /etc/group).

(user-info uid/name)  → record   POSIX getpwuid/getpwnam()
Return a user-info record giving the recorded information for a particular user. The uid/name argument is either an exact integer user id or a string user name. If uid/name does not identify an existing user, #f is returned; this does not constitute an error situation, and callers must be prepared to handle it.

(user-info? obj)  → boolean  
Returns #t if obj is a user-info record and #f otherwise.
(user-info:name user-info)  → string  
(user-info:uid user-info)  → exact integer  
(user-info:gid user-info)  → exact integer  
(user-info:home-dir user-info)  → string  
(user-info:shell user-info)  → string  
Returns the user name, user id, group id, home directory, and shell path stored in user-info respectively. An implementation returns #f for any unavailable items.
(user-info:full-name user-info)  → string  
Returns the contents of the pw_gecos field stored in user-info. Although this field is not part of POSIX, it has been part of all Unix variants since at least the Sixth Edition of Research Unix. It normally contains the user's full name, but may contain additional system-specific information; on Windows, it contains exactly the full name.
(user-info:parsed-full-name user-info)  → string list  

Returns a parsed and expanded version of the raw string returned by user-info:full-name. The raw value is split on commas, creating a list of strings to be returned. All ampersands in the first element of the list are replaced by user-info:name, which is capitalized if it starts with an ASCII lowercase letter.

However, on Windows the implementation is completely different: user-info:parsed-full-name returns a list with a single element, the result of user-info:full-name. No comma splitting or ampersand substitution is performed.

The meaning of the first element of the returned list is the user's full name on all known systems. The remaining elements have varying meaning. For example, on BSD systems, the second through fourth elements are the user's work location, the user's work phone number, and the user's home phone number, respectively. On Cygwin, the second element is the Windows SID corresponding to this user; further elements depend on Cygwin-specific entries in the /etc/nsswitch.conf file.

(group-info gid/name)  → record   POSIX getgrgid/getgrnam()
Return a group-info record giving the recorded information for a particular group. The gid/name argument is either an exact integer group id or a string group name. If gid/name does not identify an existing group, #f is returned; this does not constitute an error situation, and callers must be prepared to handle it.
(group-info? obj)  → boolean  
Returns #t if obj is a group-info record and #f otherwise.
(group-info:name group-info)  → string  
(group-info:gid group-info)  → exact integer  
Returns the group name and group id stored in group-info. The list of group member ids can be retrieved with the above (user-supplementary-gids)

3.7  [Intentionally omitted]

3.8  [Intentionally omitted]

3.9  [Intentionally omitted]

3.10  Time

(posix-time)time-object        POSIX clock_gettime()

The posix-time procedure returns the current time as a time object of type time-utc, which represents the time since the POSIX epoch (midnight January 1, 1970 Universal Time), excluding leap seconds. It uses the POSIX CLOCK_REALTIME clock.

(monotonic-time)time-object        POSIX clock_gettime()

The monotonic-time procedure returns the current time as a time object of type time-monotonic, which represents the time since an arbitrary epoch. This epoch is arbitrary, but cannot change after the current program begins to run. It is guaranteed that a call to monotonic-time cannot return a time earlier than a previous call to monotonic-time. This is not guaranteed for posix-time because the system's POSIX clock is sometimes turned backward to correct local clock drift. It uses the POSIX CLOCK_MONOTONIC clock.

3.11  Environment variables

R7RS provides two procedures , originally from SRFI 98, to get access to environment variables:

Setters have not yet been standardized, so this SRFI provides them. The main reason to set environment variables is that some C libraries depend on their values: for example, localization depends on LANG. If a program needs to change to a different locale, the most reliable way is to set the LANG variable to a new value.

(set-environment-variable! name value)  → undefined   POSIX setenv
Change the value of the environment variable name to be value. Both name and value are strings. If name is not defined at the time of call, a new variable is added; if name is defined, its old value is discarded and replaced by value. If name or value are invalid according to the operating system, an exception is signaled. Mutating name or value after the call must not change the name or value of the environment variable.
(delete-environment-variable! name)  → undefined   POSIX unsetenv
Remove the environment variable name such that a subsequent (get-environment-variable name) would return #f. If the variable cannot be removed, an exception is signaled. If name does not currently have a value, the call silently succeeds.

Concurrency note: Under most operating systems, environment variables are per-process, not per-thread. Multi-threaded implementations should guard against race conditions.

Naming note: Many Scheme implementations (including scsh) use the short procedure names getenv and setenv (or minor variations). Since the long name get-environment-variable has been canonized by R7RS and SRFI 98, this SRFI follows that pattern instead. Since environment variables are OS variables rather than Scheme variables, it's debatable whether procedures to modify them should bear the ! suffix. MIT Scheme has the suffixed names and we are not aware of any Scheme implementation with the unsuffixed names, so the suffix is kept in this SRFI.

3.12  Terminal device control

(terminal? port)  → boolean   POSIX isatty()
Returns true if the argument is a file descriptor that is open on a terminal. Raises an exception if the underlying call to isatty() returns an error other than ENOTTY. This procedure is useful when writing programs that change their behavior when their standard input or output is a terminal. Because it is expected to be called before doing input or output on the terminal, it accepts a port rather than an file descriptor object.

Implementation

There are two implementations of this SRFI, Scsh version 0.7, which can be found at GitHub in the scsh repository of scheme, and a Chibi Scheme (srfi 170) library. You can find the Chibi Scheme example implementation, and build notes for scsh, in their own srfi subdirectories.

Notes on the exceptions and deviations between this SRFI and the Chibi implementation can be found in those subdirectories, at srfi/scsh/NOTES.html, and between this SRFI and scsh 0.7 in srfi/chibi-scheme/NOTES.html.

Acknowledgements

Thanks to Olin Shivers, sine quo non, and all the Scheme implementors who have followed his work. Thanks also to all the participants in the SRFI mailing list.

(Why quo? Because in sine qua non the pronoun is feminine, agreeing with re 'thing' understood, whereas Olin is masculine.)

Special thanks to Alex Shinn for coming up with the idea of FDOs.

Copyright

© 2019 John Cowan.

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

This SRFI is derived from the documentation for scsh, whose copyright notice, from the COPYING file, is reprinted here:

Copyright (c) 1993-2003 Richard Kelsey and Jonathan Rees
Copyright (c) 1994-2003 by Olin Shivers and Brian D. Carlstrom.
Copyright (c) 1999-2003 by Martin Gasbichler.
Copyright (c) 2001-2003 by Michael Sperber.
Copyright (c) 2019-2020 by John Cowan and Harold Ancell.

All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

  1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
  2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
  3. The name of the authors may not be used to endorse or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


Editor: Arthur A. Gleckler