Inodos: la identidad real de un archivo en el filesystem

Cuando ejecutas ls y ves un nombre de archivo, estás viendo una etiqueta, no el archivo en sí. El archivo real es un inodo — una estructura de datos que vive en el filesystem y contiene todo lo que describe a ese archivo excepto su nombre. Esta distinción, que parece filosófica al principio, tiene consecuencias muy concretas en el comportamiento del sistema.

Un inodo almacena permisos, propietario, grupo, timestamps (atime, mtime, ctime), el tamaño, y punteros a los bloques de disco donde residen los datos. Lo que no almacena es el nombre. El nombre existe en el directorio, que no es más que una tabla que mapea cadenas de texto a números de inodo. Cada entrada de esa tabla se llama entrada de directorio o dentry. Cuando ejecutas open("/etc/passwd"), el kernel recorre esa ruta componente a componente, resolviendo cada nombre a un número de inodo hasta llegar al inodo final, que entonces lee para obtener los permisos y la ubicación de los datos.

El diseño es así por una razón que tiene décadas: separar identidad de nombres permite que el mismo inodo tenga múltiples nombres simultáneamente (los hard links), que mover un archivo dentro del mismo filesystem sea instantáneo — solo se reescribe la entrada de directorio, no se mueven datos —, y que el kernel gestione el ciclo de vida del inodo de forma independiente a cómo lo llames. Los permisos que ves con ls -l están en el inodo, no en el nombre; renombrar un archivo no cambia sus permisos porque no toca el inodo.

La segunda columna de ls -l es el contador de links (link count): el número de entradas de directorio que apuntan a ese inodo. Cuando ejecutas rm archivo, no borras el inodo — decrementas ese contador. El kernel libera los bloques de datos y el inodo solo cuando el contador llega a cero y ningún proceso tiene el archivo abierto. Por eso un proceso puede seguir escribiendo en un archivo de log que ya fue eliminado del directorio; el inodo sigue vivo mientras el descriptor de fichero esté abierto.

Si trabajas con un filesystem lleno de inodos pero con espacio libre disponible, no puedes crear archivos nuevos aunque df -h muestre gigabytes libres. Los inodos son un recurso finito, asignado en el momento de crear el filesystem con mkfs.ext4. df -i muestra cuántos quedan.

# Creamos tres archivos para observar el comportamiento de los inodos
mkdir -p /tmp/demo_inodos
cd /tmp/demo_inodos

echo "contenido original" > archivo_a.txt

# ls -i muestra el número de inodo antes del nombre
ls -i archivo_a.txt

# ls -l: la segunda columna es el link count (ahora vale 1)
ls -l archivo_a.txt

# Creamos un hard link: una segunda entrada de directorio → mismo inodo
ln archivo_a.txt alias_b.txt

# Ambos nombres muestran el MISMO número de inodo
ls -i archivo_a.txt alias_b.txt

# El link count ahora es 2 en ambas entradas (son el mismo inodo)
ls -l archivo_a.txt alias_b.txt

# Modificar el contenido a través de un nombre lo hace visible desde el otro
echo "línea añadida" >> alias_b.txt
cat archivo_a.txt   # verás también la línea añadida

# Eliminamos la entrada original del directorio
rm archivo_a.txt

# El inodo sigue vivo: alias_b.txt todavía funciona y el link count bajó a 1
ls -li alias_b.txt
cat alias_b.txt

# stat muestra todos los campos del inodo directamente
stat alias_b.txt

# Para verificar que un inodo fue liberado: crear un archivo, anotar su inodo,
# borrarlo y crear otro — el número puede reutilizarse
python3 -c "
import os, tempfile
# Abrimos el archivo y luego lo eliminamos del directorio
fd, path = tempfile.mkstemp(dir='.')
inode_before = os.fstat(fd).st_ino
os.unlink(path)          # desaparece del directorio, pero fd sigue abierto
still_accessible = os.fstat(fd).st_nlink  # link count = 0, inodo aún vivo
os.close(fd)             # aquí el kernel libera el inodo
print(f'inodo: {inode_before}, links mientras fd abierto: {still_accessible}')
"

# Comprobar inodos disponibles en el filesystem
df -i /tmp

Fíjate en lo que hace ls -i archivo_a.txt y ls -i alias_b.txt después del ln: los dos números son idénticos. No es una copia, no hay sincronización — es literalmente la misma estructura en disco apuntada desde dos sitios distintos del árbol de directorios. Cuando ls -l muestra 2 en la segunda columna, eso es el contador de links del inodo, no algo que pertenezca a ninguno de los dos nombres en particular.

La línea echo "línea añadida" >> alias_b.txt escribe directamente en los bloques de datos que el inodo referencia. Como archivo_a.txt apunta al mismo inodo, cat archivo_a.txt ve la modificación sin que haya habido ninguna sincronización: simplemente no hay dos objetos, solo uno con dos nombres.

El bloque de Python ilustra el caso menos obvio: cuando os.unlink(path) elimina la entrada del directorio, el link count cae a cero, pero os.fstat(fd) devuelve st_nlink == 0 y aun así funciona. El kernel mantiene el inodo porque el file descriptor cuenta como una referencia adicional que no aparece en st_nlink. Solo cuando os.close(fd) ejecuta y esa referencia cae, el kernel llama al destructor del inodo y libera los bloques. Este comportamiento es lo que hace que las rotaciones de logs con logrotate funcionen sin interrumpir procesos que siguen escribiendo.

stat alias_b.txt es más informativo que ls para esto: muestra el número de inodo, el número de links, los tres timestamps por separado (Access, Modify, Change) y el tamaño de bloque. El timestamp Change (ctime) se actualiza cada vez que cambia el inodo — permisos, propietario, link count — mientras que Modify (mtime) solo cambia cuando cambian los datos. Esta diferencia importa cuando usas herramientas de backup incrementales como rsync: rsync -a detecta cambios comparando mtime y tamaño, no el contenido.

El límite de inodos que muestra df -i es fijo para ext4 a menos que uses la opción resize2fs con flex_bg, o que hayas formateado con -T largefile para reducir la densidad de inodos a cambio de menos granularidad. En sistemas que generan millones de archivos pequeños — caches de paquetes, directorios de sesiones de PHP, nodos de builds — quedarse sin inodos es un fallo real que df -h no va a advertirte.

26

Dejar un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Scroll al inicio