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

    Python asynchronous code on a laptop for inspect.markcoroutinefunction
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    markcoroutinefunction: Detect Async Wrappers

    Learn inspect.markcoroutinefunction in Python to identify async wrappers, integrate frameworks, and avoid incorrect coroutine detection.

    Ler mais

    Tempo de leitura: 5 minutos
    10/10/2026
    Python code for traversing folders and files with Path.walk
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Path.walk: Traverse Directories Safely

    Learn Python Path.walk to traverse directories, filter files, handle errors, and control directory-tree processing safely.

    Ler mais

    Tempo de leitura: 5 minutos
    10/10/2026
    Python process debugging in a terminal with source code
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    pdb -p: Debug Running Python Processes

    Learn how to attach pdb to a running Python process, inspect stacks, and diagnose hangs safely.

    Ler mais

    Tempo de leitura: 6 minutos
    09/10/2026
    Python programming code for exact fractions
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    fractions.from_number: Convert Numbers to Fractions

    Learn Python fractions.from_number for exact rational conversion, float and Decimal handling, approximation, validation, and safe arithmetic.

    Ler mais

    Tempo de leitura: 4 minutos
    09/10/2026
    Developer configuring a Python HTTPS server and TLS certificate
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    HTTPSServer: Build a Local HTTPS Server in Python

    Learn Python HTTPSServer for local HTTPS services, TLS certificates, threaded handling, testing, and practical security limits.

    Ler mais

    Tempo de leitura: 4 minutos
    08/10/2026
    Protected files representing secure TAR extraction with Python
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    tarfile extraction_filter: Extract TAR Safely

    Learn Python tarfile extraction_filter for safer TAR extraction, path validation, links, permissions, and resource limits.

    Ler mais

    Tempo de leitura: 5 minutos
    08/10/2026