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







