fcntl en Python: locks y control de archivos

Publicado el: 25/08/2026
Tempo de leitura: 5 minutos
Multiple padlocks securing a green chain link fence, symbolizing safety and protection.

El módulo fcntl expone las llamadas Unix fcntl() e ioctl(), además de helpers para locking de archivos. Trabaja con file descriptors y permite acceder a flags, locks, configuración de pipes, control de terminales y operaciones específicas de dispositivos.

Ese poder exige cuidado. Los comandos, constantes y layouts de estructuras C cambian por sistema operativo y arquitectura. Un buffer con tamaño o tipo incorrecto puede corromper memoria o terminar el proceso. Prefiere APIs de mayor nivel y aísla las llamadas nativas en funciones pequeñas y probadas.

Disponibilidad

fcntl está disponible en Unix y no funciona en WASI. Windows usa APIs distintas. Software portable debe desactivar la característica o proporcionar una implementación específica verificada.

try:
    import fcntl
except ImportError:
    fcntl = None

No presentes un fallback incompleto como equivalente. Un lock con semántica incorrecta puede permitir escrituras simultáneas sobre el mismo estado.

File descriptors

Las funciones aceptan un entero o un objeto de I/O cuyo fileno() devuelve un descriptor real.

with open("datos.txt", "a+", encoding="utf-8") as archivo:
    fd = archivo.fileno()
    print(fd)

El sistema puede reutilizar el número después del cierre. No guardes un fd y continúes usándolo tras cerrar su objeto.

Lock exclusivo con flock()

flock() es la interfaz más clara para bloquear un archivo completo.

import fcntl

with open("estado.lock", "a+") as archivo:
    fcntl.flock(archivo, fcntl.LOCK_EX)
    try:
        actualizar_estado()
    finally:
        fcntl.flock(archivo, fcntl.LOCK_UN)

LOCK_EX solicita exclusividad y LOCK_SH permite lectores cooperantes. Normalmente son locks advisory: todos los procesos deben usar la misma convención.

Adquisición no bloqueante

Combina LOCK_NB para fallar inmediatamente.

import errno
import fcntl

try:
    fcntl.flock(archivo, fcntl.LOCK_EX | fcntl.LOCK_NB)
except OSError as exc:
    if exc.errno in {errno.EACCES, errno.EAGAIN}:
        print("Otra instancia está activa")
    else:
        raise

Comprueba ambos códigos por portabilidad. errno en Python explica estos errores.

Un archivo con PID no es un lock

Guardar el PID ayuda al diagnóstico, pero no crea exclusión. Los PIDs se reutilizan y el archivo puede quedar después de un crash.

import fcntl
import os

lock = open("app.lock", "a+")
fcntl.flock(lock, fcntl.LOCK_EX | fcntl.LOCK_NB)
lock.seek(0)
lock.truncate()
lock.write(str(os.getpid()))
lock.flush()

Mantén el descriptor abierto durante todo el período protegido. Cerrarlo normalmente libera el lock.

Regiones con lockf()

lockf() envuelve record locking y puede proteger una región de bytes.

import fcntl
import os

with open("base.dat", "r+b") as archivo:
    fcntl.lockf(archivo, fcntl.LOCK_EX, 128, 0, os.SEEK_SET)
    try:
        archivo.seek(0)
        actualizar_registro(archivo)
    finally:
        fcntl.lockf(archivo, fcntl.LOCK_UN, 128, 0, os.SEEK_SET)

La semántica cambia por sistema y filesystem. Prueba NFS, volúmenes de containers, almacenamiento compartido y filesystems especiales en el entorno real.

flock() o lockf()

Usa flock() para exclusión del archivo completo. Usa lockf() cuando necesitas rangos y todos los participantes siguen el mismo modelo. No asumas interoperabilidad.

Leer flags de estado

F_GETFL devuelve las flags actuales.

import fcntl
import os

flags = fcntl.fcntl(fd, fcntl.F_GETFL)
print(bool(flags & os.O_NONBLOCK))

Para cambiar un bit, conserva los demás.

fcntl.fcntl(fd, fcntl.F_SETFL, flags | os.O_NONBLOCK)

Reemplazar toda la máscara puede eliminar comportamiento importante.

Close-on-exec

FD_CLOEXEC evita que un descriptor se filtre a un programa iniciado con exec.

flags = fcntl.fcntl(fd, fcntl.F_GETFD)
fcntl.fcntl(fd, fcntl.F_SETFD, flags | fcntl.FD_CLOEXEC)

Python moderno crea muchos descriptors como no heredables, pero software que recibe descriptors externos todavía debe definir ownership.

Duplicación

F_DUPFD y variantes con CLOEXEC crean otra referencia a la misma open file description. Las copias pueden compartir offset y flags.

Cerrar un descriptor no libera el recurso mientras existan duplicados abiertos.

ioctl()

ioctl() envía requests específicos a dispositivos y terminales.

import array
import fcntl
import termios

buffer = array.array("h", [0])
fcntl.ioctl(0, termios.TIOCGPGRP, buffer, True)
print(buffer[0])

El número de request y el layout deben venir de la documentación C de la plataforma exacta. Copiar una estructura de otra arquitectura puede provocar crash.

Buffers mutables

Con bytearray, array.array u otro buffer escribible y mutate_flag=True, la syscall puede modificar el objeto. Python puede usar un área interna de 1024 bytes, pero el tamaño lógico debe ser correcto.

La documentación advierte que una incompatibilidad puede causar segmentation fault o corrupción sutil.

struct.pack() con cautela

Algunas operaciones esperan una estructura C.

import struct

payload = struct.pack("hhllhh", tipo, whence, inicio, longitud, pid, 0)

Alineación, tamaños, endianess y campos cambian. Una format string encontrada en internet no es portable. Prefiere flock(), un wrapper mantenido o una extensión compilada con los headers correctos.

Límite de 1024 bytes

Cuando fcntl() recibe bytes-like, el resultado tiene el mismo tamaño y un máximo de 1024 bytes. No hagas segura una operación grande adivinando otro tamaño.

Llamadas interrumpidas

En Python 3.14, ioctl() libera el GIL durante la syscall y reintenta automáticamente EINTR. Otros errores siguen generando OSError.

Capacidad de pipes

Linux puede exponer F_GETPIPE_SZ y F_SETPIPE_SZ.

if hasattr(fcntl, "F_GETPIPE_SZ"):
    capacidad = fcntl.fcntl(pipe_fd, fcntl.F_GETPIPE_SZ)
    print(capacidad)

Aumentarla puede requerir privilegios y consume memoria del kernel. No sustituye backpressure. Consulta select en Python.

Seals en memfd

Linux puede ofrecer F_ADD_SEALS, F_GET_SEALS y F_SEAL_* para descriptors de os.memfd_create(). Los seals pueden impedir escritura, reducción, crecimiento o mapeos futuros.

No son portables y algunas decisiones son irreversibles.

FICLONE y FICLONERANGE pueden crear clones copy-on-write en filesystems compatibles. Pueden fallar entre dispositivos, en red o en formatos no soportados.

Implementa fallback de copia normal y valida el destino.

Open file description locks

Linux ofrece F_OFD_SETLK y constantes relacionadas. Estos locks pertenecen a la open file description y se comportan distinto con duplicados, fork y threads.

Úsalos solo cuando necesitas esa semántica. Para exclusión simple, flock() es más claro.

Auditoría

Las llamadas generan eventos como fcntl.fcntl, fcntl.ioctl, fcntl.flock y fcntl.lockf. Entornos restringidos pueden registrarlos o rechazarlos.

Seguridad

No aceptes directamente de un usuario el request, cmd, fd o estructura empaquetada. Eso puede exponer dispositivos y recursos del proceso.

Usa allowlist, verifica el tipo de descriptor y ejecuta con mínimos privilegios.

Pruebas

Prueba procesos competidores, limpieza tras excepción, crash, filesystem local y remoto, descriptors cerrados, herencia en subprocesses, non-blocking, builds 32/64 bits, constantes ausentes y permisos.

Los locks deben probarse con procesos separados, no solo threads.

Errores comunes

Los fallos frecuentes son olvidar que los locks son advisory, cerrar el archivo demasiado pronto, mezclar flock y lockf, asumir que NFS es igual al disco local, sobrescribir flags, reutilizar fd cerrado, copiar estructuras de otra plataforma y pasar buffers con tamaño incorrecto.

Conclusión

fcntl proporciona control Unix de bajo nivel sobre archivos, pipes, locks y dispositivos. Prefiere flock() para exclusión simple. Para fcntl() e ioctl(), sigue exactamente los headers y manuales de la plataforma.

Valida constantes, conserva flags, minimiza privilegios y prueba en producción. Consulta la documentación oficial de fcntl, fcntl(2) y ioctl(2).

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

    Rack de servidores que representa el balanceo de conexiones con SO_REUSEPORT_LB en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    SO_REUSEPORT_LB: reparte conexiones entre workers

    Aprende SO_REUSEPORT_LB en Python para distribuir conexiones entre workers con pruebas, portabilidad y cierre ordenado.

    Ler mais

    Tempo de leitura: 5 minutos
    11/10/2026
    Código Python asíncrono en un portátil para inspect.markcoroutinefunction
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    markcoroutinefunction: detecta wrappers async

    Aprende inspect.markcoroutinefunction en Python para identificar wrappers asíncronos, integrar frameworks y evitar detecciones incorrectas.

    Ler mais

    Tempo de leitura: 5 minutos
    10/10/2026
    Código Python para recorrer carpetas y archivos con Path.walk
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    Path.walk: recorre directorios con seguridad

    Aprende Path.walk en Python para recorrer directorios, filtrar archivos, tratar errores y controlar la travesía con seguridad.

    Ler mais

    Tempo de leitura: 5 minutos
    10/10/2026
    Depuración de un proceso Python en terminal con código
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    pdb -p: depura procesos Python en ejecución

    Aprende a conectar pdb a un proceso Python en ejecución, inspeccionar la pila y diagnosticar bloqueos de forma segura.

    Ler mais

    Tempo de leitura: 6 minutos
    09/10/2026
    Código Python para representar fracciones exactas
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    fractions.from_number: convierte números en fracciones

    Aprende fractions.from_number en Python para convertir números en fracciones exactas, controlar precisión, validar entradas y evitar redondeos inesperados.

    Ler mais

    Tempo de leitura: 4 minutos
    09/10/2026
    Desarrollador configurando un servidor HTTPS y certificado TLS con Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    HTTPSServer: crea un servidor HTTPS local en Python

    Aprende HTTPSServer en Python para crear servicios HTTPS locales, configurar certificados, usar hilos y comprender sus límites.

    Ler mais

    Tempo de leitura: 4 minutos
    08/10/2026