fcntl no Python: locks e controle de arquivos

Publicado em: 25/08/2026
Tempo de leitura: 6 minutos
Detailed close-up of a combination lock with numbers in focus, highlighting security and privacy.

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.

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

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Conteúdo do artigo

    Artigos relacionados

    Close-up view of a computer screen displaying code in a software development environment.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    readline no Python: histórico e autocomplete

    Aprenda readline no Python para histórico, autocomplete, edição de linha, GNU Readline, libedit e prompts seguros no terminal.

    Ler mais

    Tempo de leitura: 6 minutos
    25/08/2026
    Serene stream flowing through Bavarian mountains, capturing winter beauty and natural tranquility.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    io no Python: domine streams e buffers

    Aprenda io no Python para trabalhar com streams de texto e bytes, buffering, encoding, StringIO, BytesIO e I/O bruto com

    Ler mais

    Tempo de leitura: 7 minutos
    24/08/2026
    Business professional analyzing financial data on multiple computer monitors at his workspace.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    select no Python: monitore vários I/Os

    Aprenda select no Python para monitorar sockets e pipes, tratar leituras parciais, backpressure, epoll, poll e sinais sem busy loop.

    Ler mais

    Tempo de leitura: 7 minutos
    24/08/2026
    View of multiple railway tracks with signals and buildings in an urban setting during daytime.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    signal no Python: encerre processos bem

    Aprenda signal no Python para tratar SIGTERM e SIGINT, encerrar serviços, usar timers, wakeup FD e coordenar shutdown sem deadlocks.

    Ler mais

    Tempo de leitura: 7 minutos
    24/08/2026
    Detailed view of programming code in a dark theme on a computer screen.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    errno no Python: entenda erros do sistema

    Aprenda errno no Python para interpretar códigos do sistema, tratar OSError, rede, arquivos, retries e chamadas nativas de forma portátil.

    Ler mais

    Tempo de leitura: 6 minutos
    24/08/2026
    A developer typing code on a laptop with a Python book beside in an office.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    ctypes no Python: use bibliotecas C

    Aprenda ctypes no Python para carregar bibliotecas C, definir tipos e ponteiros, gerenciar memória, callbacks, ABI e erros com segurança.

    Ler mais

    Tempo de leitura: 6 minutos
    24/08/2026