Python posix: Direct Unix System Calls

Published on: August 26, 2026
Reading time: 6 minutes
Detailed shot of a Jungle Carpet Python (Morelia spilota cheynei) in its natural habitat.

The posix module exposes common system calls on POSIX-compatible platforms. It provides a low-level interface for files, directories, processes, descriptors, permissions, users, groups, and operating-system information. Most application code should import os instead, because that module offers a more portable layer and includes nearly all of the useful posix interface.

Understanding posix still helps explain how Python communicates with Unix systems such as Linux, macOS, and BSD. It is useful when investigating platform-specific behavior, building administration tools, comparing interpreter builds, or learning where many os functions originate.

Availability and portability

posix exists only when Python is built for an operating system that provides the corresponding interface. It is normally unavailable on Windows. A cross-platform library should therefore avoid importing it globally without a fallback.

try:
    import posix
except ImportError:
    posix = None

if posix is None:
    print("POSIX interface is unavailable")

Even on Unix, individual functions can vary with the kernel, C library, compile options, and Python version. Check features with hasattr() or, preferably, use the capability sets published by os.

Why os is usually better

The os module selects the correct implementation for the current platform. On Unix, many os functions are backed by posix; on Windows, another implementation provides equivalent operations. Calls such as os.open(), os.stat(), and os.getpid() therefore keep a consistent public API.

import os

print(os.getpid())
print(os.getcwd())

Import posix directly only when the purpose is explicitly to study, test, or depend on POSIX behavior. For ordinary programs, os is clearer and safer.

File descriptors

Low-level calls operate on integer file descriptors. open() opens a path and returns a descriptor; read() and write() transfer bytes; close() releases the resource.

import os

fd = os.open("data.bin", os.O_RDONLY)
try:
    block = os.read(fd, 4096)
    print(len(block))
finally:
    os.close(fd)

The finally block matters whenever descriptors are managed manually. For regular files, the language-level open() function with with is more convenient because it handles closing, buffering, text decoding, and encoding.

Open flags

POSIX calls combine flags such as O_RDONLY, O_WRONLY, O_RDWR, O_CREAT, O_EXCL, O_APPEND, and, where available, O_CLOEXEC. Use bitwise OR to build the requested configuration.

import os

flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL
fd = os.open("new.txt", flags, 0o600)
try:
    os.write(fd, b"private content\n")
finally:
    os.close(fd)

O_EXCL together with O_CREAT avoids replacing an existing file and reduces race conditions. Mode 0o600 requests owner-only access, although the process umask also affects final permissions.

Paths as str or bytes

On Unix, paths may be passed as str or bytes. Use text paths for most programs. Bytes are useful only when preserving names that cannot be decoded with the filesystem encoding.

Do not mix the two forms in one operation. Functions given a bytes path normally return bytes names. os.fsencode() and os.fsdecode() convert through Python’s filesystem policy.

File information with stat

stat() returns size, timestamps, owner, group, mode, and additional fields. Use the stat module to interpret mode bits.

import os
import stat

info = os.stat("data.bin", follow_symlinks=False)
print(info.st_size)
print(stat.filemode(info.st_mode))
print(stat.S_ISREG(info.st_mode))

When security depends on the object being inspected, avoid the sequence “check a path, then open it.” Another process could replace the path between those calls. Prefer descriptor-based APIs and parameters such as dir_fd and follow_symlinks when supported.

stat() normally follows symbolic links, while lstat() describes the link itself. The distinction matters in scanners, backup programs, installers, and cleanup tools.

import os

target = os.readlink("shortcut")
link_info = os.lstat("shortcut")
print(target, link_info.st_mode)

Never assume that a trusted pathname continues pointing to the same object. In shared directories, symlink-swap attacks can redirect privileged operations.

Permissions and umask

chmod() changes permission bits. umask() defines which requested permissions are removed when new objects are created. Because umask is process-global state, changing it in a multithreaded application is risky.

import os

os.chmod("new.txt", 0o600)

Do not use chmod(0o777) as a universal fix. Grant only what is required and account for ACLs, container policies, mounted volumes, and the process identity.

User, group, and process identity

Functions such as getpid(), getppid(), getuid(), geteuid(), getgid(), and getegid() describe the current process. Real and effective identities may differ in specially privileged programs.

import os

print({
    "pid": os.getpid(),
    "uid": os.getuid(),
    "euid": os.geteuid(),
    "gid": os.getgid(),
})

To map IDs to account names, see Python pwd and Python grp. Those account databases are not authentication mechanisms.

Environment variables

At the POSIX level, the environment is a collection of keys and values attached to a process. In Python, prefer os.environ, a mutable mapping whose changes are inherited by later child processes.

import os

mode = os.environ.get("APP_MODE", "development")
os.environ["APP_CHILD_FLAG"] = "1"

Do not log the entire environment. It may contain tokens, passwords, private endpoints, and filesystem paths. Validate values before using them in commands or filenames.

Creating processes

POSIX exposes primitives such as fork(), execve(), waitpid(), and _exit(). They are powerful but require detailed handling of threads, buffers, locks, and inherited descriptors.

For launching programs, prefer subprocess. It models arguments, redirection, exit codes, timeouts, and descriptor closing more safely.

import subprocess

result = subprocess.run(
    ["uname", "-s"],
    check=True,
    capture_output=True,
    text=True,
    timeout=5,
)
print(result.stdout.strip())

Avoid shell=True with untrusted data. Pass arguments as a list.

Fork and threads

After fork(), only the calling thread remains in the child. Locks held by vanished threads may stay permanently locked. Database clients, loggers, TLS libraries, and memory allocators can also contain inconsistent state.

Modern applications should use subprocess or an appropriate multiprocessing start method. Avoid complex Python work between fork() and exec() in a multithreaded process.

Inherited descriptors

An open descriptor can leak into a child and keep files, sockets, or pipes alive. Python creates many descriptors as non-inheritable by default, but native extensions and explicit settings can change that behavior.

Use os.get_inheritable(), os.set_inheritable(), and subprocess options deliberately. Python fcntl covers Unix descriptor flags and locks.

Directory-relative operations

Many functions accept dir_fd. Instead of concatenating path strings, open a trusted directory and resolve names relative to that descriptor. This can reduce race conditions and make restricted operations easier to reason about.

import os

base_fd = os.open("data", os.O_RDONLY | os.O_DIRECTORY)
try:
    fd = os.open("item.txt", os.O_RDONLY, dir_fd=base_fd)
    try:
        print(os.read(fd, 100))
    finally:
        os.close(fd)
finally:
    os.close(base_fd)

Not every function or platform supports dir_fd. Inspect os.supports_dir_fd.

Synchronization and durability

fsync() asks the system to flush data and associated metadata for a descriptor. For critical replacement, write a temporary file on the same filesystem, flush it, rename it atomically, and, where required, flush the directory.

A successful call does not guarantee protection from every hardware or cache failure. Durability depends on the filesystem, storage device, mount options, and infrastructure.

Errors and OSError

System-call failures appear as subclasses of OSError, including FileNotFoundError, PermissionError, FileExistsError, and IsADirectoryError.

from pathlib import Path

try:
    data = Path("config.ini").read_text(encoding="utf-8")
except FileNotFoundError:
    data = ""
except PermissionError as error:
    raise RuntimeError("cannot read config.ini") from error

Catch the narrowest exception that enables a meaningful response. Do not suppress unexpected failures with except OSError: pass.

Resource limits

POSIX calls may fail because the process has exhausted descriptors, memory, disk space, or process limits. Python resource explains how to inspect and set several Unix limits.

Handle failures, set timeouts, and cap input volume. A directory walker or batch processor should close resources progressively rather than keeping everything open.

Test on real systems

Containers do not reproduce every difference among macOS, Linux, and BSD. Test permissions, symlinks, byte paths, read-only mounts, network filesystems, full disks, different UIDs, umask behavior, signals, and descriptor inheritance.

Run tests with reduced privileges as well. Administrator-only testing can hide permission mistakes and unsafe assumptions.

Common mistakes

Frequent errors include importing posix when os is sufficient, assuming every function exists, forgetting to close descriptors, mixing text and bytes paths, following symlinks unintentionally, changing process-wide umask from several threads, using fork() unsafely, and catching OSError without diagnostics.

Conclusion

posix exposes the Unix system-call layer used by Python, but os should remain the default interface for most projects. Choose high-level APIs when they communicate intent more clearly, and use descriptors, flags, and directory-relative operations when precise control is genuinely needed.

Validate capabilities, close resources, minimize privilege, and treat shared paths as mutable. Consult the official posix documentation and the os documentation.

Share:

Facebook
WhatsApp
Twitter
LinkedIn

Article content

    Related articles

    Vivid close-up of code on a computer screen showcasing programming details.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python curses: Build Terminal Interfaces

    Learn Python curses to build terminal interfaces with windows, colors, keyboard input, resize handling, Unicode, and safe cleanup.

    Ler mais

    Tempo de leitura: 6 minutos
    26/08/2026
    Close-up view of a computer screen displaying code in a software development environment.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python grp: Query Unix Groups

    Learn Python grp to query Unix groups, GIDs, members, ownership, supplementary groups, NSS, and container identities safely.

    Ler mais

    Tempo de leitura: 5 minutos
    26/08/2026
    Close-up of HTML and CSS code displayed on a computer screen, ideal for tech and programming themes.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python pwd: Query Unix User Accounts

    Learn Python pwd to query Unix users by UID or login, retrieve home, shell, and ownership without using the database

    Ler mais

    Tempo de leitura: 5 minutos
    26/08/2026
    Stack of cut logs covered in snow, showcasing a cold winter texture and woody elements.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python syslog: Send Logs to Unix

    Learn Python syslog to send Unix logs with priorities, facilities, masks, structured content, and protection against log injection.

    Ler mais

    Tempo de leitura: 5 minutos
    26/08/2026
    High-resolution close-up of a CPU processor, RAM sticks, and a hard drive, showcasing modern computer hardware.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python resource: CPU and Memory Limits

    Learn Python resource to measure CPU, peak memory, page faults, and set limits for files, processes, descriptors, and address space

    Ler mais

    Tempo de leitura: 5 minutos
    26/08/2026
    A developer typing code on a laptop with a Python book beside in an office.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python pty: Automate Unix Terminals

    Learn Python pty to run and test interactive programs, control pseudo-terminals, handle EOF, resize, signals, timeouts, and cleanup.

    Ler mais

    Tempo de leitura: 5 minutos
    25/08/2026