← Sistemas de Archivos y E/SBloque 11 · Sistemas de archivos reales · Tema 37 de 50

Tema 37

ext4

ext4 organiza el volumen en grupos y relaciona los archivos con rangos de bloques mediante árboles de extents. Estudiaremos sus metadatos, los huecos, las reservas sin inicializar y la asignación retrasada, sin confundir asignación con persistencia. Dos programas en C, una imagen propia y dos simuladores permitirán contrastar el modelo con dumpe2fs, debugfs y FIEMAP.

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.

ejemplo · bloques de 4096 bytes
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.

Organización conceptual en grupos de bloquesEl volumen tiene varios grupos con descriptores, mapas, tablas de inodos y datos. flex_bg puede reunir metadatos de varios grupos, por lo que el dibujo no determina sus direcciones reales. Volumen ext4: superbloque y descriptores Grupo 0mapas · inodos · datos Grupo 1mapas · inodos · datos Grupo 2mapas · inodos · datos Las características del volumen determinan la disposición física.
Modelo de responsabilidades; las posiciones se consultan en los descriptores.

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.

CampoQué permite conocer
s_log_block_sizeTamaño de bloque: 1024 × 2 elevado a ese valor.
s_blocks_count_lo / hiNúmero de bloques, interpretando la parte alta cuando corresponde.
s_blocks_per_group / s_inodes_per_groupGeometría de grupos e inodos.
s_inode_size / s_desc_sizeAnchuras de estructuras según la revisión y características.
s_feature_compat / incompat / ro_compatCaracterísticas que afectan a la interpretación y compatibilidad.
s_uuid / s_magicIdentificació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.

separar identidades y ubicaciones
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 inodo

El 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

fórmulas · los números de inodo empiezan en 1
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 = 807

Las 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.

registro hoja conceptual · no es una estructura C para copiar directamente
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

EstadoAsignación físicaLectura lógica dentro del tamaño
HuecoNo tiene bloques asignados en ese intervalo.Ceros.
Extent sin inicializar, unwrittenHay almacenamiento reservado/asignado.Ceros hasta que se convierta la parte escrita.
Extent inicializadoExiste una correspondencia para sus datos.Bytes almacenados, que también podrían ser ceros.
Asignación retrasada, delallocPuede 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.

secuencia posible · no todas las escrituras pasan por cada estado observable
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.

ejemplo de límites de representación · bloques de 4096 bytes
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/interfazObjeto consultadoInterpretación
dumpe2fsMetadatos de un volumen o imagen ext.Superbloque y grupos; -h limita el resumen.
debugfsEstructuras internas de una imagen o volumen.Permite consultar inodos, extents y extraer contenido.
FIEMAPDescriptor de un archivo mediante su proveedor.Devuelve rangos lógicos, posiciones y banderas.
filefragArchivo 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.

resumen-ext.c · decodificación explícita y límites de imagen
#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.

mapa-fiemap.c · paginación acotada y banderas de mapeo
#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.

terminal · compilar y preparar un directorio exclusivo
$ 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 fuente

Crear contenido normal y un archivo disperso

Python 3 · fuentes propios, sin escrituras concurrentes
$ 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.img

El 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

terminal · imagen estable y desmontada
$ "$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.img

El 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.

Python 3 · campos, contenido y estabilidad de la imagen
$ 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')
PY

FIEMAP sobre un archivo del anfitrión

terminal · identificar qué sistema de archivos responde
$ 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.

terminal · montar solo la imagen propia y consultar sus archivos
$ 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

resultado de la traducción en el modelo

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

PropiedadEstado del modelo

17. Diagnóstico y resumen

ObservaciónInterpretació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.

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

  1. Relaciona los campos de resumen-ext con los correspondientes de dumpe2fs -h.
  2. Ubica los mapas y tablas de ambos grupos usando dumpe2fs -g.
  3. Calcula el grupo y el índice para varios números de inodo con la geometría observada.
  4. Compara stat y extents de los dos archivos incorporados al volumen.
  5. Explica por qué disperso.bin puede tener ocho MiB lógicos y pocos bloques de datos asignados.
  6. Ejecuta el verificador y comprueba que la extracción conserva el contenido.
  7. En el primer simulador, prueba el último byte de un extent y el primero del intervalo siguiente.
  8. Compara una posición unwritten con una posición de hueco: misma lectura lógica, distinta asignación.
  9. Recorre las etapas del segundo simulador y separa mapa físico de confirmación de persistencia.
  10. Consulta FIEMAP solo después de identificar el proveedor del archivo y declara qué capa observas.
  11. Explica qué validaciones faltan para convertir resumen-ext en un analizador completo.
  12. 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.