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ú.
Tabla de Contenidos
Cómo usar este artículo
- Lee el enunciado y la salida esperada de cada nivel. Tu programa tiene que dar exactamente esa salida.
- Intenta resolverlo sin mirar la pista. Si te atascas más de 15 minutos, mírala.
- Haz «¿Qué falla aquí?» antes de las soluciones: predice la salida de cada fragmento y luego compruébala.
- 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.
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.
| Momento | nombre[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 \0 | Teodoro |
| Tras leer jugador2 (3 bytes) | L i a d o r o \0 | Liadoro |
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)yreturn** en lugar de salir del programa: asímainpuede seguir con el siguiente fichero. El nombre delante del mensaje dice qué fichero falló. - **
for (i = 0; i < leidos; i++)**, noi < 16. En este fichero las 5 lecturas devuelven 16 (5 · 16 = 80), así que con16daría lo mismo… por casualidad. Con 81 bytes, la última lectura devolvería 1 y contarías 15 bytes de restos. - **
longpara los contadores:** unintllega 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.
| Lectura | read devuelve | \n en el trozo | bytes | lineas |
|---|---|---|---|---|
| 1 | 16 | 0 | 16 | 0 |
| 2 | 16 | 1 (byte 19) | 32 | 1 |
| 3 | 16 | 1 (byte 39) | 48 | 2 |
| 4 | 16 | 1 (byte 59) | 64 | 3 |
| 5 | 16 | 1 (byte 79) | 80 | 4 |
| 6 | 0 | — | 80 | 4 |
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_APPENDsinO_CREAT:** añadir solo tiene sentido si el fichero ya existe. Si no existiera,opendarí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. ConO_APPEND, cadawritees «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
openconO_RDONLYpara 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:** reutilizamosranura, 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),atoileería basura; así, esa ranura simplemente no se procesa. - **
atoi(ranura + POS_PUNTOS):**ranura + 15es un puntero al byte 15, el primer dígito.atoiconvierte"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 = -1ymax = -1al 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ída | puntos | ¿puntos > max? | max | mejor |
|---|---|---|---|---|---|
| 0 | Lia | 450 | 450 > −1 sí | 450 | 0 |
| 1 | Max | 2750 | 2750 > 450 sí | 2750 | 1 |
| 2 | Ane | 980 | 980 > 2750 no | 2750 | 1 |
| 3 | Teo | 1200 | 1200 > 2750 no | 2750 | 1 |
| 4 | (read devuelve 0) | — | sale del while | 2750 | 1 |
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:
- Al declarar
nombre[12] = {0}, las 12 casillas aparecen con\0. - Tras la primera llamada, las casillas 0-6 tienen
Teodoroy la 7 sigue con\0: por eso el primerprintfsale bien. - Tras la segunda llamada, solo cambian las casillas 0-2 (
L,i,a). Las 3-6 conservand,o,r,o: es el «Liadoro». nombre[n] = '\0'conn = 3pone un cero en la casilla 3, y la cadena termina ahí. Las casillas 4-6 siguen conoro, pero ya no importa:%sse 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ódigo | Resultado |
|---|---|---|
| Abrir para leer | open("f", O_RDONLY) | fd (3 si es el primero) o −1 |
| Crear o vaciar para escribir | open("f", O_WRONLY | O_CREAT | O_TRUNC, 0644) | rw-r--r--, tamaño 0 |
| Añadir al final | open("f", O_WRONLY | O_APPEND) | cada write va al final |
| Crear solo si no existe | O_WRONLY | O_CREAT | O_EXCL, 0644 | −1 y File exists si ya estaba |
| Leer y escribir | open("f", O_RDWR) | un solo puntero para las dos cosas |
| Saber dónde estoy | lseek(fd, 0, SEEK_CUR) | la posición, sin moverse |
| Saber cuánto mide | lseek(fd, 0, SEEK_END) | el tamaño (y el puntero queda al final) |
Ir a la ranura n | lseek(fd, n * 20, SEEK_SET) | ranura 2 → 40 |
| Último carácter de un texto | lseek(fd, -2, SEEK_END) | −1 sería el \n |
Escribir en pantalla sin printf | write(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íntoma | Causa probable |
|---|---|
| Restos de lo que había antes al final del fichero | Falta O_TRUNC al reescribir |
No such file or directory | El fichero no existe o la ruta está mal (o falta O_CREAT) |
File exists | O_CREAT | O_EXCL con un fichero que ya estaba |
Bad file descriptor | Leer con O_WRONLY, escribir con O_RDONLY o usar un fd cerrado o −1 |
| Basura o restos al imprimir lo leído | Falta buf[leidos] = '\0' |
| La copia mide más que el original | write con el tamaño del buffer en vez de leidos |
Lo escrito aparece al final aunque hice lseek | El fichero está abierto con O_APPEND |
| Sale un salto de línea en lugar del último carácter | lseek(fd, -1, SEEK_END): el último byte es el \n |
| Permisos raros en el fichero creado | O_CREAT sin el modo, o 644 sin el 0 |
Antes de entregar, pregúntate:
- ¿Compruebo el −1 de cada
open(y cierro lo que ya estaba abierto si algo falla)? - ¿Uso lo que devuelve
read(ywrite) en lugar de tamaños fijos? - ¿Pongo el
'\0'antes de tratar lo leído como cadena, y tengo sitio para él? - ¿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?
¿Te ha servido este artículo? Así puedes apoyar SergioLearns →
