You are looking at historical revision 45547 of this page. It may differ significantly from its current revision.
srfi-170
SRFI 170: POSIX API
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.
Implementation Notes
Some of these functions exist in one form or another in other eggs. In particular, this egg is in some ways in competition with the (chicken file posix) module. It's an open question whether this spec has a better interface than (chicken file posix) or some of those other specs. I think in many respects the API is better here. For example, the option to use open-directory to control the open/close lifecycle, and file-info which has named getters. It being the case, that this reinvents some of these internal chicken modules, it's hard to decide whether to reimplement from scratch or to piggy back off them. In most (but not all) cases I've take the approach to reimplement from scratch, partly on the assumption that those other eggs might want to reliquish resonsibility for them for the standard, partly because the srfi spec has specific error reporting, which they may not implement.
One notable question was what to do about ports. I could have just relied on (chicken port) and bypassed (chicken file posix) altogether, and in many ways I would have preferred that. On the other hand, that would have made this module non interoperable with (chicken file posix) in one important respect: the ports returned by (chicken file posix) contain the file descriptor and implement (port->fileno), so the ports returned would not have interoperated with ports here had I done that. In many ways, that doesn't seem like a big deal, because (chicken file posix) does not contain many extra features, and I intend to add those features here anyway so that users can at least bypass it in favor of this. On the other hand, it's at least conceivable that you may want to include some other scheme library that uses (chicken file posix) so that would have potentially been a headache. So I've taken the pragmatic approach, and I allow (chicken file posix) to create the port, just so it can be the custodian of the file descriptor and allow interoperability
I have made a small amount of effort to make the code here work on Windows, but have not tested it as of yet, and I have no idea what might be required to make it actually work or what (chicken file posix) does about that. I've made a good faith effort to make it compatible with BSD and MacOS, but have not tested it there either. File a bug report if it doesn't work for you.
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:
[procedure] (posix-error? obj) → booleanThis procedure returns #t if obj is a condition object that describes a POSIX error, and #f otherwise.
- obj
- A condition error object
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
- A condition error object
Return the errno code. Recommended to use posix-error-name for portability.
- posix-error
- A condition error object
Returns a human-readable string describing the POSIX error described by the condition object obj.
- posix-error
- A condition error object
The procedure which caused the error
- posix-error
- A condition error object
The arguments passed to the api which caused the error
- posix-error
- A condition error object
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.
However, as an extension to the spec, many APIs that take ports will be extended to also take file descriptors
[constant] binary-inputBinary input
[constant] binary-outputBinary output
[constant] textual-inputText input
[constant] textual-outputText output
[constant] binary-input/outputBinary 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.
[constant] buffer-noneNo buffering
[constant] buffer-lineBuffering at the line level
[constant] buffer-blockBuffering at the block level
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.
[constant] open/createCreate file if it doesn't exist
[constant] open/exclusiveFails if the file exists
[constant] open/truncateTruncates the file
[constant] open/appendAlways appends to the file
[procedure] (create-directory fname #!optional (permission-bits 509)) → undefinedCreates a directory in the file system. If an object with the same name exists, an exception is signaled.
- fname
- File name
- permission-bits
- file permission bits
Creates a FIFO in the file system. If an object with the same name exists, an exception is signaled.
- fname
- File name
- permission-bits
- file permission bits
Creates a hard link in the file system. If an object with the same name exists, an exception is signaled.
- old-fname
- Existing name
- new-fname
- New name
- permission-bits
- file permission bits
Creates a symolic link in the file system. If an object with the same name exists, an exception is signaled.
- old-fname
- Existing name
- new-fname
- New name
Return the filename referenced by the symlink fname.
- fname
- path
The specified file is truncated to len bytes in length.
- len
- length in bytes
[procedure] (make-file-info device inode mode nlinks uid gid rdev size atime mtime ctime blksize blocks)
[procedure] file-info?
[procedure] file-info:device
[procedure] file-info:inode
[procedure] file-info:mode
[procedure] file-info:nlinks
[procedure] file-info:uid
[procedure] file-info:gid
[procedure] file-info:rdev
[procedure] file-info:size
[procedure] file-info:atime
[procedure] file-info:mtime
[procedure] file-info:ctime
[procedure] file-info:blksize
[procedure] file-info:blocks
Record containing file information
[procedure] (file-info fname/port #!optional (follow? #t))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.
- fname/port
- path or Scheme port
- follow?
- Follow symlinks, default #t
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.
- fname
- path
- mode-bits
- permission bits
Return a list of filenames in directory dir. The special . and .. names 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.
- dir
- path
- dot-files?
- whether filenames beginning with "." are returned
Return a SRFI 158 generator of the filenames in directory dir. The special . and .. names 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. As an extension to the standard, you can pass the symbol 'close to the generator to close it early.
- dir
- path
- dot-files?
- whether filenames beginning with "." are returned
[procedure] (make-directory-object ptr)
[procedure] directory-object?
[procedure] directory-object:ptr
[procedure] directory-object:ptr-set!
[procedure] directory-object:dot-files?
Directory access handle
[procedure] (open-directory dir #!optional (dot-files? #f)) → directory-objectopens the directory with the specified pathname for reading, returning an opaque directory object.
- dir
- path
- dot-files?
- whether filenames beginning with "." are returned
returns the name of the next available file, or the end-of-file object if there are no more files. The special . and .. names are never returned.
- directory-object
- opaque directory object
returns the name of the next available file, or the end-of-file object if there are no more files.
- directory-object
- opaque directory object
closes a directory object.
- directory-object
- opaque directory object
Returns an absolute pathname derived from pathname that names the same file and whose resolution does not involve dot (.), dot-dot (..), or symlinks.
- path
- path
[procedure] (make-file-system-info block-size fragment-size fragments-number blocks-free blocks-available inode-number inodes-free inodes-available id flags name-max)
[procedure] file-system-info?
[procedure] file-system-info:block-size
[procedure] file-system-info:fragment-size
[procedure] file-system-info:number-of-fragments
[procedure] file-system-info:blocks-free
[procedure] file-system-info:blocks-available
[procedure] file-system-info:inode-number
[procedure] file-system-info:inodes-free
[procedure] file-system-info:inodes-available
[procedure] file-system-info:id
[procedure] file-system-info:flags
[procedure] file-system-info:name-max
File system info record
[procedure] (file-system-info fname/port) → file-system-infoRead information about the file system
- fname/port
- path or Scheme port
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.
- fname/port
- path or Scheme port
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.
[procedure] (create-temp-file #!optional (prefix temp-file-prefix)) → stringCreates 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.
- temp-file-prefix
- the filename prefix to use
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 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).
[procedure] (umask) → exact integerReturns 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.
[procedure] (set-umask! umask) → unspecifiedSets 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.[procedure] (current-directory) → string
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.
[procedure] (set-current-directory! new-directory) → unspecifiedSets 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.
- new-directory
- directory path
Retrieves the process id for the current process.
[procedure] (nice #!optional (delta 1)) → exact integerIncrements 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.[procedure] (user-uid) → exact integer
Retrieves the current user's uid
[procedure] (user-gid) → exact integerRetrieves the current user's group
[procedure] (user-effective-uid) → exact integerRetrieves the current user's effective uid
[procedure] (user-effective-gid) → exact integerRetrieves the current user's effective gid
[procedure] (user-supplementary-gids) → exact integer listRetrieve the current user's list of supplementary gids
[record] <user-info>[procedure] (make-user-info name password uid gid home-dir shell full-name parsed-full-name)
[procedure] user-info?
[procedure] user-info:name
[procedure] user-info:password
[procedure] user-info:uid
[procedure] user-info:gid
[procedure] user-info:home-dir
[procedure] user-info:shell
[procedure] user-info:full-name
[procedure] user-info:parsed-full-name
Record about system user
[procedure] (user-info uid/name) → user-infoReturn 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.
- uid/name
- User's uid or login name
[procedure] (make-group-info name password gid members)
[procedure] group-info?
[procedure] group-info:name
[procedure] group-info:password
[procedure] group-info:gid
[procedure] group-info:members
Record about system group
[procedure] (group-info gid-name) → group-infoReturn 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.
- gid/name
- Group's gid or group name
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.
[procedure] (monotonic-time) → time-objectThe 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.
[constant] delete-environment-variable!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.
[procedure] terminal? → booleanReturns 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.
Maintainer
Chris Bitmead
Author
Chris Bitmead
Version History
License
MIT
(C) 2026 Chris Bitmead 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.