Pular para o conteúdo

Erros e errno

O C não levanta: devolve -1 ou NULL, e deixa o motivo em errno. C.errno lê o motivo com nome e mensagem.

Uma função C não tem exceção. Ela avisa que falhou pelo retorno-1, NULL, zero — e deixa o motivo numa variável global chamada errno: ENOENT (não existe), EACCES (sem permissão), EINTR (interrompida por sinal)…

dataforge
adopt Arcane.C as C

libc := C.padrao()
abrir := libc.funcao("open", ["texto", "i32"], "i32")

C.zerar_errno()
fd := abrir("/este/caminho/nao/existe", 0)
assert fd is -1                              // o retorno diz QUE falhou

erro := C.errno()                            // o errno diz POR QUE
assert erro["nome"] is "ENOENT"
out erro["mensagem"]

As regras do errno#

  • Só leia depois de uma falha. Numa chamada que deu certo, o errno pode ter qualquer valor — a norma do C não manda zerá-lo. Ler o errno de um sucesso é ler o erro de outra chamada.
  • Leia logo. Toda biblioteca aqui é aberta com use_errno: o ctypes guarda uma cópia por thread, logo depois de cada chamada. Sem isso, o errno seria sobrescrito pela próxima coisa que o próprio Python fizesse.
  • `zerar_errno` antes, quando o retorno é ambíguo. strtol devolve 0 tanto para "0" quanto para erro: zerar antes e olhar depois é o único jeito de distinguir.
dataforge
adopt Arcane.C as C

libc := C.padrao()
abrir := libc.funcao("open", ["texto", "i32"], "i32")

action abrir_ou_explicar(caminho):
    C.zerar_errno()
    fd := abrir(caminho, 0)
    given fd is -1:
        e := C.errno()
        trigger $"não abri '{caminho}': {e["mensagem"]} ({e["nome"]})"
    yield fd

motivo := void
monitor:
    abrir_ou_explicar("/nao/existe")
handle Error as e:
    motivo := e.message
assert motivo.contains("ENOENT")