O módulo fcntl oferece acesso direto às chamadas Unix fcntl() e ioctl(), além de helpers para locking de arquivos. Ele trabalha com file descriptors e expõe recursos que não existem na API de arquivos de alto nível, como alterar flags, consultar propriedades do terminal, controlar pipes e coordenar processos.
Esse poder exige cuidado. Comandos, estruturas e constantes variam por sistema operacional. Um buffer com tipo ou tamanho incorreto pode causar corrupção de dados ou até segfault. Por isso, use as funções de mais alto nível sempre que possível e isole chamadas nativas em código pequeno e testado.
Disponibilidade
fcntl está disponível em Unix e não funciona em WASI. Windows possui APIs diferentes. Código multiplataforma deve oferecer alternativa ou desativar o recurso.
try:
import fcntl
except ImportError:
fcntl = None
Não esconda a diferença com um fallback incompleto. Um lock de arquivo com semântica errada pode permitir duas instâncias modificando o mesmo estado.
File descriptors
As funções aceitam um inteiro retornado por fileno() ou um objeto de I/O com esse método.
with open("dados.txt", "a+", encoding="utf-8") as arquivo:
fd = arquivo.fileno()
print(fd)
O número pode ser reutilizado depois do fechamento. Nunca guarde um fd e continue usando após fechar o objeto correspondente.
Lock exclusivo com flock()
flock() é a interface mais simples para bloquear um arquivo inteiro.
import fcntl
with open("estado.lock", "a+") as arquivo:
fcntl.flock(arquivo, fcntl.LOCK_EX)
try:
atualizar_estado()
finally:
fcntl.flock(arquivo, fcntl.LOCK_UN)
LOCK_EX solicita exclusividade. LOCK_SH permite múltiplos leitores. O lock normalmente é advisory: outros processos precisam cooperar usando o mesmo mecanismo.
Lock não bloqueante
Combine LOCK_NB para falhar imediatamente.
import errno
import fcntl
try:
fcntl.flock(arquivo, fcntl.LOCK_EX | fcntl.LOCK_NB)
except OSError as exc:
if exc.errno in {errno.EACCES, errno.EAGAIN}:
print("Outra instância está executando")
else:
raise
Confira EACCES e EAGAIN por portabilidade. O artigo de errno no Python detalha códigos de sistema.
Arquivo de lock e PID
Um arquivo contendo PID pode ajudar no diagnóstico, mas não substitui o lock. PIDs são reutilizados e o arquivo pode permanecer após 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()
Mantenha o arquivo aberto durante todo o período protegido. Fechar o descriptor normalmente libera o lock.
lockf() e regiões
lockf() envolve locks de registro do fcntl() e pode proteger uma região.
import fcntl
import os
with open("banco.dat", "r+b") as arquivo:
fcntl.lockf(arquivo, fcntl.LOCK_EX, 128, 0, os.SEEK_SET)
try:
arquivo.seek(0)
atualizar_registro(arquivo)
finally:
fcntl.lockf(arquivo, fcntl.LOCK_UN, 128, 0, os.SEEK_SET)
A semântica varia entre sistemas e filesystems. Locks em NFS, containers, volumes compartilhados e filesystems especiais precisam de testes reais.
flock() ou lockf()?
flock() costuma ser melhor para exclusão de arquivo inteiro. lockf() é útil para regiões, mas depende mais das regras de record locking. Não misture mecanismos supondo que eles interoperam.
Documente qual mecanismo todos os processos devem usar.
Ler flags do descriptor
fcntl.fcntl() com F_GETFL consulta flags de status.
import fcntl
import os
flags = fcntl.fcntl(fd, fcntl.F_GETFL)
print(bool(flags & os.O_NONBLOCK))
Para alterar, recupere o valor, ajuste bits e use F_SETFL.
fcntl.fcntl(fd, fcntl.F_SETFL, flags | os.O_NONBLOCK)
Preserve bits desconhecidos. Não substitua todas as flags por uma constante isolada.
FD_CLOEXEC
O flag close-on-exec impede que um descriptor vaze para um novo programa após exec.
flags = fcntl.fcntl(fd, fcntl.F_GETFD)
fcntl.fcntl(fd, fcntl.F_SETFD, flags | fcntl.FD_CLOEXEC)
Python moderno cria muitos descriptors como não herdáveis por padrão, mas código que recebe descriptors externos deve verificar ownership e herança.
Duplicar descriptors
Constantes como F_DUPFD e variantes com CLOEXEC criam outra referência ao mesmo open file description.
O novo descriptor compartilha offset e algumas flags. Fechar um não fecha o outro, mas o recurso só é liberado após todas as referências serem fechadas.
ioctl()
ioctl() envia operações específicas a dispositivos, terminais e drivers.
import array
import fcntl
import termios
buffer = array.array("h", [0])
fcntl.ioctl(0, termios.TIOCGPGRP, buffer, True)
print(buffer[0])
O request e o layout do buffer vêm da documentação C da plataforma. Copiar um formato de outra arquitetura pode causar erro grave.
Buffers mutáveis
Com bytearray, array.array ou outro buffer gravável e mutate_flag=True, o kernel pode alterar o objeto no lugar. Se o buffer for curto, o módulo usa internamente uma área de até 1024 bytes e copia de volta.
O tamanho ainda precisa ser correto. A documentação alerta que incompatibilidade pode causar segfault ou corrupção sutil.
struct.pack() com cautela
Alguns comandos esperam estruturas C.
import struct
estrutura = struct.pack("hhllhh", tipo, whence, inicio, tamanho, pid, 0)
O layout depende de alinhamento, tamanho de tipos, arquitetura e sistema. Não trate uma format string encontrada na internet como portável. Prefira wrappers de alto nível ou compile uma extensão que use os headers corretos.
Limite de 1024 bytes
Quando fcntl() recebe um argumento bytes-like, o retorno tem o mesmo tamanho e é limitado a 1024 bytes. Uma operação que devolve estrutura maior não pode ser usada com segurança dessa forma.
Não tente contornar passando tamanho incorreto.
Chamadas interrompidas
No Python 3.14, ioctl() libera o GIL durante a syscall e repete automaticamente falhas por EINTR. Mesmo assim, erros de dispositivo e argumentos continuam gerando OSError.
Pipes
Em Linux, F_GETPIPE_SZ e F_SETPIPE_SZ consultam e alteram capacidade de pipe quando suportados.
if hasattr(fcntl, "F_GETPIPE_SZ"):
tamanho = fcntl.fcntl(fd_pipe, fcntl.F_GETPIPE_SZ)
print(tamanho)
Aumentar o tamanho pode exigir privilégios e consumir memória do kernel. Não use como substituto de backpressure. O guia de select no Python explica I/O não bloqueante.
Seals em memfd
Linux pode expor F_ADD_SEALS, F_GET_SEALS e constantes F_SEAL_* para impedir alterações em descriptors criados com os.memfd_create().
Seals podem restringir escrita, crescimento, redução e futuros mapeamentos graváveis. Eles não são portáveis e algumas operações são irreversíveis.
Reflink
Em Linux recente, FICLONE e FICLONERANGE podem criar cópias copy-on-write em filesystems compatíveis. A operação pode falhar em ext4 antigo, filesystem remoto ou entre dispositivos.
Tenha fallback para cópia normal e verifique o resultado.
Open file description locks
Constantes F_OFD_SETLK e relacionadas permitem locks associados à open file description em Linux. Eles têm semântica diferente dos locks tradicionais relacionados ao processo.
Use somente quando a aplicação compreende duplicação de fd, fork e threads. Para exclusão simples, flock() continua mais claro.
Auditoria
As operações geram eventos de auditoria como fcntl.fcntl, fcntl.ioctl, fcntl.flock e fcntl.lockf. Ambientes restritos podem registrar ou impedir chamadas.
Segurança
Não aceite diretamente de usuário o número do request, cmd, fd ou bytes de estrutura. Isso pode transformar a aplicação em uma interface para controlar dispositivos ou memória.
Use allowlist de operações, valide o tipo do recurso e execute com o menor privilégio possível.
Testes
Teste lock concorrente em processos separados, liberação após exceção, crash, filesystem local e remoto, descriptors fechados, herança em subprocessos, non-blocking, arquitetura 32/64 bits, constants ausentes e permissões.
Testes de locking precisam de processos, não apenas threads, para reproduzir a semântica real.
Erros comuns
Os erros mais frequentes são esquecer que locks são advisory, fechar o arquivo cedo, misturar flock e lockf, presumir NFS igual a disco local, substituir flags existentes, reutilizar fd fechado, usar estrutura C de outra plataforma e passar buffers com tamanho errado.
Conclusão
fcntl oferece controle Unix de baixo nível sobre arquivos, pipes, locks e dispositivos. Para locking simples, prefira flock() com contextos claros. Para fcntl() e ioctl(), siga exatamente os headers e manuais da plataforma.
Valide constantes, preserve flags, limite privilégios e teste no sistema de produção. Consulte a documentação oficial de fcntl e os manuais fcntl(2) e ioctl(2).







