Hard links: múltiples nombres para el mismo inodo

Cuando creas un archivo en Linux, el sistema genera un inodo — una estructura interna que almacena los metadatos del archivo (permisos, propietario, timestamps, punteros a los bloques de datos) y le asigna un número único dentro del filesystem. Lo que normalmente llamamos “nombre de archivo” no es más que una entrada de directorio: un mapeo entre un nombre legible y ese número de inodo. Un hard link es exactamente eso: una segunda entrada de directorio que apunta al mismo inodo que ya existe.

La implicación directa es que no hay un archivo “original” y uno “secundario”. Ambos nombres son referencias al inodo con exactamente el mismo peso. El kernel mantiene un contador de referencias (link count) en el inodo; cada nombre que apunta a él incrementa ese contador en 1. Cuando ejecutas rm sobre uno de los nombres, el kernel decrementa el contador. Los datos en disco solo se liberan cuando ese contador llega a cero, es decir, cuando desaparece el último nombre que referenciaba ese inodo. Antes de eso, borrar un nombre no borra nada más que la entrada de directorio.

Esto tiene una consecuencia que suele sorprender: si modificas el contenido del archivo usando cualquiera de sus nombres, estás modificando el mismo inodo y los mismos bloques de datos. No hay copias. Permisos, propietario, timestamps de modificación — todos son atributos del inodo, no del nombre, así que son idénticos desde ambos nombres sin ningún tipo de sincronización, porque literalmente son el mismo objeto.

¿Cuándo tiene sentido usarlo? Los dos casos clásicos son backups incrementales con herramientas como rsync --link-dest (que evita duplicar datos entre snapshots creando hard links hacia la versión anterior cuando el archivo no ha cambiado) y distribuir archivos de configuración base donde quieres que múltiples rutas lean siempre exactamente el mismo contenido sin mantener copias sincronizadas manualmente.

Dos limitaciones de diseño que no puedes saltarte: los hard links no pueden cruzar filesystems porque los números de inodo son locales a cada filesystem — un inodo 4821 en /dev/sda1 no tiene ninguna relación con un inodo 4821 en /dev/sdb1. Y los hard links no pueden apuntar a directorios en sistemas de archivos convencionales; permitirlo crearía ciclos en el árbol de directorios que romperían herramientas de traversal como find y el propio recolector de espacio del kernel. (El caso especial de . y .. los gestiona el kernel directamente, fuera del mecanismo normal de hard links.)

Si te equivocas con hard links — por ejemplo, asumes que borrar el “original” elimina el archivo — te encontrarás con datos que siguen ocupando espacio en disco sin ningún nombre visible en el directorio que consultaste. Diagnosticar eso sin saber que existen otros links es frustrante.

# Creamos un archivo de configuración base
echo "max_connections = 100" > /etc/app/base.conf

# Consultamos el inodo y el link count antes de nada
stat /etc/app/base.conf
# Output relevante: Inode: XXXXX  Links: 1

# Creamos el hard link: misma sintaxis que cp, pero apunta al mismo inodo
ln /etc/app/base.conf /etc/app/production.conf

# Verificamos: ambas entradas deben mostrar el mismo número de inodo
# y Links: 2
stat /etc/app/base.conf
stat /etc/app/production.conf

# ls -li muestra el inodo en la primera columna — deben ser idénticos
ls -li /etc/app/base.conf /etc/app/production.conf

# Modificamos el archivo desde uno de los nombres
echo "timeout = 30" >> /etc/app/production.conf

# El cambio es visible desde el otro nombre porque es el mismo inodo
cat /etc/app/base.conf
# Output: max_connections = 100
#         timeout = 30

# Borramos el nombre "original" — el inodo sigue vivo porque Links: 1
rm /etc/app/base.conf

stat /etc/app/production.conf
# Links: 1 — los datos siguen intactos, accesibles por production.conf

# Intento de hard link entre filesystems: falla por diseño
ln /etc/app/production.conf /tmp/cross_fs.conf
# ln: failed to create hard link '/tmp/cross_fs.conf' => '/etc/app/production.conf':
# Invalid cross-device link
# /tmp puede estar en tmpfs (distinto filesystem), de ahí el error

# find puede localizar todos los nombres que comparten un inodo
# -inum acepta el número que devolvió stat
INODE=$(stat -c '%i' /etc/app/production.conf)
find /etc -inum "$INODE"

El stat inicial antes de crear el link es un hábito útil: te muestra el número de inodo y Links: 1, lo que confirma el estado de partida. Después de ln, ambos stat devuelven exactamente el mismo número de inodo — no uno parecido, el mismo — y Links: 2. Eso es lo que diferencia un hard link de una copia: cp crearía un inodo nuevo con número distinto.

Fíjate en que la modificación via production.conf aparece inmediatamente en base.conf sin ninguna operación de sincronización. No hay nada que sincronizar porque no existe dualidad: hay un único inodo con dos nombres en la tabla de directorios.

El rm /etc/app/base.conf borra la entrada de directorio y decrementa el link count a 1, pero el inodo con todos sus datos sigue intacto. El stat posterior lo confirma: Links: 1. Si en este punto también ejecutaras rm /etc/app/production.conf, el link count llegaría a 0 y el kernel marcaría los bloques como disponibles para reutilización.

El error de cross-device link no es un bug ni una restricción arbitraria del comando ln — lo devuelve el syscall link(2) directamente desde el kernel. Puedes comprobarlo con strace ln /etc/app/production.conf /tmp/cross_fs.conf y verás el EXDEV (error 18) en la llamada al sistema.

El find -inum al final es la herramienta correcta para diagnosticar situaciones donde sospechas que un archivo tiene más nombres de los esperados — mucho más fiable que buscar por nombre cuando el archivo puede haberse copiado con distinto nombre en otro directorio del mismo filesystem.

27

Dejar un comentario

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

Scroll al inicio