Llamadas al sistema en C — ejercicios con contadores de bytes, O_APPEND, un ranking de partidas y tres fallos de lseek y read (y la partida que se negó a ir al principio)

Quería que la partida más reciente saliera la primera del fichero. Abrí partidas.sav con O_APPEND, hice lseek(fd, 0, SEEK_SET) para ir al principio, escribí… y la partida apareció al final. Repasé el lseek diez veces convencido de que estaba mal escrito. No lo estaba: con O_APPEND, el kernel lleva el puntero al final **antes de cada write**, diga lo que diga tu lseek. Lo vas a comprobar tú mismo en el nivel 2, y es el tipo de detalle que separa «me sé las funciones» de «sé lo que hace el kernel con ellas».

Estos son los ejercicios de llamadas al sistema en C. Si aún no has hecho la práctica, empieza por ella: aquí se usa el mismo fichero de partidas (cuatro ranuras de 20 bytes: Lia, Max, Ane y Teo), pero el código lo escribes tú.

Cómo usar este artículo

  1. Lee el enunciado y la salida esperada de cada nivel. Tu programa tiene que dar exactamente esa salida.
  2. Intenta resolverlo sin mirar la pista. Si te atascas más de 15 minutos, mírala.
  3. Haz «¿Qué falla aquí?» antes de las soluciones: predice la salida de cada fragmento y luego compruébala.
  4. Compara tu solución con la mía, y fíjate sobre todo en el «por qué está hecho así». Al final tienes el chuletario para repasar.

Todos los niveles parten de este fichero, que puedes crear con una función preparar como la de la práctica (un solo write de los 80 bytes):

Lia   nivel 03 0450
Max   nivel 12 2750
Ane   nivel 05 0980
Teo   nivel 07 1200

Nivel 1 (básico): contar bytes y líneas

Escribe una función void contar(const char *nombre) que abra el fichero, lo lea con un buffer de 16 bytes y muestre cuántos bytes tiene, cuántas líneas (cuántos \n) y cuántas lecturas devolvieron datos. Si el fichero no existe, que lo diga con perror y vuelva. Llámala con partidas.sav y con records.sav (que no existe).

Salida esperada:

partidas.sav: 80 bytes, 4 líneas, 5 lecturas con datos
records.sav: No such file or directory

El bucle de lectura de siempre, while ((leidos = read(fd, buf, sizeof buf)) > 0). Dentro, recorre el buffer buscando '\n', pero solo hasta leidos.

Nivel 2 (intermedio): añadir una partida con O_APPEND

Añade la partida Sam nivel 01 0050 (20 bytes) a partidas.sav abriéndolo con O_WRONLY | O_APPEND. Para comprobar cómo funciona O_APPEND, haz antes del write un lseek(fd, 0, SEEK_SET), como si quisieras ponerla al principio. Después muestra dónde ha quedado el puntero, abre el fichero para leer y enseña su tamaño en bytes y en ranuras, la última ranura (con SEEK_END) y la primera.

Salida esperada:

Tras el write, el puntero está en 100
Tamaño: 100 bytes = 5 ranuras
Última ranura: Sam   nivel 01 0050
Primera ranura: Lia   nivel 03 0450

El tamaño es lseek(fd, 0, SEEK_END) y las ranuras, el tamaño entre 20. La última ranura empieza 20 bytes antes del final. No olvides el '\0' antes de imprimir con %s.

Nivel 3 (desafío): el ranking

Abre partidas.sav con O_RDWR y recórrelo ranura a ranura (un read de 20 bytes por vuelta). En cada una, saca los puntos (están en el byte 15 de la ranura) y quédate con la mejor. Muestra en cada vuelta los puntos y cuál es la mejor hasta ese momento. Después marca a la ganadora escribiendo un * justo detrás del nombre (byte 3 de la ranura) y vuelca el fichero entero con su tamaño.

Salida esperada:

Ranura 0:  450 puntos, mejor hasta ahora: ranura 0
Ranura 1: 2750 puntos, mejor hasta ahora: ranura 1
Ranura 2:  980 puntos, mejor hasta ahora: ranura 1
Ranura 3: 1200 puntos, mejor hasta ahora: ranura 1
Marco la ranura 1 en el byte 23
--- partidas.sav (80 bytes) ---
Lia   nivel 03 0450
Max*  nivel 12 2750
Ane   nivel 05 0980
Teo   nivel 07 1200

atoi(ranura + 15) convierte "2750\n" en 2750 (se para al llegar al \n). Para la marca, la posición es 20 · mejor + 3. Y antes del volcado, vuelve al principio con lseek.

¿Qué falla aquí?

Tres fragmentos que compilan sin avisos con gcc -Wall -std=c11. Lee cada uno, predice qué imprime y después mira la explicación.

Fragmento 1: el último dígito

#include <stdio.h>
#include <fcntl.h>      /* open y las constantes O_... */
#include <unistd.h>     /* read, write, lseek, close */

int main(void) {
    int fd = open("partida.sav", O_RDWR | O_CREAT | O_TRUNC, 0644);
    write(fd, "Teo   nivel 07 1200\n", 20);

    char c;
    lseek(fd, -1, SEEK_END);                  /* quiero el último dígito */
    read(fd, &c, 1);
    printf("Último dígito: [%c]\n", c);
    close(fd);
    return 0;
}

¿Qué imprime? Piénsalo antes de seguir.

Esta es la salida real:

Último dígito: [
]

El corchete de cierre ha saltado a la línea siguiente. El fichero mide 20 bytes y SEEK_END apunta al 20, justo después del último byte. -1 lleva al 19, que es el \n del final de la línea, así que c vale '\n' y printf imprime un salto de línea entre los corchetes.

Por qué -2 y no -1 Los seis últimos bytes de la ranura con su posición (14 a 19) y su desplazamiento desde el final (-6 a -1). SEEK_END apunta a 20, fuera del fichero. -1 cae en el salto de línea (en rojo) y -2 en el último dígito, el 0 (en verde). El final de "Teo nivel 07 1200\n" · 14 -6 1 15 -5 2 16 -4 0 17 -3 0 18 -2 \n 19 -1 fin 20 SEEK_END posición desde el final lseek(fd, -2, SEEK_END) → el '0' que buscábamos lseek(fd, -1, SEEK_END) → el salto de línea
SEEK_END apunta justo después del último byte; si el fichero acaba en \n, el último carácter visible está en -2.

Contar desde el final como si el último byte fuera el último carácter visible. En un fichero de texto, el último byte casi siempre es el \n.

El arreglo es -2:

lseek(fd, -2, SEEK_END);                  /* -1 es el '\n': el dígito está en -2 */
read(fd, &c, 1);

Y ahora sí:

Último dígito: [0]

Si no sabes si el fichero acaba en \n, lee el último byte con -1 y, si es '\n', retrocede uno más. Es lo que hace la pregunta del guion de prácticas: ¿por qué −2 y no −1?

Fragmento 2: el nombre que no era

#include <stdio.h>
#include <fcntl.h>      /* open y las constantes O_... */
#include <unistd.h>     /* read, write, lseek, close */
#include <string.h>     /* strlen */

/* Crea un fichero con el texto dado */
static void crear(const char *fichero, const char *texto) {
    int fd = open(fichero, O_WRONLY | O_CREAT | O_TRUNC, 0644);
    write(fd, texto, strlen(texto));
    close(fd);
}

/* Carga el nombre del jugador en buf */
static void cargar(const char *fichero, char *buf, size_t cap) {
    int fd = open(fichero, O_RDONLY);
    read(fd, buf, cap);
    close(fd);
}

int main(void) {
    crear("jugador1.txt", "Teodoro");
    crear("jugador2.txt", "Lia");
    char nombre[32] = {0};                    /* empieza lleno de '\0' */
    cargar("jugador1.txt", nombre, sizeof nombre - 1);
    printf("Jugador 1: %s\n", nombre);
    cargar("jugador2.txt", nombre, sizeof nombre - 1);
    printf("Jugador 2: %s\n", nombre);
    return 0;
}

¿Qué imprime la segunda línea?

Jugador 1: Teodoro
Jugador 2: Liadoro

«Liadoro». El buffer nombre empieza lleno de ceros. La primera lectura copia 7 bytes (Teodoro) y deja un cero en la posición 7, así que la primera línea sale bien. La segunda lectura copia 3 bytes (Lia) sobre las posiciones 0-2, pero read **no pone el '\0'**: las posiciones 3-6 siguen teniendo doro, y %s sigue imprimiendo hasta el primer cero.

Momentonombre[0..7]%s imprime
Al declararlo\0 \0 \0 \0 \0 \0 \0 \0(nada)
Tras leer jugador1 (7 bytes)T e o d o r o \0Teodoro
Tras leer jugador2 (3 bytes)L i a d o r o \0Liadoro

Ignorar lo que devuelve read. Ese número es justo lo que necesitas para cerrar la cadena.

El arreglo: guardar lo que devuelve read y cerrar la cadena ahí.

/* Carga el nombre del jugador en buf */
static void cargar(const char *fichero, char *buf, size_t cap) {
    int fd = open(fichero, O_RDONLY);
    ssize_t n = read(fd, buf, cap);
    close(fd);
    buf[n > 0 ? n : 0] = '\0';               /* cierra la cadena donde acaba lo leído */
}

Con ese cambio, cada nombre termina donde termina lo leído:

Jugador 1: Teodoro
Jugador 2: Lia

El n > 0 ? n : 0 evita escribir en buf[-1] si read devolviera −1.

Fragmento 3: guardar y cargar con el mismo descriptor

#include <stdio.h>
#include <fcntl.h>      /* open y las constantes O_... */
#include <unistd.h>     /* read, write, lseek, close */

int main(void) {
    int fd = open("partida.sav", O_WRONLY | O_CREAT | O_TRUNC, 0644);
    write(fd, "Lia   nivel 03 0450\n", 20);
    lseek(fd, 0, SEEK_SET);                   /* vuelvo al principio para leerla */

    char buf[21];
    ssize_t n = read(fd, buf, 20);
    printf("read devuelve %zd\n", n);
    if (n == -1) {
        perror("read");
        close(fd);
        return 1;
    }
    buf[n] = '\0';
    printf("Cargada: %s", buf);
    close(fd);
    return 0;
}

¿Se carga la partida?

read devuelve -1
read: Bad file descriptor

No. El fichero se abrió con O_WRONLY: ese descriptor solo sirve para escribir. El write funciona, el lseek también (mover el puntero no es leer), pero read devuelve −1 y perror dice Bad file descriptor, que aquí significa «este descriptor no sirve para leer». El programa no se cuelga ni lee basura gracias a que comprueba el −1.

Decidir el modo de apertura pensando solo en la primera operación. Si vas a escribir y después leer con el mismo descriptor, necesitas O_RDWR.

El arreglo es cambiar O_WRONLY por O_RDWR en el open:

int fd = open("partida.sav", O_RDWR | O_CREAT | O_TRUNC, 0644);

Ahora el mismo descriptor sirve para las dos cosas:

read devuelve 20
Cargada: Lia   nivel 03 0450

Soluciones

Solución del nivel 1

#include <stdio.h>
#include <fcntl.h>      /* open y las constantes O_... */
#include <unistd.h>     /* read, write, lseek, close */

#define TAM_RANURA 20
#define PARTIDAS "Lia   nivel 03 0450\n" "Max   nivel 12 2750\n" \
                 "Ane   nivel 05 0980\n" "Teo   nivel 07 1200\n"

/* Crea el fichero de partidas con las cuatro ranuras de ejemplo */
static int preparar(const char *nombre) {
    int fd = open(nombre, O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (fd == -1) {
        perror(nombre);
        return -1;
    }
    write(fd, PARTIDAS, 4 * TAM_RANURA);
    close(fd);
    return 0;
}

/* Cuenta bytes y líneas leyendo de 16 en 16 */
static void contar(const char *nombre) {
    int fd = open(nombre, O_RDONLY);
    if (fd == -1) {
        perror(nombre);
        return;
    }
    char buf[16];
    ssize_t leidos;
    long bytes = 0, lineas = 0;
    int lecturas = 0;
    while ((leidos = read(fd, buf, sizeof buf)) > 0) {
        lecturas++;
        bytes += leidos;
        for (ssize_t i = 0; i < leidos; i++)  /* solo los leidos, no 16 */
            if (buf[i] == '\n')
                lineas++;
    }
    close(fd);
    printf("%s: %ld bytes, %ld líneas, %d lecturas con datos\n",
           nombre, bytes, lineas, lecturas);
}

int main(void) {
    if (preparar("partidas.sav") == -1)
        return 1;
    contar("partidas.sav");
    contar("records.sav");                    /* no existe */
    return 0;
}

Salida (la del enunciado):

partidas.sav: 80 bytes, 4 líneas, 5 lecturas con datos
records.sav: No such file or directory

Por qué está hecho así:

  • **perror(nombre) y return** en lugar de salir del programa: así main puede seguir con el siguiente fichero. El nombre delante del mensaje dice qué fichero falló.
  • **for (i = 0; i < leidos; i++)**, no i < 16. En este fichero las 5 lecturas devuelven 16 (5 · 16 = 80), así que con 16 daría lo mismo… por casualidad. Con 81 bytes, la última lectura devolvería 1 y contarías 15 bytes de restos.
  • **long para los contadores:** un int llega a unos 2 GB, y un fichero puede ser más grande.
  • **No hace falta '\0':** no usamos el buffer como cadena, solo miramos sus bytes de uno en uno.
Lecturaread devuelve\n en el trozobyteslineas
1160160
2161 (byte 19)321
3161 (byte 39)482
4161 (byte 59)643
5161 (byte 79)804
60—804

Comprobación: los \n están en 19, 39, 59 y 79 (20 · n + 19), y cada uno cae en una lectura distinta: el 19 en la 2 (bytes 16-31), el 39 en la 3 (32-47), el 59 en la 4 (48-63) y el 79 en la 5 (64-79).

Solución del nivel 2

#include <stdio.h>
#include <fcntl.h>      /* open y las constantes O_... */
#include <unistd.h>     /* read, write, lseek, close */

#define TAM_RANURA 20
#define PARTIDAS "Lia   nivel 03 0450\n" "Max   nivel 12 2750\n" \
                 "Ane   nivel 05 0980\n" "Teo   nivel 07 1200\n"

/* Crea el fichero de partidas con las cuatro ranuras de ejemplo */
static int preparar(const char *nombre) {
    int fd = open(nombre, O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (fd == -1) {
        perror(nombre);
        return -1;
    }
    write(fd, PARTIDAS, 4 * TAM_RANURA);
    close(fd);
    return 0;
}

int main(void) {
    if (preparar("partidas.sav") == -1)
        return 1;

    /* O_APPEND: el kernel lleva el puntero al final antes de CADA write */
    int fd = open("partidas.sav", O_WRONLY | O_APPEND);
    if (fd == -1) {
        perror("partidas.sav");
        return 1;
    }
    lseek(fd, 0, SEEK_SET);                   /* intento escribir al principio... */
    write(fd, "Sam   nivel 01 0050\n", TAM_RANURA);
    printf("Tras el write, el puntero está en %ld\n", (long) lseek(fd, 0, SEEK_CUR));
    close(fd);

    fd = open("partidas.sav", O_RDONLY);
    off_t tam = lseek(fd, 0, SEEK_END);
    printf("Tamaño: %ld bytes = %ld ranuras\n", (long) tam, (long) tam / TAM_RANURA);

    char ranura[TAM_RANURA + 1];
    lseek(fd, -TAM_RANURA, SEEK_END);         /* la última ranura */
    read(fd, ranura, TAM_RANURA);
    ranura[TAM_RANURA] = '\0';
    printf("Última ranura: %s", ranura);

    lseek(fd, 0, SEEK_SET);                   /* y la primera sigue intacta */
    read(fd, ranura, TAM_RANURA);
    printf("Primera ranura: %s", ranura);
    close(fd);
    return 0;
}

Salida (la del enunciado):

Tras el write, el puntero está en 100
Tamaño: 100 bytes = 5 ranuras
Última ranura: Sam   nivel 01 0050
Primera ranura: Lia   nivel 03 0450

Por qué está hecho así:

  • **O_WRONLY | O_APPEND sin O_CREAT:** añadir solo tiene sentido si el fichero ya existe. Si no existiera, open daría −1 y lo diríamos, en lugar de crear un fichero con una sola partida.
  • **El lseek(fd, 0, SEEK_SET) no sirve de nada, y eso es lo que queríamos comprobar: el puntero queda en 100** (80 + 20), no en 20. Con O_APPEND, cada write es «mueve al final y escribe» en un solo paso del kernel. Esto, además, hace que dos procesos que añaden a la vez a un mismo log no se pisen.
  • **Un segundo open con O_RDONLY para leer:** el primer descriptor era de solo escritura (fragmento 3).
  • **tam / TAM_RANURA:** 100 / 20 = 5 ranuras. Funciona porque el formato es fijo.
  • **La primera ranura sin el '\0' de nuevo:** reutilizamos ranura, que ya tenía el '\0' en la posición 20 desde la lectura anterior, y volvemos a leer exactamente 20 bytes. Si leyéramos menos, quedarían restos (fragmento 2).

Solución del nivel 3

#include <stdio.h>
#include <fcntl.h>      /* open y las constantes O_... */
#include <unistd.h>     /* read, write, lseek, close */
#include <stdlib.h>     /* atoi */

#define TAM_RANURA 20
#define PARTIDAS "Lia   nivel 03 0450\n" "Max   nivel 12 2750\n" \
                 "Ane   nivel 05 0980\n" "Teo   nivel 07 1200\n"
#define POS_PUNTOS 15
#define POS_MARCA 3                       /* el primer espacio tras el nombre */

/* Crea el fichero de partidas con las cuatro ranuras de ejemplo */
static int preparar(const char *nombre) {
    int fd = open(nombre, O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (fd == -1) {
        perror(nombre);
        return -1;
    }
    write(fd, PARTIDAS, 4 * TAM_RANURA);
    close(fd);
    return 0;
}

int main(void) {
    if (preparar("partidas.sav") == -1)
        return 1;
    int fd = open("partidas.sav", O_RDWR);
    if (fd == -1) {
        perror("partidas.sav");
        return 1;
    }

    /* 1. Recorrer las ranuras de una en una y quedarse con la mejor */
    char ranura[TAM_RANURA + 1];
    int n = 0, mejor = -1, max = -1;
    while (read(fd, ranura, TAM_RANURA) == TAM_RANURA) {
        ranura[TAM_RANURA] = '\0';
        int puntos = atoi(ranura + POS_PUNTOS);   /* "2750\n" -> 2750 */
        if (puntos > max) {                   /* > y no >=: en empate gana la primera */
            max = puntos;
            mejor = n;
        }
        printf("Ranura %d: %4d puntos, mejor hasta ahora: ranura %d\n", n, puntos, mejor);
        n++;
    }

    /* 2. Marcar al ganador con un '*' justo detrás del nombre */
    off_t pos = lseek(fd, (off_t) mejor * TAM_RANURA + POS_MARCA, SEEK_SET);
    write(fd, "*", 1);
    printf("Marco la ranura %d en el byte %ld\n", mejor, (long) pos);

    /* 3. Volcar el fichero entero para comprobarlo */
    char buf[128];
    lseek(fd, 0, SEEK_SET);
    ssize_t leidos = read(fd, buf, sizeof buf - 1);
    buf[leidos] = '\0';
    printf("--- partidas.sav (%zd bytes) ---\n%s", leidos, buf);
    close(fd);
    return 0;
}

Salida (la del enunciado):

Ranura 0:  450 puntos, mejor hasta ahora: ranura 0
Ranura 1: 2750 puntos, mejor hasta ahora: ranura 1
Ranura 2:  980 puntos, mejor hasta ahora: ranura 1
Ranura 3: 1200 puntos, mejor hasta ahora: ranura 1
Marco la ranura 1 en el byte 23
--- partidas.sav (80 bytes) ---
Lia   nivel 03 0450
Max*  nivel 12 2750
Ane   nivel 05 0980
Teo   nivel 07 1200

El corazón es el bucle de búsqueda:

/* 1. Recorrer las ranuras de una en una y quedarse con la mejor */
char ranura[TAM_RANURA + 1];
int n = 0, mejor = -1, max = -1;
while (read(fd, ranura, TAM_RANURA) == TAM_RANURA) {
    ranura[TAM_RANURA] = '\0';
    int puntos = atoi(ranura + POS_PUNTOS);   /* "2750\n" -> 2750 */
    if (puntos > max) {                   /* > y no >=: en empate gana la primera */
        max = puntos;
        mejor = n;
    }
    printf("Ranura %d: %4d puntos, mejor hasta ahora: ranura %d\n", n, puntos, mejor);
    n++;
}
  • **while (read(...) == TAM_RANURA)**, no > 0: queremos ranuras completas. Si el fichero acabara con una ranura a medias (por ejemplo, de 7 bytes), atoi leería basura; así, esa ranura simplemente no se procesa.
  • **atoi(ranura + POS_PUNTOS):** ranura + 15 es un puntero al byte 15, el primer dígito. atoi convierte "2750\n" en 2750 y se para en el \n. Los ceros de delante no molestan: "0450" es 450.
  • **> y no >=:** si dos jugadores empataran, se quedaría la primera ranura. Con >=, ganaría la última.
  • **mejor = -1 y max = -1 al empezar:** cualquier puntuación real (0 o más) es mayor que −1, así que la primera ranura siempre pasa a ser «la mejor hasta ahora».
Vuelta (n)Ranura leídapuntos¿puntos > max?maxmejor
0Lia450450 > −1 sí4500
1Max27502750 > 450 sí27501
2Ane980980 > 2750 no27501
3Teo12001200 > 2750 no27501
4(read devuelve 0)—sale del while27501
Resultado del nivel 3 Barras con los puntos de las cuatro ranuras: Lia 450, Max 2750, Ane 980 y Teo 1200; Max es la mejor. Debajo, los bytes de la ranura 1 tras escribir un asterisco en el byte 23: Max*. ranking.c: la mejor ranura y su marca ranura 0 · Lia 450 ranura 1 · Max 2750 ← mejor ranura 2 · Ane 980 ranura 3 · Teo 1200 Ranura 1 tras write(fd, "*", 1) en el byte 20 · 1 + 3 = 23: M 20 a 21 x 22 * 23 · 24 · 25 n 26 i 27 v 28 e 29 l 30 · 31 1 32 2 33 · 34 2 35 7 36 5 37 0 38
Se recorre el fichero ranura a ranura para encontrar el máximo y luego se vuelve con lseek a escribir un solo byte.

Después, la marca: lseek(fd, 1 · 20 + 3, SEEK_SET) = byte 23, justo el primer espacio tras Max, y write de 1 byte. Por eso Max pasa a Max* y la línea sigue midiendo 20: el tamaño final es 80. Para volcar el fichero hay que volver al 0 con lseek, porque tras el write el puntero estaba en el 24.

Míralo paso a paso en Python Tutor

Python Tutor es perfecto para el fragmento 2, porque dibuja el array casilla a casilla; elige C. No trabaja con ficheros reales, así que simular_read hace lo mismo que read: copia los bytes y no pone el '\0'.

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

/* Simula read(): copia los bytes y NO pone '\0' */
int simular_read(char *buf, const char *fichero) {
    int n = strlen(fichero);
    for (int i = 0; i < n; i++)
        buf[i] = fichero[i];
    return n;
}

int main(void) {
    char nombre[12] = {0};
    int n = simular_read(nombre, "Teodoro");
    printf("Jugador 1: %s\n", nombre);
    n = simular_read(nombre, "Lia");
    printf("Jugador 2: %s\n", nombre);    /* restos del anterior */
    nombre[n] = '\0';                     /* el arreglo */
    printf("Arreglado: %s\n", nombre);
    return 0;
}

Salida:

Jugador 1: Teodoro
Jugador 2: Liadoro
Arreglado: Lia

Qué observar:

  1. Al declarar nombre[12] = {0}, las 12 casillas aparecen con \0.
  2. Tras la primera llamada, las casillas 0-6 tienen Teodoro y la 7 sigue con \0: por eso el primer printf sale bien.
  3. Tras la segunda llamada, solo cambian las casillas 0-2 (L, i, a). Las 3-6 conservan d, o, r, o: es el «Liadoro».
  4. nombre[n] = '\0' con n = 3 pone un cero en la casilla 3, y la cadena termina ahí. Las casillas 4-6 siguen con oro, pero ya no importa: %s se para en el primer \0.

Chuletario: llamadas al sistema para ficheros

Lo esencial en 30 segundos: open da un descriptor (el libre más bajo; 0, 1 y 2 ya están ocupados) o −1. read y write mueven bytes desde el puntero y te dicen cuántos: usa ese número. read devuelve 0 al final y no pone el '\0'. lseek mueve el puntero desde el principio, desde donde estás o desde el final (que es el tamaño). Cierra todo lo que abres y comprueba siempre el −1.

Quiero…CódigoResultado
Abrir para leeropen("f", O_RDONLY)fd (3 si es el primero) o −1
Crear o vaciar para escribiropen("f", O_WRONLY | O_CREAT | O_TRUNC, 0644)rw-r--r--, tamaño 0
Añadir al finalopen("f", O_WRONLY | O_APPEND)cada write va al final
Crear solo si no existeO_WRONLY | O_CREAT | O_EXCL, 0644−1 y File exists si ya estaba
Leer y escribiropen("f", O_RDWR)un solo puntero para las dos cosas
Saber dónde estoylseek(fd, 0, SEEK_CUR)la posición, sin moverse
Saber cuánto midelseek(fd, 0, SEEK_END)el tamaño (y el puntero queda al final)
Ir a la ranura nlseek(fd, n * 20, SEEK_SET)ranura 2 → 40
Último carácter de un textolseek(fd, -2, SEEK_END)−1 sería el \n
Escribir en pantalla sin printfwrite(1, texto, strlen(texto))1 = stdout, 2 = stderr

Plantilla del bucle de lectura (niveles 1 y 3, y la copia de la práctica):

char buf[16];
ssize_t leidos;
while ((leidos = read(fd, buf, sizeof buf)) > 0) {
    /* usa SOLO buf[0] .. buf[leidos - 1] */
}
/* aquí leidos vale 0 (fin) o -1 (error) */

Plantilla para leer una ranura como cadena (nivel 2 y fragmento 2):

char r[TAM + 1];                       /* +1 para el '\0' */
lseek(fd, (off_t) n * TAM, SEEK_SET);
ssize_t k = read(fd, r, TAM);
r[k > 0 ? k : 0] = '\0';

Plantilla para sobrescribir un campo (práctica y nivel 3):

lseek(fd, (off_t) n * TAM + DESPLAZAMIENTO, SEEK_SET);
write(fd, campo, LONGITUD_DEL_CAMPO);  /* sin el '\0' */
SíntomaCausa probable
Restos de lo que había antes al final del ficheroFalta O_TRUNC al reescribir
No such file or directoryEl fichero no existe o la ruta está mal (o falta O_CREAT)
File existsO_CREAT | O_EXCL con un fichero que ya estaba
Bad file descriptorLeer con O_WRONLY, escribir con O_RDONLY o usar un fd cerrado o −1
Basura o restos al imprimir lo leídoFalta buf[leidos] = '\0'
La copia mide más que el originalwrite con el tamaño del buffer en vez de leidos
Lo escrito aparece al final aunque hice lseekEl fichero está abierto con O_APPEND
Sale un salto de línea en lugar del último carácterlseek(fd, -1, SEEK_END): el último byte es el \n
Permisos raros en el fichero creadoO_CREAT sin el modo, o 644 sin el 0

Antes de entregar, pregúntate:

  1. ¿Compruebo el −1 de cada open (y cierro lo que ya estaba abierto si algo falla)?
  2. ¿Uso lo que devuelve read (y write) en lugar de tamaños fijos?
  3. ¿Pongo el '\0' antes de tratar lo leído como cadena, y tengo sitio para él?
  4. ¿El modo de apertura (O_RDONLY, O_WRONLY, O_RDWR, O_TRUNC, O_APPEND) es el que necesitan todas las operaciones que hago con ese descriptor?
Sigue aprendiendoTodos los artículos de C

¿Te ha servido este artículo? Así puedes apoyar SergioLearns →

Sigue aprendiendo

Deja una respuesta

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