1. srfi-170
  2. Abstract
  3. Rationale
  4. Implementation Notes
  5. Error Handling
  6. I/O
    1. Maintainer
    2. Author
    3. Version History
    4. License

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) → boolean

This procedure returns #t if obj is a condition object that describes a POSIX error, and #f otherwise.

obj
A condition error object
[procedure] (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
A condition error object
[procedure] (posix-error-number posix-error) → number

Return the errno code. Recommended to use posix-error-name for portability.

posix-error
A condition error object
[procedure] (posix-error-message posix-error) → string

Returns a human-readable string describing the POSIX error described by the condition object obj.

posix-error
A condition error object
[procedure] (posix-error-procedure posix-error) → symbol

The procedure which caused the error

posix-error
A condition error object
[procedure] (posix-error-arguments posix-error) → list

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-input

Binary input

[constant] binary-output

Binary output

[constant] textual-input

Text input

[constant] textual-output

Text output

[constant] binary-input/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.

[constant] buffer-none

No buffering

[constant] buffer-line

Buffering at the line level

[constant] buffer-block

Buffering 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/create

Create file if it doesn't exist

[constant] open/exclusive

Fails if the file exists

[constant] open/truncate

Truncates the file

[constant] open/append

Always appends to the file

[procedure] (create-directory fname #!optional (permission-bits 509)) → undefined

Creates 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
[procedure] (create-fifo fname #!optional (permission-bits 436)) → undefined

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
[procedure] (create-hard-link old-fname new-fname) → undefined

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
[procedure] (create-symlink old-fname new-fname) → undefined

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
[procedure] (read-symlink path) → string

Return the filename referenced by the symlink fname.

fname
path
[procedure] (truncate-file fname/port #!optional (len 0)) → undefined

The specified file is truncated to len bytes in length.

len
length in bytes
[record] <file-info>
[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
[procedure] (set-file-mode fname mode-bits)

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
[procedure] (directory-files dir #!optional (dot-files? #f))

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
[procedure] (make-directory-files-generator dir #!optional (dot-files? #f))

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
[record] <directory-object>
[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-object

opens the directory with the specified pathname for reading, returning an opaque directory object.

dir
path
dot-files?
whether filenames beginning with "." are returned
[procedure] (read-directory-entry directory-object) → dirent or eof-object

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
[procedure] (read-directory directory-object) → string or eof-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
[procedure] (close-directory directory-object) → undefined

closes a directory object.

directory-object
opaque directory object
[procedure] (real-path path) → string

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
[record] <file-system-info>
[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-info

Read information about the file system

fname/port
path or Scheme port
[procedure] (file-space fname/port) → exact integer

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
[constant] temp-file-prefix

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)) → 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.

temp-file-prefix
the filename prefix to use
[procedure] (call-with-temporary-filename maker #!optional (prefix temp-file-prefix) #!key (tries 10)) → object

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 integer

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.

[procedure] (set-umask! umask) → unspecified

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.
[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) → unspecified

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.
new-directory
directory path
[procedure] (pid) → exact integer

Retrieves the process id for the current process.

[procedure] (nice #!optional (delta 1)) → exact integer

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.
[procedure] (user-uid) → exact integer

Retrieves the current user's uid

[procedure] (user-gid) → exact integer

Retrieves the current user's group

[procedure] (user-effective-uid) → exact integer

Retrieves the current user's effective uid

[procedure] (user-effective-gid) → exact integer

Retrieves the current user's effective gid

[procedure] (user-supplementary-gids) → exact integer list

Retrieve 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-info

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.

uid/name
User's uid or login name
[record] <group-info>
[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-info

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.

gid/name
Group's gid or group name
[procedure] (posix-time) → time-object

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-object

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.

[procedure] delete-environment-variable! → undefined

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.

name
variable name
[procedure] terminal? → boolean

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.

Maintainer

Chris Bitmead

Author

Chris Bitmead

Version History

0.16
0.17

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.