Declaraciones públicas de src/safety/fs.zig (zkit (SDK consumido)).
apps/docs/generated/zig/zkit.jsonPágina generada desde
zkit@zkit-0.0.0-_fk2iAa4BQCqGFKJeeY9WvMQClX5kiZQ2iOZrVsxVyyN (src/root.zig). No se edita a mano:bun run docs:genla regenera ybun run docs:checkfalla si difiere.
zkit.safety.fs — abrir ficheros controlados por un cliente dentro de una raíz, sin path traversal, sin symlinks que escapen y sin TOCTOU.
El patrón clásico (el de styx/local_file.zig, que es el origen del path
guard de aquí): rechazar .. léxicamente, realpath de la ruta y de la
raíz, comprobar el prefijo con frontera, open(O_NOFOLLOW) y comparar
(dev, ino) del fd con los del realpath. Cierra casi todo, pero sigue
siendo RESOLUCIÓN POR RUTA: entre el realpath y el open un componente
INTERMEDIO puede cambiarse por un symlink (O_NOFOLLOW sólo protege el
último), y la comprobación de identidad llega después del open.
Root resuelve RELATIVO A UN DESCRIPTOR de la raíz, que se abre una vez
(configuración del operador) y ya no depende de ninguna ruta:
openat2(root_fd, rel, RESOLVE_BENEATH | …). El kernel
resuelve TODA la ruta atómicamente y garantiza que no sale de la raíz
(ni por .., ni por symlink absoluto o relativo, ni por /proc
magic-links). Sin ventana TOCTOU: es una sola syscall.openat(dirfd, comp, O_NOFOLLOW | O_DIRECTORY). Cada paso se hace sobre el fd del anterior; un symlink
en cualquier componente es error. Tampoco hay ventana: nunca se
vuelve a resolver una ruta.Política de symlinks (Symlinks):
.reject (defecto): ningún componente puede ser symlink..beneath: se siguen symlinks mientras el resultado quede dentro de
la raíz (sólo con openat2; sin él cae a .reject, que es más
estricto, nunca a menos).Y además: require_regular (por defecto) rechaza directorios, FIFOs y
dispositivos (error.NotRegularFile): un FIFO bajo la raíz bloquearía un
lector para siempre.
Montajes (Mounts): por defecto (.same) una apertura no cruza ningún
punto de montaje bajo la raíz, tampoco un bind mount de un fichero de
fuera sobre un nombre de dentro (mismo dev, así que comparar dev no lo
ve). openat2 lleva RESOLVE_NO_XDEV; además, en Linux, el mnt_id
(statx) de lo abierto tiene que ser el de la raíz, lo que cubre también
el recorrido, que por sí solo no distingue un montaje. .cross lo relaja
para la raíz que lo necesite (bind mounts dentro de una biblioteca).
Entradas directas de la raíz (un servidor que gestiona su directorio de
staging): deleteEntry, renameEntry, entries y syncDir. Sólo
aceptan UN componente (validateEntryName: sin /, ni ./..), así
que no hay ningún componente intermedio que resolver ni que cambiar por
un symlink; y unlinkat/renameat actúan sobre el enlace, nunca lo
siguen: un symlink plantado con ese nombre se borra o se reemplaza, lo
que apunta queda intacto.
safety.fs.PathErrorconst · línea 57
pub const PathError = error{ /// Ruta vacía. EmptyPath, /// Ruta absoluta donde se exige relativa a la raíz. AbsolutePath, /// Un componente `..`. ParentReference, /// Byte NUL embebido o ruta más l …Sin ///.
safety.fs.validateRelativefn · línea 72
pub fn validateRelative(path: []const u8) PathError!voidValidación LÉXICA de una ruta relativa a la raíz. Rechaza vacío, /
inicial, componentes .. y NUL. . y // se toleran (no escapan).
safety.fs.relativeTofn · línea 89
pub fn relativeTo(root: []const u8, abs: []const u8) PathError![]const u8Parte relativa de abs bajo root (ambas absolutas), con frontera de
componente: /media/a NO contiene a /media/ab (el bug de prefijo que
styx arregló en TKT-009). Léxico: no resuelve symlinks — eso lo hace
Root. error.PathEscapesRoot si no está debajo.
safety.fs.EntryNameErrorconst · línea 101
pub const EntryNameError = PathError || error{ /// No es UNA entrada directa de la raíz: lleva `/`, o es `.`/`..`. NotAnEntryName, }Sin ///.
safety.fs.validateEntryNamefn · línea 108
pub fn validateEntryName(name: []const u8) EntryNameError!voidValidación léxica de un nombre de entrada directa de la raíz: un solo
componente, no vacío, sin /, sin NUL, distinto de . y ...
safety.fs.DeleteErrorconst · línea 115
pub const DeleteError = EntryNameError || error{ FileNotFound, AccessDenied, IsDir, FileBusy, ReadOnlyFileSystem, SystemResources, Unexpected, }Sin ///.
safety.fs.RenameErrorconst · línea 125
pub const RenameError = EntryNameError || error{ FileNotFound, AccessDenied, IsDir, NotDir, DirNotEmpty, FileBusy, NoSpaceLeft, ReadOnlyFileSystem, SystemResources, Unexpected, }Sin ///.
safety.fs.SyncDirErrorconst · línea 138
pub const SyncDirError = zfs.OpenError || zfs.WriteErrorSin ///.
safety.fs.EntriesErrorconst · línea 140
pub const EntriesError = zfs.OpenError || error{SystemResources}Sin ///.
safety.fs.FileIdentitytype · línea 143
pub const FileIdentity = structIdentidad de un objeto del sistema de ficheros (no de su ruta).
safety.fs.FileIdentity.ofFilefn · línea 147
pub fn ofFile(f: zfs.File) zfs.StatError!FileIdentitySin ///.
safety.fs.FileIdentity.eqlfn · línea 152
pub fn eql(a: FileIdentity, b: FileIdentity) boolSin ///.
safety.fs.Symlinkstype · línea 157
pub const Symlinks = enumSin ///.
safety.fs.Resolvertype · línea 159
pub const Resolver = enumSin ///.
safety.fs.Mountstype · línea 168
pub const Mounts = enum¿Puede una apertura cruzar un punto de montaje bajo la raíz?
safety.fs.Accesstype · línea 176
pub const Access = enumSin ///.
safety.fs.OpenOptionstype · línea 178
pub const OpenOptions = structSin ///.
safety.fs.CreateOptionstype · línea 186
pub const CreateOptions = structSin ///.
safety.fs.Errorconst · línea 196
pub const Error = zfs.OpenError || zfs.StatError || PathError || error{ /// Un componente es un symlink y la política lo prohíbe. SymlinkRejected, /// No es un fichero regular (directorio, FIFO, dis …Sin ///.
safety.fs.Openedtype · línea 203
pub const Opened = structSin ///.
safety.fs.Opened.identityfn · línea 207
pub fn identity(o: Opened) FileIdentitySin ///.
safety.fs.Roottype · línea 212
pub const Root = structSin ///.
safety.fs.Root.openfn · línea 222
pub fn open(path: []const u8) Error!RootAbre la raíz (ruta del OPERADOR, p. ej. STYX_MEDIA_ROOT). La raíz
en sí puede ser un symlink (lo decide quien configura); lo que hay
DEBAJO se rige por la política de cada apertura.
safety.fs.Root.OwnedErrorconst · línea 232
pub const OwnedError = Error || zfs.MakeDirError || error{ /// El directorio es de otro uid: quien lo creó antes que este /// proceso decide qué hay dentro (symlinks o ficheros plantados). NotOwned, …Sin ///.
safety.fs.Root.OwnedOptionstype · línea 241
pub const OwnedOptions = structSin ///.
safety.fs.Root.openOwnedfn · línea 258
pub fn openOwned(path: []const u8, opts: OwnedOptions) OwnedError!RootAbre un directorio PRIVADO del proceso (estado, caché, staging: lo
que nadie más debe leer ni escribir) como raíz, y lo crea 0700 si no
existe. A diferencia de open, la ruta la fija el proceso y un
directorio que ya estaba ahí no se acepta sin más: con un nombre
predecible en un directorio compartido (/tmp/x), otro usuario lo
crea antes y es suyo. Se exige:
NotOwned si no);AccessibleByOthers).
La comprobación es sobre el fd ya abierto, no sobre la ruta: lo que
se valida es lo que se usará. Las entradas se crean después con
createFile (O_EXCL, 0600 por defecto) y se borran con deleteEntry.safety.fs.Root.closefn · línea 280
pub fn close(self: *Root) voidSin ///.
safety.fs.Root.openFilefn · línea 286
pub fn openFile(self: *const Root, rel: []const u8, opts: OpenOptions) Error!OpenedAbre rel (relativa a la raíz) según opts.
safety.fs.Root.createFilefn · línea 304
pub fn createFile(self: *const Root, rel: []const u8, opts: CreateOptions) Error!zfs.FileCrea rel bajo la raíz. Los directorios intermedios deben existir
(y no ser symlinks). Con exclusive un symlink plantado en el nombre
final hace fallar la creación (PathAlreadyExists), no la redirige.
safety.fs.Root.makeDirfn · línea 312
pub fn makeDir(self: *const Root, rel: []const u8, opts: zfs.MakeDirOptions) (Error || zfs.MakeDirError)!voidCrea el directorio rel bajo la raíz (padres deben existir).
safety.fs.Root.deleteEntryfn · línea 326
pub fn deleteEntry(self: *const Root, name: []const u8) DeleteError!voidBorra la entrada name de la raíz (unlinkat, relativo al fd). Un
symlink con ese nombre se borra él, sin seguirlo; un directorio es
error.IsDir (no borra árboles) en toda plataforma.
safety.fs.Root.renameEntryfn · línea 341
pub fn renameEntry(self: *const Root, from: []const u8, to: []const u8) RenameError!voidRenombra la entrada from a to, las dos directas de la raíz
(renameat, atómico). Si to existe se reemplaza — también un
symlink plantado con ese nombre: se sustituye el enlace, lo que
apuntaba no se toca. Un from symlink se renombra como enlace.
safety.fs.Root.syncDirfn · línea 370
pub fn syncDir(self: *const Root) SyncDirError!voidfsync del directorio raíz: hace durables los createFile,
renameEntry y deleteEntry ya hechos. El fd de la raíz es O_PATH
en Linux (sólo búsqueda) y no se puede sincronizar: abre . debajo
para lectura, sincroniza y cierra.
safety.fs.Root.SpaceErrorconst · línea 376
pub const SpaceError = error{ /// Sólo Linux de 64 bits: en otro sistema no hay `fstatfs` de este /// módulo (quien llama decide si eso es fallar cerrado). Unsupported, Unexpected, }Sin ///.
safety.fs.Root.availableBytesfn · línea 387
pub fn availableBytes(self: *const Root) SpaceError!u64Bytes que un proceso sin privilegios puede escribir aún en el sistema
de ficheros de la raíz (f_bavail × f_bsize de fstatfs sobre el fd
de la raíz, el mismo que usan las aperturas: no se resuelve ninguna
ruta). Satura en maxInt(u64).
safety.fs.Root.entriesfn · línea 400
pub fn entries(self: *const Root) EntriesError!EntriesRecorre las entradas directas de la raíz (sin . ni ..). Cierra el
iterador con close. Borrar con deleteEntry la entrada que se acaba
de recibir es seguro; si una entrada creada o borrada por otro durante
el recorrido aparece o no, POSIX no lo fija.
safety.fs.Root.openAbsolutefn · línea 411
pub fn openAbsolute(self: *const Root, root_path: []const u8, abs: []const u8, opts: OpenOptions) Error!OpenedAtajo: abs debe estar léxicamente bajo root_path (la misma ruta
con la que se abrió la raíz), y se abre su parte relativa.
safety.fs.Entriestype · línea 482
pub const Entries = structIterador de Root.entries.
safety.fs.Entries.nextfn · línea 489
pub fn next(self: *Entries) ?[]const u8Nombre de la siguiente entrada, o null al acabar (o si el sistema
falla leyendo: no hay forma de distinguirlo sin errno, y para quien
barre un directorio es lo mismo). El slice vale hasta el siguiente
next o close.
safety.fs.Entries.closefn · línea 498
pub fn close(self: *Entries) voidSin ///.