1. De la cadena FAT al mapa por rangos
En FAT seguimos un enlace por cada clúster de la cadena. ext4 utiliza inodos y, habitualmente para los archivos modernos, extents: registros que describen rangos de bloques contiguos. No hace falta guardar una referencia individual para cada bloque de un tramo continuo.
Extent: correspondencia entre un intervalo de bloques lógicos de un archivo y un intervalo de bloques del volumen, con una longitud y un estado. Bloque lógico del archivo: posición relativa dentro del archivo, independiente de dónde estén sus datos en el volumen.
Extent A: lógico 0, longitud 3, físico 100
archivo 0 → volumen 100
archivo 1 → volumen 101
archivo 2 → volumen 102
Extent B: lógico 5, longitud 2, físico 200
archivo 5 → volumen 200
archivo 6 → volumen 201
Los bloques lógicos 3 y 4 no aparecen en estos extents.Para saber si un intervalo ausente representa un hueco legible o está fuera del archivo también necesitamos su tamaño. El mapa de asignación y el tamaño lógico responden preguntas diferentes.
2. Bloques del volumen y grupos
ext4 agrupa bloques para organizar asignación y metadatos y favorecer la localidad. Un bloque del sistema de archivos tampoco equivale necesariamente a un sector físico ni a una página de memoria. El tamaño se decide al crear el volumen.
En el esquema ordinario, un bitmap de un bloque puede representar hasta 8 × tamaño_del_bloque unidades. Con bloques de 4 KiB y 32768 bloques por grupo, el grupo abarca 128 MiB. Es un caso frecuente, no una regla universal: bigalloc cambia la unidad de asignación.
La organización general y su relación con la localidad se describen en el diseño de alto nivel de ext4.
3. Superbloque y características habilitadas
El superbloque primario comienza a 1024 bytes del inicio del sistema de archivos. En bloques de 1 KiB está en el bloque 1; con bloques de 4 KiB ocupa parte del bloque 0. Una imagen de disco con particiones requiere localizar primero el comienzo del volumen.
| Campo | Qué permite conocer |
|---|---|
| s_log_block_size | Tamaño de bloque: 1024 × 2 elevado a ese valor. |
| s_blocks_count_lo / hi | Número de bloques, interpretando la parte alta cuando corresponde. |
| s_blocks_per_group / s_inodes_per_group | Geometría de grupos e inodos. |
| s_inode_size / s_desc_size | Anchuras de estructuras según la revisión y características. |
| s_feature_compat / incompat / ro_compat | Características que afectan a la interpretación y compatibilidad. |
| s_uuid / s_magic | Identificación y firma de la familia ext. |
La firma 0xEF53 no distingue por sí sola ext2, ext3 y ext4. Tampoco acredita integridad. Hay que consultar revisión, características y estructuras. Los campos ext4 son little-endian; el journal JBD2 usa su propia representación.
Las banderas no son decoración: un lector que desconoce una característica puede interpretar mal las direcciones. La referencia de campos y categorías es la documentación del superbloque. Nuestro inspector lee solo un subconjunto explícito y no verifica su checksum.
4. Descriptores, bitmaps y tablas de inodos
Un descriptor de grupo informa dónde están sus mapas y su tabla de inodos, entre otros datos. Un bitmap de bloques registra asignación; otro mapa registra inodos. Ninguno equivale a la lista de nombres que muestra ls.
Con flex_bg pueden agruparse metadatos de distintos grupos. meta_bg, 64bit y las políticas de copias de superbloque también afectan al formato. Por eso no se localiza una tabla de inodos sumando un número fijo de bloques a cada comienzo de grupo.
Número de grupo → descriptor correspondiente
Descriptor → dirección de la tabla de inodos
Número de inodo → posición dentro de esa tabla
Inodo → tamaño, atributos y mapa de datos del objeto
Directorio → relación entre un nombre y un número de inodoEl esquema de grupos y las variantes de disposición se detallan en Block Groups. El laboratorio consultará las direcciones reales con dumpe2fs, en lugar de deducirlas del dibujo.
5. Localizar un inodo sin confundir nombre y objeto
grupo = (numero_inodo - 1) / inodos_por_grupo
indice = (numero_inodo - 1) % inodos_por_grupo
offset_inodo = bloque_tabla_del_grupo × bytes_por_bloque
+ indice × bytes_por_inodo
Ejemplo: inodo 9000, 8192 inodos por grupo
grupo = 1
indice = 807Las divisiones son enteras. La dirección de la tabla del grupo se obtiene de su descriptor, que puede tener campos altos. El tamaño de inodo no se fija siempre en 128: el volumen del ensayo usa 256 bytes.
El nombre no está dentro del inodo. Un directorio lo relaciona con el objeto y puede usar índices para acelerar búsquedas. Dos hard links pueden llevar al mismo inodo; renombrar una entrada no crea necesariamente otro objeto.
La región i_block del inodo tiene usos diferentes según su tipo y banderas: no debe leerse siempre como un árbol de extents. Por ejemplo, algunos enlaces simbólicos cortos guardan allí su texto.
6. Extents y árbol de asignación
El árbol de extents tiene un encabezado con firma, número de entradas, capacidad y profundidad. Con profundidad cero contiene registros hoja; con mayor profundidad contiene índices hacia otros nodos. El espacio del inodo puede alojar una raíz pequeña, sin requerir un bloque externo para todos los casos.
ee_block comienzo lógico en bloques del archivo
ee_len longitud y codificación del estado
ee_start_hi/lo comienzo físico en bloques del volumen
Si el bloque lógico L pertenece a un extent inicializado:
fisico = comienzo_fisico + (L - comienzo_logico)La longitud necesita decodificarse según el formato: no basta con borrar siempre el bit superior. En ext4, el valor 32768 representa un extent inicializado de esa longitud; los valores mayores codifican extents sin inicializar y se interpretan de otra manera.
Un archivo puede usar varios extents por fragmentación, límites de longitud o diferencias de estado. Dos registros no implican automáticamente una separación física de sus datos. Véase la documentación de i_block y del árbol de extents.
7. Hueco, extent sin inicializar y datos escritos
| Estado | Asignación física | Lectura lógica dentro del tamaño |
|---|---|---|
| Hueco | No tiene bloques asignados en ese intervalo. | Ceros. |
| Extent sin inicializar, unwritten | Hay almacenamiento reservado/asignado. | Ceros hasta que se convierta la parte escrita. |
| Extent inicializado | Existe una correspondencia para sus datos. | Bytes almacenados, que también podrían ser ceros. |
| Asignación retrasada, delalloc | Puede no tener ubicación física definitiva. | La caché puede proporcionar los bytes recientes. |
Leer ceros no demuestra que haya un hueco. Un archivo disperso, una reserva unwritten y un archivo lleno de ceros pueden producir el mismo contenido lógico y tener asignaciones distintas.
La preasignación puede crear extents sin inicializar; escribir sobre una parte puede dividirlos y convertirla en inicializada. No se deben leer bytes crudos del dispositivo para ignorar esas reglas: podrían no representar el contenido que la interfaz de archivos debe entregar.
SEEK_HOLE y SEEK_DATA pueden ayudar a explorar huecos con las garantías que admita el proveedor, pero no son una certificación del historial de cada bloque. Para observar mapeos y banderas utilizaremos FIEMAP.
8. Asignación retrasada y writeback
La escritura almacenada en caché puede diferir la elección definitiva de bloques. Esperar a conocer más datos permite agrupar asignaciones. Esto separa tamaño lógico, páginas sucias, reservas internas, mapa físico y persistencia.
write acepta bytes en caché
→ páginas sucias y asignación pendiente
→ asignación de rangos para writeback
→ E/S y actualización del estado de los extents
→ confirmación requerida por fsync
El kernel puede iniciar writeback antes de que lo solicite la aplicación.Por eso no es reproducible afirmar que una consulta «sin fsync» siempre mostrará DELALLOC. La presión de memoria, la actividad concurrente y el tiempo transcurrido pueden cambiar lo observado. Consultar dos veces tampoco proporciona una instantánea congelada.
El éxito de write no descarta fallos diferidos por espacio o E/S. La guía de ext4 describe asignación retrasada y opciones relacionadas. El programa compara observaciones sin convertirlas en una promesa de rendimiento o persistencia.
9. Journal, checksums y persistencia
JBD2 coordina transacciones internas y recuperación. Retomando el tema 33, data=ordered, writeback y journal no ofrecen la misma relación entre datos y metadatos. El journal no es una copia histórica general de todos los documentos ni una transacción que agrupe automáticamente las decisiones de la aplicación.
Que un archivo tenga extents inicializados no prueba que todas sus escrituras recientes sobrevivirán a un apagado. El protocolo del tema 35 sigue comprobando escritura, fsync del archivo, rename y fsync del directorio.
metadata_csum protege estructuras concretas de metadatos. No convierte todos los bloques de datos ordinarios en contenido verificado como en los sistemas analizados en el tema 34. Una validación del superbloque tampoco comprueba cada archivo.
La representación y recuperación del diario están documentadas en Journal (JBD2). La práctica no interrumpe el journal ni modifica sus registros.
10. Límites que dependen de la configuración
«El máximo de ext4» necesita condiciones: tamaño de bloque, características, forma de mapear los archivos, kernel, herramientas y arquitectura. Una capacidad codificable por un campo no garantiza que todo el conjunto la soporte.
Sin la característica 64bit:
2^32 bloques × 4096 bytes = 16 TiB de volumen como límite de representación
Bloques lógicos de un archivo con extents:
2^32 bloques × 4096 bytes = 16 TiB como techo de ese espacio lógico
Activar 64bit amplía el direccionamiento del volumen;
no amplía automáticamente el índice lógico de cada extent.Estos cálculos no son una recomendación de tamaño ni un compromiso del entorno instalado. El tamaño máximo de bloque utilizable también depende de la plataforma. La referencia de dimensiones y límites se encuentra en Blocks.
bigalloc agrupa bloques en clústeres de asignación y cambia los cálculos del bitmap. inline_data permite otra representación para ciertos contenidos pequeños. El inspector que construiremos rechaza bigalloc al calcular la geometría ordinaria y no intenta localizar datos inline.
11. Qué observan dumpe2fs, debugfs y FIEMAP
| Herramienta/interfaz | Objeto consultado | Interpretación |
|---|---|---|
| dumpe2fs | Metadatos de un volumen o imagen ext. | Superbloque y grupos; -h limita el resumen. |
| debugfs | Estructuras internas de una imagen o volumen. | Permite consultar inodos, extents y extraer contenido. |
| FIEMAP | Descriptor de un archivo mediante su proveedor. | Devuelve rangos lógicos, posiciones y banderas. |
| filefrag | Archivo accesible por una ruta. | Presenta información de mapeo con unidades indicadas en su salida. |
Consultaremos la imagen desmontada y estable. La salida de dumpe2fs(8) puede ser inconsistente sobre un volumen activo. debugfs(8) abre en lectura por defecto; en esta práctica no se usa -w.
FIEMAP devuelve posiciones y longitudes en bytes, no directamente los registros crudos del árbol. Sus banderas pueden indicar asignación pendiente, datos sin inicializar u otras representaciones. Las posiciones no son instrucciones para leer un dispositivo por fuera del sistema de archivos. Véase Fiemap Ioctl.
Si consultas FIEMAP sobre volumen.img, obtienes el mapa de ese archivo en el anfitrión. Para consultar un archivo interior necesitas acceder a él mediante el montaje correspondiente. Esa diferencia es central en el laboratorio.
12. Programa en C: resumir el superbloque
Guarda resumen-ext.c. Lee el superbloque primario de una imagen regular cuyo volumen empieza en cero. Valida un subconjunto de geometría de revisión dinámica, combina el contador alto cuando está habilitado 64bit y calcula el número de grupos. No recorre descriptores ni valida checksums.
#define _POSIX_C_SOURCE 200809L
#define _FILE_OFFSET_BITS 64
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdint.h>
#include <inttypes.h>
#include <stdio.h>
#include <errno.h>
static uint16_t le16(const unsigned char *p)
{
return (uint16_t)((uint16_t)p[0] | ((uint16_t)p[1] << 8));
}
static uint32_t le32(const unsigned char *p)
{
return (uint32_t)p[0] | ((uint32_t)p[1] << 8) |
((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24);
}
static int leer_super(int fd, unsigned char *p)
{
size_t usados = 0;
while (usados < 1024) {
ssize_t n = pread(fd, p + usados, 1024 - usados, (off_t)(1024 + usados));
if (n > 0) usados += (size_t)n;
else if (n == -1 && errno == EINTR) continue;
else { if (n == -1) perror("pread"); return -1; }
}
return 0;
}
static int analizar(const unsigned char *s, uint64_t bytes)
{
if (le16(s + 0x38) != 0xef53 || le32(s + 0x4c) != 1) {
fprintf(stderr, "Firma o revision fuera del subconjunto admitido.\n"); return 1;
}
uint32_t log = le32(s + 0x18), incompat = le32(s + 0x60);
uint32_t ro = le32(s + 0x64), compat = le32(s + 0x5c);
if (log > 6 || (ro & 0x200) || (incompat & 0x8)) {
fprintf(stderr, "Bloques no admitidos, bigalloc o dispositivo de journal.\n"); return 1;
}
uint64_t bloque = UINT64_C(1024) << log;
uint64_t bloques = le32(s + 0x04), libres = le32(s + 0x0c);
if (incompat & 0x80) {
bloques |= (uint64_t)le32(s + 0x150) << 32;
libres |= (uint64_t)le32(s + 0x158) << 32;
}
uint32_t primero = le32(s + 0x14), bpg = le32(s + 0x20);
uint32_t ipg = le32(s + 0x28), inodos = le32(s);
uint32_t libres_i = le32(s + 0x10), tam_i = le16(s + 0x58);
uint32_t esperado = bloque == 1024 ? 1 : 0;
if (primero != esperado || bloques <= primero || bloques > bytes / bloque ||
libres > bloques || bpg == 0 || bpg > 8 * bloque ||
ipg == 0 || ipg > 8 * bloque || le32(s + 0x24) != bpg ||
le32(s + 0x1c) != log || inodos == 0 || libres_i > inodos ||
tam_i < 128 || tam_i > bloque || (tam_i & (tam_i - 1))) {
fprintf(stderr, "Geometria incoherente o volumen fuera de la imagen.\n"); return 1;
}
uint64_t grupos = (bloques - primero - 1) / bpg + 1;
if (((uint64_t)inodos - 1) / ipg + 1 > grupos) {
fprintf(stderr, "Los inodos no caben en los grupos declarados.\n"); return 1;
}
uint32_t desc = (incompat & 0x80) ? le16(s + 0xfe) : 32;
if (desc < 32 || desc > bloque || (desc & (desc - 1)) ||
((incompat & 0x80) && desc < 64)) {
fprintf(stderr, "Tamano de descriptor no admitido.\n"); return 1;
}
printf("BLOCK_SIZE=%" PRIu64 "\nBLOCKS=%" PRIu64 "\n", bloque, bloques);
printf("FREE_BLOCKS=%" PRIu64 "\nFIRST_DATA_BLOCK=%u\n", libres, (unsigned)primero);
printf("BLOCKS_PER_GROUP=%u\nGROUPS=%" PRIu64 "\n", (unsigned)bpg, grupos);
printf("INODES=%u\nINODES_PER_GROUP=%u\nINODE_SIZE=%u\nDESC_SIZE=%u\n",
(unsigned)inodos, (unsigned)ipg, (unsigned)tam_i, (unsigned)desc);
printf("COMPAT=%08" PRIx32 "\nINCOMPAT=%08" PRIx32 "\nRO_COMPAT=%08" PRIx32 "\n",
compat, incompat, ro);
printf("EXTENTS=%s\nHAS_JOURNAL=%s\n", incompat & 0x40 ? "si" : "no",
compat & 0x4 ? "si" : "no");
puts("Resumen de la familia ext; no es una validacion integral ni verifica checksums.");
return 0;
}
int main(int argc, char **argv)
{
if (argc != 2) { fprintf(stderr, "Uso: %s volumen.img\n", argv[0]); return 2; }
int fd = open(argv[1], O_RDONLY | O_CLOEXEC | O_NOFOLLOW);
if (fd == -1) { perror("open"); return 1; }
struct stat st;
int estado = 1;
unsigned char super[1024];
if (fstat(fd, &st) == -1) perror("fstat");
else if (!S_ISREG(st.st_mode) || st.st_size < 2048)
fprintf(stderr, "Se requiere una imagen regular de al menos 2048 bytes.\n");
else if (leer_super(fd, super) == -1) fprintf(stderr, "Superbloque incompleto.\n");
else estado = analizar(super, (uint64_t)st.st_size);
if (close(fd) == -1) { perror("close"); estado = 1; }
return estado;
}Las características que no cambian los campos resumidos se imprimen como máscaras, sin interpretarlas como permiso para recorrer otras estructuras. Un analizador completo necesita reglas adicionales para cada característica soportada.
13. Programa en C: consultar rangos con FIEMAP
Guarda mapa-fiemap.c. Es específico de Linux y consulta un archivo regular mediante su sistema de archivos. Acepta sin-sync o sincronizar; el segundo solicita fsync antes del mapa. No usa FIEMAP_FLAG_SYNC en la consulta. Mantén el archivo sin modificaciones concurrentes durante el ensayo.
#define _POSIX_C_SOURCE 200809L
#define _FILE_OFFSET_BITS 64
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <linux/fs.h>
#include <linux/fiemap.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdint.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
static void banderas(uint32_t flags)
{
if (flags & FIEMAP_EXTENT_LAST) printf(" LAST");
if (flags & FIEMAP_EXTENT_UNKNOWN) printf(" UNKNOWN");
if (flags & FIEMAP_EXTENT_DELALLOC) printf(" DELALLOC");
if (flags & FIEMAP_EXTENT_UNWRITTEN) printf(" UNWRITTEN");
if (flags & FIEMAP_EXTENT_ENCODED) printf(" ENCODED");
if (flags & FIEMAP_EXTENT_DATA_INLINE) printf(" INLINE");
if (flags & FIEMAP_EXTENT_SHARED) printf(" SHARED");
}
static int consultar(int fd)
{
const uint32_t capacidad = 64;
size_t bytes = sizeof(struct fiemap) + capacidad * sizeof(struct fiemap_extent);
struct fiemap *mapa = calloc(1, bytes);
if (!mapa) { perror("calloc"); return 1; }
uint64_t cursor = 0;
int estado = 3;
for (unsigned lote = 0; lote < 64; lote++) {
memset(mapa, 0, bytes);
mapa->fm_start = cursor;
mapa->fm_length = UINT64_MAX - cursor;
mapa->fm_extent_count = capacidad;
int r;
do { r = ioctl(fd, FS_IOC_FIEMAP, mapa); } while (r == -1 && errno == EINTR);
if (r == -1) { perror("FIEMAP"); estado = 1; break; }
if (mapa->fm_mapped_extents > capacidad) {
fprintf(stderr, "Respuesta mayor que el buffer.\n"); estado = 1; break;
}
if (mapa->fm_mapped_extents == 0) { estado = 0; break; }
uint64_t fin = cursor;
int invalido = 0, ultimo = 0;
for (uint32_t i = 0; i < mapa->fm_mapped_extents; i++) {
const struct fiemap_extent *e = &mapa->fm_extents[i];
uint64_t inicio = e->fe_logical, longitud = e->fe_length;
if (longitud == 0 || inicio > UINT64_MAX - longitud ||
inicio + longitud <= fin || (i > 0 && inicio < fin)) {
invalido = 1; break;
}
fin = inicio + longitud;
uint32_t flags = e->fe_flags;
printf("LOGICAL=%" PRIu64 " LENGTH=%" PRIu64 " PHYSICAL=", inicio, longitud);
uint32_t no_ordinario = FIEMAP_EXTENT_UNKNOWN | FIEMAP_EXTENT_DELALLOC |
FIEMAP_EXTENT_ENCODED | FIEMAP_EXTENT_DATA_INLINE | FIEMAP_EXTENT_NOT_ALIGNED;
if (flags & no_ordinario) printf("no_interpretar");
else printf("%" PRIu64, (uint64_t)e->fe_physical);
printf(" FLAGS=%08" PRIx32, flags); banderas(flags); putchar('\n');
if (flags & FIEMAP_EXTENT_LAST) {
if (i + 1 != mapa->fm_mapped_extents) invalido = 1;
ultimo = 1;
}
}
if (invalido || fin <= cursor) {
fprintf(stderr, "Mapa sin progreso o rangos incoherentes.\n"); estado = 1; break;
}
if (ultimo || fin == UINT64_MAX) { estado = 0; break; }
cursor = fin;
}
if (estado == 3) fprintf(stderr, "Limite de 4096 rangos alcanzado: consulta incompleta.\n");
free(mapa);
return estado;
}
int main(int argc, char **argv)
{
if (argc < 2 || argc > 3 || (argc == 3 &&
strcmp(argv[2], "sin-sync") && strcmp(argv[2], "sincronizar"))) {
fprintf(stderr, "Uso: %s archivo [sin-sync|sincronizar]\n", argv[0]); return 2;
}
int fd = open(argv[1], O_RDONLY | O_CLOEXEC | O_NOFOLLOW);
if (fd == -1) { perror("open"); return 1; }
struct stat st;
int estado = 1;
if (fstat(fd, &st) == -1) perror("fstat");
else if (!S_ISREG(st.st_mode)) fprintf(stderr, "Se requiere un archivo regular.\n");
else {
int r = 0;
if (argc == 3 && !strcmp(argv[2], "sincronizar")) {
do { r = fsync(fd); } while (r == -1 && errno == EINTR);
}
if (r == -1) perror("fsync");
else {
printf("SIZE=%" PRIu64 " ST_BLOCKS_512=%" PRIu64 "\n",
(uint64_t)st.st_size, (uint64_t)st.st_blocks);
estado = consultar(fd);
}
}
if (close(fd) == -1) { perror("close"); estado = 1; }
return estado;
}Las cifras son bytes salvo ST_BLOCKS_512, que expresa unidades de 512 bytes observadas por fstat antes de la sincronización opcional. Los rangos pueden extenderse más allá del tamaño lógico por preasignación o alineación; no se suman sus longitudes para decidir cuánto contenido leer.
El programa admite proveedores compatibles con FIEMAP, no identifica ext4 automáticamente. La salida hexadecimal conserva incluso las banderas que no traduce a texto. Un error EOPNOTSUPP o ENOTTY puede indicar falta de soporte. Estado 3 significa que el límite didáctico dejó la consulta incompleta.
14. Laboratorio sin montar una imagen ext4
Usa Linux con compilador C, Python 3 y e2fsprogs. No se requiere sudo para la parte principal. Trabaja en una misma sesión Bash y detén la secuencia si alguna orden falla. La imagen nueva tendrá 256 MiB lógicos; comprueba espacio disponible en el anfitrión.
$ cc -std=c11 -Wall -Wextra -Wpedantic -O2 resumen-ext.c -o resumen-ext
$ cc -std=c11 -Wall -Wextra -Wpedantic -O2 mapa-fiemap.c -o mapa-fiemap
$ BIN37="$PWD"
$ LAB37=$(mktemp -d "$HOME/ext437.XXXXXX")
$ cd -- "$LAB37"
$ mkdir fuenteCrear contenido normal y un archivo disperso
$ python3 - <<'PY'
from pathlib import Path
base = Path('fuente')
with (base / 'datos.bin').open('xb') as f:
f.write(bytes(range(256)) * 256)
with (base / 'disperso.bin').open('xb') as f:
f.write(b'A' * 4096)
f.seek(8 * 1024 * 1024 - 4096)
f.write(b'Z' * 4096)
print('datos.bin: 65536 bytes; disperso.bin: 8388608 bytes')
PY
$ truncate -s 256M volumen.img
$ mkfs.ext4 -b 4096 -I 256 -m 0 -L CURSO37 -E lazy_itable_init=0,lazy_journal_init=0 -d fuente volumen.imgEl directorio exclusivo hace que volumen.img sea nuevo. El formateo se dirige solo a ese archivo y no usa -F ni un dispositivo real. -d incorpora el contenido de fuente al crear el sistema de archivos. Desactivar la inicialización diferida de metadatos hace el ensayo más fácil de inspeccionar, sin imponer direcciones concretas de los archivos.
Consultar el superbloque, los grupos y los inodos
$ "$BIN37/resumen-ext" volumen.img
$ dumpe2fs -h volumen.img
$ dumpe2fs -g volumen.img
$ debugfs -R 'ls -l /' volumen.img
$ debugfs -R 'stat /datos.bin' volumen.img
$ debugfs -R 'extents /datos.bin' volumen.img
$ debugfs -R 'stat /disperso.bin' volumen.img
$ debugfs -R 'extents /disperso.bin' volumen.img
$ debugfs -R 'dump /disperso.bin recuperado.bin' volumen.img
$ cmp -- fuente/disperso.bin recuperado.bin
$ e2fsck -fn volumen.imgEl inspector debe informar BLOCK_SIZE=4096, BLOCKS=65536, INODE_SIZE=256 y, con la geometría ordinaria de 32768 bloques por grupo, GROUPS=2. Contrasta los valores con dumpe2fs; no fijes números de inodo ni bloques de datos, que dependen de la versión y configuración de las herramientas.
Observa el salto entre los rangos del archivo disperso. Su lectura completa debe conservar el prefijo A, el sufijo Z y ceros intermedios. El número exacto de extents puede variar. e2fsck -fn fuerza revisión de lectura; no aplica reparaciones y su resultado debe interpretarse como en el tema 33.
15. Verificación y observación sobre un montaje
El verificador contrasta los campos con dumpe2fs, valida el contenido extraído y comprueba que las consultas no cambian la imagen. Se ejecuta después del laboratorio, desde LAB37.
$ python3 - "$BIN37/resumen-ext" <<'PY'
from pathlib import Path
import hashlib
import os
import re
import subprocess
import sys
programa = str(Path(sys.argv[1]).resolve())
imagen = Path('volumen.img')
def sha256(ruta):
h = hashlib.sha256()
with ruta.open('rb') as f:
for bloque in iter(lambda: f.read(1024 * 1024), b''):
h.update(bloque)
return h.digest()
antes = sha256(imagen)
r = subprocess.run([programa, str(imagen)], capture_output=True, text=True, check=True)
campos = dict(re.findall(r'^([A-Z_]+)=(\d+)$', r.stdout, re.M))
env = dict(os.environ, LC_ALL='C')
d = subprocess.run(['dumpe2fs', '-h', str(imagen)], env=env,
capture_output=True, text=True, check=True)
for clave, etiqueta in [('BLOCK_SIZE', 'Block size'), ('BLOCKS', 'Block count'),
('INODE_SIZE', 'Inode size'), ('INODES', 'Inode count'),
('BLOCKS_PER_GROUP', 'Blocks per group')]:
m = re.search(r'^' + re.escape(etiqueta) + r':\s+(\d+)', d.stdout, re.M)
if not m or campos.get(clave) != m.group(1):
raise SystemExit('Diferencia en ' + clave)
if campos.get('GROUPS') != '2':
raise SystemExit('Geometria distinta del laboratorio previsto')
if sha256(Path('fuente/disperso.bin')) != sha256(Path('recuperado.bin')):
raise SystemExit('Contenido recuperado diferente')
if sha256(imagen) != antes:
raise SystemExit('La imagen cambio durante las consultas')
print('OK: superbloque contrastado, dos grupos, contenido y consultas de lectura')
PYFIEMAP sobre un archivo del anfitrión
$ findmnt --target "$LAB37/fuente/disperso.bin" --output TARGET,SOURCE,FSTYPE,OPTIONS
$ "$BIN37/mapa-fiemap" "$LAB37/fuente/disperso.bin" sin-sync
$ "$BIN37/mapa-fiemap" "$LAB37/fuente/disperso.bin" sincronizar
$ filefrag -v "$LAB37/fuente/disperso.bin"Estas consultas corresponden al anfitrión; para estudiar ext4 con ellas, findmnt debe confirmar que esa ruta reside en ext4. Las operaciones offline anteriores no muestran delalloc del archivo interior: un creador de imágenes como mkfs.ext4 escribe estructuras sin pasar por el writeback de un archivo montado.
Práctica opcional con la imagen montada
Si tu entorno permite montajes loop y tienes autorización para realizarlos, puedes consultar los archivos interiores. Cierra las inspecciones crudas antes de montar y no uses debugfs ni e2fsck sobre la imagen mientras esté activa.
$ MNT37="$LAB37/mnt"
$ mkdir -- "$MNT37"
$ sudo mount -o loop,nodev,nosuid,noexec "$LAB37/volumen.img" "$MNT37"
$ findmnt --mountpoint "$MNT37" --output TARGET,SOURCE,FSTYPE,OPTIONS
$ "$BIN37/mapa-fiemap" "$MNT37/disperso.bin" sincronizar
$ filefrag -v "$MNT37/disperso.bin"
$ sudo umount "$MNT37"Los ejecutables están fuera del montaje noexec. Si umount indica ocupado, cierra las referencias y repite el desmontaje normal antes de volver a inspeccionar la imagen. Esta práctica opcional puede cambiar metadatos del volumen al montar y desmontar; la comprobación de estabilidad del verificador corresponde a las consultas offline.
16. Simuladores de extents y asignación
Traducir una posición lógica
El archivo del modelo mide once bloques de 4096 bytes. Tiene un extent inicializado lógico [0,3) que comienza en el bloque físico 100, uno unwritten [5,7) que comienza en 200 y otro inicializado [9,11) que comienza en 700. Los intervalos [3,5) y [7,9) son huecos. Los extremos superiores de estos intervalos se excluyen.
Mapa de un archivo con huecos y reservas
Observar asignación y confirmación por separado
Este recorrido representa una posible escritura almacenada en caché sobre un archivo recién creado. Omite operaciones concurrentes, cambios de tamaño y optimizaciones. La etapa unwritten es ilustrativa: no toda escritura ni toda consulta expone ese paso.
De la caché a la confirmación
| Propiedad | Estado del modelo |
|---|
17. Diagnóstico y resumen
| Observación | Interpretación a comprobar |
|---|---|
| La firma es 0xEF53. | Reconoce la familia ext; faltan características, validación y contexto. |
| Un bitmap o tabla no está al comienzo de su grupo. | Las características y descriptores pueden agrupar o desplazar metadatos. |
| El archivo mide mucho más que su asignación. | Puede contener huecos; diferenciar tamaño lógico y espacio asignado. |
| FIEMAP muestra UNWRITTEN. | Hay asignación cuya lectura lógica aún devuelve ceros en ese rango. |
| FIEMAP muestra DELALLOC o UNKNOWN. | No interpretar la posición como un bloque físico definitivo ordinario. |
| No aparece DELALLOC antes de fsync. | El kernel pudo haber iniciado la asignación o el writeback. |
| La imagen y su archivo interior muestran mapas distintos. | Se están consultando capas diferentes: anfitrión y sistema interno. |
- ext4 organiza metadatos y datos mediante grupos, descriptores e inodos.
- Los campos se interpretan según revisión y características, sin asumir una disposición única.
- Los extents describen rangos; un árbol permite representar mapas más amplios.
- Hueco, unwritten y delalloc tienen significados diferentes aunque algunas lecturas devuelvan ceros.
- La asignación retrasada separa aceptación de datos, elección de bloques y persistencia.
- Journal y checksums de metadatos no validan automáticamente toda la lógica ni todo el contenido.
- dumpe2fs y debugfs inspeccionan estructuras; FIEMAP consulta un archivo a través de su proveedor.
- Los límites deben expresarse junto a sus condiciones y al soporte real del entorno.
18. Preguntas de repaso y práctica
1. ¿Qué ventaja aporta un extent para datos contiguos?
Describe un rango con un comienzo y una longitud, evitando una referencia individual por cada bloque del tramo.
2. ¿Bloque lógico del archivo y bloque del volumen son lo mismo?
No. Uno identifica una posición dentro del archivo; el otro una ubicación del sistema de archivos.
3. ¿Dónde comienza el superbloque primario?
A 1024 bytes del inicio del volumen, que puede estar dentro de una partición de una imagen de disco.
4. ¿La firma 0xEF53 identifica inequívocamente ext4?
No. Se comparte en la familia ext y no acredita integridad.
5. ¿Cómo se localiza la tabla de inodos de un grupo?
Mediante su descriptor y las reglas de características habilitadas, no mediante una posición fija supuesta.
6. ¿Qué grupo contiene el inodo 9000 con 8192 inodos por grupo?
El grupo 1; su índice interno es 807, porque el número de inodo comienza en 1.
7. ¿El nombre del archivo reside en su inodo?
No. El directorio relaciona nombres con números de inodo.
8. ¿i_block siempre contiene extents?
No. Su interpretación depende del tipo y características del objeto; puede contener otras representaciones.
9. ¿Qué significa profundidad cero en el árbol de extents?
Que sus entradas son registros hoja de extents, no índices hacia niveles inferiores.
10. ¿Leer ceros demuestra que hay un hueco?
No. Un extent unwritten o un extent inicializado con ceros puede producir la misma lectura.
11. ¿Un extent unwritten tiene espacio asignado?
Sí, pero la interfaz lógica no debe entregar contenido antiguo del dispositivo como datos del archivo.
12. ¿Write exitoso garantiza un mapa físico definitivo?
No. Puede haber datos aceptados en caché con asignación retrasada.
13. ¿Una consulta sin fsync debe mostrar siempre DELALLOC?
No. El kernel puede haber realizado writeback antes de la consulta.
14. ¿Metadata_csum verifica todo el contenido ordinario?
No. Protege estructuras concretas de metadatos según el formato.
15. ¿Extents inicializados significan persistencia confirmada?
No. La asignación y el estado del extent no sustituyen las confirmaciones del protocolo.
16. ¿Activar 64bit amplía automáticamente el tamaño máximo de cada archivo?
No. El direccionamiento del volumen y el espacio lógico de un archivo tienen límites distintos.
17. ¿Qué mapa devuelve FIEMAP aplicado a volumen.img?
El de ese archivo en el anfitrión, no los mapas de los archivos interiores.
18. ¿Se usa debugfs -w para consultar extents en el laboratorio?
No. Las consultas offline se hacen en lectura sobre la imagen desmontada.
19. ¿Qué indica el estado 3 de mapa-fiemap?
Que alcanzó su límite de rangos y la consulta quedó incompleta.
20. ¿El inspector C reemplaza e2fsck?
No. Solo resume y valida un subconjunto de campos; no revisa todas las estructuras ni sus checksums.
Para practicar
- Relaciona los campos de resumen-ext con los correspondientes de dumpe2fs -h.
- Ubica los mapas y tablas de ambos grupos usando dumpe2fs -g.
- Calcula el grupo y el índice para varios números de inodo con la geometría observada.
- Compara stat y extents de los dos archivos incorporados al volumen.
- Explica por qué disperso.bin puede tener ocho MiB lógicos y pocos bloques de datos asignados.
- Ejecuta el verificador y comprueba que la extracción conserva el contenido.
- En el primer simulador, prueba el último byte de un extent y el primero del intervalo siguiente.
- Compara una posición unwritten con una posición de hueco: misma lectura lógica, distinta asignación.
- Recorre las etapas del segundo simulador y separa mapa físico de confirmación de persistencia.
- Consulta FIEMAP solo después de identificar el proveedor del archivo y declara qué capa observas.
- Explica qué validaciones faltan para convertir resumen-ext en un analizador completo.
- Reformula un supuesto límite máximo indicando bloque, características, kernel y herramientas necesarios.
En el tema 38 estudiaremos NTFS: MFT, atributos, archivos residentes, registro de transacciones y flujos alternativos.