14
Persistencia

Implementación de sistemas de archivos, FFS y journaling

Persistencia

Semana 14 — Implementación de sistemas de archivos, FFS y journaling

Objetivos

  • Diseñar un sistema de archivos simple (VSFS): superbloque, bitmaps, tabla de inodos, bloques de datos.
  • Explicar cómo FFS aprovecha la geometría del disco.
  • Explicar el problema de consistencia ante fallos y sus soluciones: fsck y journaling.

Sesión A — Implementación de un FS

Lecturas:

Temas:

  • Estructuras en disco: superbloque (superblock), bitmaps, inodos, bloques de datos.
  • Punteros directos, indirectos y multinivel; extents.
  • Recorrido de una ruta (path traversal) al abrir, leer y escribir un archivo.
  • Caché de páginas y búfer de escritura (write buffering).

Sesión B — FFS · Journaling

Lecturas:

Temas:

  • Grupos de cilindros (cylinder groups), política de ubicación, excepción de archivos grandes.
  • Escenarios de fallo durante una escritura. fsck.
  • Journaling de datos y de metadatos (ordered mode), checkpoint, revocación (ext3/ext4).
  • Alternativas: soft updates, copy-on-write, backpointers.

Laboratorio — Simuladores de FS y el sistema de archivos de xv6

  1. vsfs.py: preguntas 1–4 del cap. 40.
  2. ffs.py y fsck.py: preguntas seleccionadas.
  3. Para P5: leer kernel/fs.h, kernel/fs.c (bmap()) y mkfs/mkfs.c de xv6-riscv; dibujar el formato del disco e identificar dónde se agregaría un bloque doblemente indirecto.

Preguntas de repaso

  1. ¿Cuántas lecturas de disco necesita open("/foo/bar") en VSFS sin caché?
  2. Con bloques de 4 KB, punteros de 4 bytes, 12 directos y uno indirecto, ¿cuál es el tamaño máximo de archivo?
  3. ¿Por qué el journaling de metadatos escribe los datos antes que la transacción del journal?

Código de la semana

Compilar dentro del contenedor del curso con make o gcc -Wall.

Ejemplos (4)Compilación (1)

Ejemplos

Los programas que usan los apuntes y la clase.

bmapcalc.cDescargar bmapcalc.c
// bmapcalc.c: el formato del disco de xv6-riscv y qué bloque guarda un byte.
//
// Las constantes son las de kernel/fs.h, kernel/param.h y mkfs/mkfs.c.
//   bmapcalc                 muestra el formato del disco
//   bmapcalc off [off...]    para cada offset: bloque lógico y dónde está
//   bmapcalc -i inum         en qué bloque y byte está el inodo inum
//   bmapcalc -max bs ptr nd  tamaño máximo con nd directos y un indirecto

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// kernel/fs.h
#define BSIZE     1024
#define NDIRECT   12
#define NINDIRECT (BSIZE / sizeof(unsigned int))
#define MAXFILE   (NDIRECT + NINDIRECT)
#define DINODE    64                     // sizeof(struct dinode)
#define IPB       (BSIZE / DINODE)
#define BPB       (BSIZE * 8)
// kernel/param.h y mkfs/mkfs.c
#define FSSIZE    2000
#define LOGBLOCKS 30
#define NINODES   200

static int nlog, ninodeblocks, nbitmap, nmeta;
static int logstart, inodestart, bmapstart;

static void layout(void) {
    // Mismas fórmulas que mkfs/mkfs.c
    nbitmap = FSSIZE / BPB + 1;
    ninodeblocks = NINODES / IPB + 1;
    nlog = LOGBLOCKS + 1;                 // cabecera + bloques del log
    nmeta = 2 + nlog + ninodeblocks + nbitmap;
    logstart = 2;
    inodestart = 2 + nlog;
    bmapstart = 2 + nlog + ninodeblocks;
}

static void print_layout(void) {
    printf("xv6: %d bloques de %d B\n", FSSIZE, BSIZE);
    printf("  %4d          arranque (boot)\n", 0);
    printf("  %4d          superbloque\n", 1);
    printf("  %4d - %4d   log (cabecera + %d bloques)\n", logstart,
           logstart + nlog - 1, LOGBLOCKS);
    printf("  %4d - %4d   inodos (%d por bloque)\n", inodestart,
           inodestart + ninodeblocks - 1, IPB);
    printf("  %4d          bitmap (%d bits por bloque)\n", bmapstart, BPB);
    printf("  %4d - %4d   datos (%d bloques)\n", nmeta, FSSIZE - 1, FSSIZE - nmeta);
    printf("archivo máximo: %d + %d = %d bloques = %d KB\n", NDIRECT,
           (int)NINDIRECT, (int)MAXFILE, (int)MAXFILE * BSIZE / 1024);
}

static void where(unsigned long off) {
    unsigned long bn = off / BSIZE;
    printf("offset %lu: bloque lógico %lu, byte %lu dentro del bloque\n",
           off, bn, off % BSIZE);
    if (bn < NDIRECT) {
        printf("  directo: addrs[%lu]; lecturas = inodo + datos = 2\n", bn);
    } else if (bn < MAXFILE) {
        unsigned long k = bn - NDIRECT;
        printf("  indirecto: addrs[%d] -> entrada %lu (bytes %lu-%lu del bloque)\n",
               NDIRECT, k, k * 4, k * 4 + 3);
        printf("  lecturas = inodo + indirecto + datos = 3\n");
    } else {
        printf("  fuera de rango: MAXFILE = %d bloques (esto es P5)\n", (int)MAXFILE);
    }
}

int main(int argc, char *argv[]) {
    layout();
    if (argc == 1) {
        print_layout();
        return 0;
    }
    if (strcmp(argv[1], "-i") == 0 && argc == 3) {
        int inum = atoi(argv[2]);
        // IBLOCK(i, sb) de kernel/fs.h
        printf("inodo %d: bloque %d, byte %d\n", inum, inum / IPB + inodestart,
               (inum % IPB) * DINODE);
        return 0;
    }
    if (strcmp(argv[1], "-max") == 0 && argc == 5) {
        long bs = atol(argv[2]), ptr = atol(argv[3]), nd = atol(argv[4]);
        long nptr = bs / ptr;
        long blocks = nd + nptr;
        printf("%ld punteros por bloque\n", nptr);
        printf("%ld + %ld = %ld bloques = %ld KB\n", nd, nptr, blocks, blocks * bs / 1024);
        return 0;
    }
    for (int i = 1; i < argc; i++)
        where(strtoul(argv[i], NULL, 0));
    return 0;
}
fsyncbench.cDescargar fsyncbench.c
// fsyncbench.c: cuánto cuesta pedir que cada escritura llegue al disco.
//
//   fsyncbench archivo n
// Escribe n registros de 512 B tres veces: sin fsync, con fdatasync y con
// fsync después de cada uno. Los números dependen mucho del disco y, en el
// contenedor, de la capa de virtualización: compárenlos entre sí.

#define _POSIX_C_SOURCE 200809L
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

static double now(void) {
    struct timespec t;
    clock_gettime(CLOCK_MONOTONIC, &t);
    return t.tv_sec + t.tv_nsec / 1e9;
}

// modo 0: solo write; 1: write + fdatasync; 2: write + fsync
static double run(const char *path, int n, int mode) {
    char rec[512];
    memset(rec, 'x', sizeof rec);
    int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (fd < 0) { perror(path); exit(1); }
    double t0 = now();
    for (int i = 0; i < n; i++) {
        if (write(fd, rec, sizeof rec) != (ssize_t)sizeof rec) { perror("write"); exit(1); }
        if (mode == 1 && fdatasync(fd) < 0) { perror("fdatasync"); exit(1); }
        if (mode == 2 && fsync(fd) < 0) { perror("fsync"); exit(1); }
    }
    double t = now() - t0;
    close(fd);
    return t;
}

int main(int argc, char *argv[]) {
    if (argc != 3) {
        fprintf(stderr, "uso: %s archivo n\n", argv[0]);
        return 1;
    }
    int n = atoi(argv[2]);
    if (n <= 0) n = 100;
    const char *label[] = { "write", "write + fdatasync", "write + fsync" };
    for (int m = 0; m < 3; m++) {
        double t = run(argv[1], n, m);
        printf("%-18s %5d registros: %8.1f us por registro\n", label[m], n, t / n * 1e6);
    }
    unlink(argv[1]);
    return 0;
}
journal.cDescargar journal.c
// journal.c: simula una actualización con y sin journaling, y un fallo
// (crash) después de cada escritura al disco.
//
// La actualización es la de OSTEP cap. 42: agregar un bloque a un archivo.
// Cambian tres bloques: el inodo I, el bitmap de datos B y el bloque Db.
//
//   journal none      escribir I, B y Db en su lugar, sin protección
//   journal data      journaling de datos: TxB I B Db | TxE | checkpoint
//   journal ordered   journaling de metadatos: Db | TxB I B | TxE | checkpoint
//   journal reorder   como data, pero el disco reordena y TxE llega primero

#include <stdio.h>
#include <string.h>

enum { I, B, DB, TXB, JI, JB, JDB, TXE, NBLK };
static const char *name[NBLK] = { "I", "B", "Db", "TxB", "j:I", "j:B", "j:Db", "TxE" };

// 0 = viejo (o TxB/TxE ausentes), 1 = nuevo, 2 = basura de una transacción anterior
struct disk { int v[NBLK]; };

// Recuperación: si el journal tiene TxB y TxE, se reproduce (replay).
// Sin checksum, no se puede saber si los bloques del medio llegaron.
static void recover(struct disk *d, int ordered) {
    if (d->v[TXB] && d->v[TXE]) {
        d->v[I] = d->v[JI];
        d->v[B] = d->v[JB];
        if (!ordered) d->v[DB] = d->v[JDB];
    }
    d->v[TXB] = d->v[TXE] = 0;                   // vaciar el journal
}

static const char *verdict(const struct disk *d) {
    if (d->v[I] == 2 || d->v[B] == 2 || d->v[DB] == 2)
        return "CORRUPTO: replay de basura";
    if (d->v[I] != d->v[B]) return "INCONSISTENTE: I y B no coinciden";
    if (d->v[I] == 0) return "ok: estado viejo";
    if (d->v[DB] == 0) return "metadatos ok, Db es basura";
    return "ok: estado nuevo";
}

static void run(const char *mode, const int *order, int n, int ordered) {
    int w = 18;                                  // ancho de la primera columna
    int len = 0;
    for (int i = 0; i < n; i++) len += strlen(name[order[i]]) + 1;
    if (len > w) w = len;
    printf("== %s ==\n", mode);
    printf("%-*s %s\n", w, "en disco al fallar", "tras recuperar");
    for (int k = 0; k <= n; k++) {               // k escrituras llegaron al disco
        struct disk d = {{ 0, 0, 0, 0, 2, 2, 2, 0 }};
        char done[64] = "";
        for (int i = 0; i < k; i++) {
            d.v[order[i]] = 1;
            strcat(done, name[order[i]]);
            strcat(done, " ");
        }
        if (k == 0) strcpy(done, "(nada)");
        recover(&d, ordered);
        printf("%-*s %s\n", w, done, verdict(&d));
    }
}

int main(int argc, char *argv[]) {
    const char *m = argc > 1 ? argv[1] : "data";
    if (strcmp(m, "none") == 0) {
        int o[] = { DB, I, B };
        run("sin journal: Db, I, B", o, 3, 0);
    } else if (strcmp(m, "data") == 0) {
        int o[] = { TXB, JI, JB, JDB, TXE, I, B, DB };
        run("journaling de datos", o, 8, 0);
    } else if (strcmp(m, "ordered") == 0) {
        int o[] = { DB, TXB, JI, JB, TXE, I, B };
        run("journaling de metadatos (ordered)", o, 7, 1);
    } else if (strcmp(m, "reorder") == 0) {
        int o[] = { TXB, TXE, JI, JB, JDB };      // el disco reordenó TxE
        run("sin barrera: TxE llega antes", o, 5, 0);
    } else {
        fprintf(stderr, "uso: %s none|data|ordered|reorder\n", argv[0]);
        return 1;
    }
    return 0;
}
vsfs.cDescargar vsfs.c
// vsfs.c: un sistema de archivos muy simple (VSFS) sobre un archivo imagen.
//
// Formato: 64 bloques de 4 KB.
//   0 superbloque | 1 bitmap de inodos | 2 bitmap de datos |
//   3-7 tabla de inodos (80 inodos de 256 B) | 8-63 bloques de datos
// Sin caché: cada lectura o escritura de bloque es una E/S y se cuenta.
//
// Uso:
//   vsfs img mkfs
//   vsfs img mkdir /ruta
//   vsfs img create /ruta nbytes      (crea y escribe nbytes)
//   vsfs img read /ruta               (abre y lee todo el archivo)
//   vsfs img ls /ruta
//   vsfs img stat inum
// Con -v antes de img se imprime cada E/S.

#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define BSIZE      4096
#define NBLOCKS    64
#define IBMAP      1
#define DBMAP      2
#define INODESTART 3
#define DATASTART  8
#define ISIZE      256
#define IPB        (BSIZE / ISIZE)              // 16 inodos por bloque
#define NINODES    ((DATASTART - INODESTART) * IPB)
#define NDATA      (NBLOCKS - DATASTART)
#define NDIRECT    12
#define ROOTINO    2                            // como en Unix
#define DIRSIZ     12
#define MAGIC      0x56534653

enum { T_FREE = 0, T_FILE = 1, T_DIR = 2 };

struct superblock {
    uint32_t magic, nblocks, ninodes, inodestart, datastart;
};

struct dinode {                                 // 256 bytes en disco
    uint16_t type, nlink;
    uint32_t size;
    uint32_t addrs[NDIRECT];                    // 0 = sin bloque
    uint8_t pad[ISIZE - 8 - 4 * NDIRECT];
};

struct dirent {                                 // 16 bytes
    uint32_t inum;
    char name[DIRSIZ];
};

static int fd, verbose, nreads, nwrites;

static const char *region(uint32_t b) {
    static char s[32];
    if (b == 0) return "superbloque";
    if (b == IBMAP) return "bitmap de inodos";
    if (b == DBMAP) return "bitmap de datos";
    if (b < DATASTART) {
        snprintf(s, sizeof s, "inodos %u-%u", (b - INODESTART) * IPB,
                 (b - INODESTART) * IPB + IPB - 1);
        return s;
    }
    return "datos";
}

static void bread(uint32_t b, void *buf) {
    if (pread(fd, buf, BSIZE, (off_t)b * BSIZE) != BSIZE) { perror("pread"); exit(1); }
    nreads++;
    if (verbose) printf("    leer      bloque %2u  (%s)\n", b, region(b));
}

static void bwrite(uint32_t b, const void *buf) {
    if (pwrite(fd, buf, BSIZE, (off_t)b * BSIZE) != BSIZE) { perror("pwrite"); exit(1); }
    nwrites++;
    if (verbose) printf("    escribir  bloque %2u  (%s)\n", b, region(b));
}

// Inodo inum: está en el bloque INODESTART + inum/IPB, posición inum%IPB.
static void iread(uint32_t inum, struct dinode *ip) {
    uint8_t buf[BSIZE];
    bread(INODESTART + inum / IPB, buf);
    memcpy(ip, buf + (inum % IPB) * ISIZE, ISIZE);
}

static void iwrite(uint32_t inum, const struct dinode *ip) {
    uint8_t buf[BSIZE];
    bread(INODESTART + inum / IPB, buf);         // leer-modificar-escribir
    memcpy(buf + (inum % IPB) * ISIZE, ip, ISIZE);
    bwrite(INODESTART + inum / IPB, buf);
}

// Busca un bit en 0 en el bitmap, lo pone en 1 y devuelve su índice.
static int bitmap_alloc(uint32_t bmap, int n) {
    uint8_t buf[BSIZE];
    bread(bmap, buf);
    for (int i = 0; i < n; i++) {
        if ((buf[i / 8] & (1 << (i % 8))) == 0) {
            buf[i / 8] |= 1 << (i % 8);
            bwrite(bmap, buf);
            return i;
        }
    }
    return -1;
}

static int balloc(void) {
    int i = bitmap_alloc(DBMAP, NDATA);
    return i < 0 ? -1 : DATASTART + i;
}

// Busca name en el directorio dp; devuelve el inum o 0.
static uint32_t dirlookup(struct dinode *dp, const char *name) {
    struct dirent de[BSIZE / sizeof(struct dirent)];
    uint32_t nent = dp->size / sizeof(struct dirent);
    for (uint32_t i = 0; i < nent; i++) {
        uint32_t k = i % (BSIZE / sizeof(struct dirent));
        if (k == 0) bread(dp->addrs[i / (BSIZE / sizeof(struct dirent))], de);
        if (de[k].inum && strncmp(de[k].name, name, DIRSIZ) == 0)
            return de[k].inum;
    }
    return 0;
}

// Recorre la ruta desde la raíz. Si last != NULL, se detiene en el
// directorio padre y copia el último componente en last.
static uint32_t namei(const char *path, char *last) {
    char copy[256], *save, *tok, *next;
    uint32_t inum = ROOTINO;
    struct dinode d;

    snprintf(copy, sizeof copy, "%s", path);
    tok = strtok_r(copy, "/", &save);
    while (tok) {
        next = strtok_r(NULL, "/", &save);
        if (last && next == NULL) {             // último componente
            snprintf(last, DIRSIZ + 1, "%s", tok);
            return inum;
        }
        iread(inum, &d);
        if (d.type != T_DIR) return 0;
        uint32_t child = dirlookup(&d, tok);
        if (verbose) printf("  en el inodo %u: \"%s\" -> inodo %u\n", inum, tok, child);
        if (child == 0) return 0;
        inum = child;
        tok = next;
    }
    return inum;
}

// Agrega (name, inum) al final del directorio dinum, cuyo inodo es *dp.
static int diradd(uint32_t dinum, struct dinode *dp, const char *name, uint32_t inum) {
    struct dirent de[BSIZE / sizeof(struct dirent)];
    uint32_t bn = dp->size / BSIZE;
    if (bn >= NDIRECT) return -1;
    if (dp->size % BSIZE == 0) {                // hace falta un bloque nuevo
        int b = balloc();
        if (b < 0) return -1;
        dp->addrs[bn] = b;
        memset(de, 0, sizeof de);
    } else {
        bread(dp->addrs[bn], de);
    }
    struct dirent *e = &de[(dp->size % BSIZE) / sizeof(struct dirent)];
    e->inum = inum;
    strncpy(e->name, name, DIRSIZ);
    bwrite(dp->addrs[bn], de);
    dp->size += sizeof(struct dirent);
    iwrite(dinum, dp);
    return 0;
}

// Crea un archivo o directorio vacío en path; devuelve su inum.
static uint32_t create(const char *path, int type) {
    char name[DIRSIZ + 1];
    uint32_t parent = namei(path, name);
    struct dinode dp;
    if (parent == 0) { fprintf(stderr, "no existe el directorio padre\n"); exit(1); }
    iread(parent, &dp);
    if (dirlookup(&dp, name)) { fprintf(stderr, "%s ya existe\n", path); exit(1); }
    int inum = bitmap_alloc(IBMAP, NINODES);
    if (inum < 0) { fprintf(stderr, "no hay inodos libres\n"); exit(1); }
    struct dinode ip = { .type = type, .nlink = 1 };
    if (type == T_DIR) {                        // un bloque con "." y ".."
        struct dirent de[BSIZE / sizeof(struct dirent)] = {{ inum, "." }, { parent, ".." }};
        int b = balloc();
        if (b < 0) { fprintf(stderr, "disco lleno\n"); exit(1); }
        bwrite(b, de);
        ip.addrs[0] = b;
        ip.size = 2 * sizeof(struct dirent);
        ip.nlink = 2;
    }
    iwrite(inum, &ip);
    if (type == T_DIR) dp.nlink++;              // por el ".." del hijo
    if (diradd(parent, &dp, name, inum) < 0) { fprintf(stderr, "directorio lleno\n"); exit(1); }
    return inum;
}

static void phase(const char *what) {
    static int r0, w0;
    if (verbose && what == NULL)
        printf("    -> %d lecturas, %d escrituras\n", nreads - r0, nwrites - w0);
    if (what && verbose) printf("%s\n", what);
    r0 = nreads;
    w0 = nwrites;
}

static void do_mkfs(void) {
    uint8_t buf[BSIZE] = {0};
    for (int b = 0; b < NBLOCKS; b++) bwrite(b, buf);
    struct superblock sb = { MAGIC, NBLOCKS, NINODES, INODESTART, DATASTART };
    memcpy(buf, &sb, sizeof sb);
    bwrite(0, buf);
    memset(buf, 0, BSIZE);
    buf[0] = 0x07;                              // inodos 0, 1 (reservados) y 2 (raíz)
    bwrite(IBMAP, buf);
    buf[0] = 0x01;                              // bloque de datos 0 = bloque 8
    bwrite(DBMAP, buf);
    struct dinode root = { .type = T_DIR, .nlink = 2, .size = 2 * sizeof(struct dirent) };
    root.addrs[0] = DATASTART;
    iwrite(ROOTINO, &root);
    struct dirent de[BSIZE / sizeof(struct dirent)] = {{ ROOTINO, "." }, { ROOTINO, ".." }};
    bwrite(DATASTART, de);
    printf("mkfs: %d bloques de %d B, %d inodos de %d B, %d bloques de datos\n",
           NBLOCKS, BSIZE, NINODES, ISIZE, NDATA);
}

static void do_write(uint32_t inum, int nbytes) {
    uint8_t buf[BSIZE], iblk[BSIZE];
    uint32_t ib = INODESTART + inum / IPB, io = (inum % IPB) * ISIZE;
    struct dinode ip;
    for (int off = 0, i = 0; off < nbytes; off += BSIZE, i++) {
        char what[64];
        snprintf(what, sizeof what, "write() del bloque %d del archivo", i);
        phase(what);
        if (i >= NDIRECT) { fprintf(stderr, "archivo demasiado grande\n"); exit(1); }
        bread(ib, iblk);                        // leer el inodo
        memcpy(&ip, iblk + io, ISIZE);
        int b = balloc();                       // leer y escribir el bitmap
        if (b < 0) { fprintf(stderr, "disco lleno\n"); exit(1); }
        memset(buf, 'a' + i % 26, BSIZE);
        bwrite(b, buf);                         // escribir los datos
        ip.addrs[i] = b;
        ip.size = (uint32_t)(off + BSIZE < nbytes ? off + BSIZE : nbytes);
        memcpy(iblk + io, &ip, ISIZE);
        bwrite(ib, iblk);                       // escribir el inodo
        phase(NULL);
    }
}

static void do_read(const char *path) {
    uint8_t buf[BSIZE];
    struct dinode ip;
    phase("open()");
    uint32_t inum = namei(path, NULL);
    if (inum == 0) { fprintf(stderr, "%s: no existe\n", path); exit(1); }
    iread(inum, &ip);
    phase(NULL);
    for (uint32_t i = 0; i * BSIZE < ip.size; i++) {
        phase("read()");
        bread(ip.addrs[i], buf);                // el inodo ya está en memoria
        phase(NULL);
    }
    printf("%s: inodo %u, %u bytes\n", path, inum, ip.size);
}

static void do_ls(const char *path) {
    struct dinode dp, ip;
    struct dirent de[BSIZE / sizeof(struct dirent)];
    uint32_t inum = namei(path, NULL);
    if (inum == 0) { fprintf(stderr, "%s: no existe\n", path); exit(1); }
    iread(inum, &dp);
    for (uint32_t i = 0; i < dp.size / sizeof(struct dirent); i++) {
        uint32_t k = i % (BSIZE / sizeof(struct dirent));
        if (k == 0) bread(dp.addrs[i / (BSIZE / sizeof(struct dirent))], de);
        iread(de[k].inum, &ip);
        printf("%-12.12s inodo %2u  %-4s %6u B\n", de[k].name, de[k].inum,
               ip.type == T_DIR ? "dir" : "arch", ip.size);
    }
}

static void do_stat(uint32_t inum) {
    struct dinode ip;
    if (inum >= NINODES) { fprintf(stderr, "inodo fuera de rango\n"); exit(1); }
    iread(inum, &ip);
    printf("inodo %u: bloque %u, byte %u del bloque\n", inum,
           INODESTART + inum / IPB, (inum % IPB) * ISIZE);
    printf("  tipo %s, nlink %u, tamaño %u B\n",
           ip.type == T_DIR ? "dir" : ip.type == T_FILE ? "arch" : "libre",
           ip.nlink, ip.size);
    printf("  bloques:");
    for (int i = 0; i < NDIRECT && ip.addrs[i]; i++) printf(" %u", ip.addrs[i]);
    printf("\n");
}

int main(int argc, char *argv[]) {
    int a = 1;
    if (a < argc && strcmp(argv[a], "-v") == 0) { verbose = 1; a++; }
    if (argc - a < 2) {
        fprintf(stderr, "uso: %s [-v] img mkfs|mkdir|create|read|ls|stat ...\n", argv[0]);
        return 1;
    }
    const char *img = argv[a], *cmd = argv[a + 1], *arg = argc - a > 2 ? argv[a + 2] : "/";
    int mk = strcmp(cmd, "mkfs") == 0;
    fd = open(img, mk ? O_RDWR | O_CREAT | O_TRUNC : O_RDWR, 0644);
    if (fd < 0) { perror(img); return 1; }
    if (!mk) {
        struct superblock sb;
        if (pread(fd, &sb, sizeof sb, 0) != sizeof sb || sb.magic != MAGIC) {
            fprintf(stderr, "%s: no es una imagen vsfs\n", img);
            return 1;
        }
    }

    if (mk) {
        do_mkfs();
    } else if (strcmp(cmd, "mkdir") == 0) {
        phase("mkdir()");
        create(arg, T_DIR);
        phase(NULL);
    } else if (strcmp(cmd, "create") == 0 && argc - a > 3) {
        phase("create()");
        uint32_t inum = create(arg, T_FILE);
        phase(NULL);
        do_write(inum, atoi(argv[a + 3]));
        printf("%s: inodo %u\n", arg, inum);
    } else if (strcmp(cmd, "read") == 0) {
        do_read(arg);
    } else if (strcmp(cmd, "ls") == 0) {
        do_ls(arg);
    } else if (strcmp(cmd, "stat") == 0) {
        do_stat(atoi(arg));
    } else {
        fprintf(stderr, "comando desconocido: %s\n", cmd);
        return 1;
    }
    if (verbose) printf("total: %d lecturas, %d escrituras\n", nreads, nwrites);
    close(fd);
    return 0;
}

Compilación

Cómo se compilan y ejecutan los ejemplos.

MakefileDescargar Makefile
# Semana 14: implementación de sistemas de archivos y journaling.
#   make         compila todo
#   make test    corre cada programa (la imagen vsfs va a /tmp)
#   make clean   borra los binarios y la imagen
CFLAGS = -Wall -Wextra -g
PROGS  = vsfs bmapcalc journal fsyncbench
IMG    = /tmp/vsfs.img

all: $(PROGS)

$(PROGS): %: %.c
	$(CC) $(CFLAGS) -o $@ $<

test: $(PROGS)
	./vsfs $(IMG) mkfs
	./vsfs -v $(IMG) mkdir /foo
	./vsfs -v $(IMG) create /foo/bar 12288
	./vsfs -v $(IMG) read /foo/bar
	./vsfs $(IMG) ls /foo
	./vsfs $(IMG) stat 4
	./vsfs $(IMG) stat 37
	./bmapcalc
	./bmapcalc 0 5000 13000 70000 300000
	./bmapcalc -i 25
	./bmapcalc -max 4096 4 12
	./journal none
	./journal data
	./journal ordered
	./journal reorder
	./fsyncbench /tmp/fsb.dat 200

clean:
	rm -f $(PROGS) $(IMG) /tmp/fsb.dat

.PHONY: all test clean