System calls in C — practice saving, copying and editing game saves with open, read, write and lseek (and the backup that was 96 bytes long)
The first backup I made of my saved games was 96 bytes long. The original was 80. I opened both files and the end of the copy had 16 bytes repeated from the previous line, like an echo. The bug was a single word: I had written write(out, buf, 32) instead of write(out, buf, nread). In the last round of the loop only 16 bytes had been read, but I wrote all 32 of the buffer, and the other 16 were leftovers from the previous round. The numbers add up: 3 rounds · 32 = 96. The lesson I took away, and which you’ll see repeated in all three programs: with system calls, the number they return matters as much as what they do.
This is the practice for the system calls theory. There are three medium-level programs on the same file, saves.sav, with four saved games of 20 bytes each: the first saves it slot by slot, the second makes a backup with a read and write loop, and the third edits the points of one game without touching the rest. The data looks like real data: names of different lengths, one- and two-digit levels, a file that doesn’t exist and a slot that isn’t there.
Table of Contents
Before you start
Make sure you’re clear on:
- That
openreturns a descriptor (usually 3) or −1, and whatO_RDONLY,O_WRONLY,O_RDWR,O_CREATandO_TRUNCmean. - That
readandwritereturn how many bytes they moved and thatreadreturns 0 when it reaches the end. - That
readdoesn’t add the'\0'. - How
lseekmoves the pointer withSEEK_SET,SEEK_CURandSEEK_END.
If any of these sounds shaky, review the theory before carrying on.
Each program creates its files in the folder you run it from. Compile with gcc -Wall -std=c11 save.c -o save, run with ./save and look at the result with cat saves.sav or with ls -l (it tells you the size in bytes, which is what we’ll be checking all the time).
Program 1: saving the games
We have four players with their level and points in three arrays. They must be saved in saves.sav with a fixed format of 20 bytes per game, checking that each write writes what it should and, at the end, showing the file on screen by reading it with read and writing it to descriptor 1. What makes it tricky: names and numbers have different lengths (3 and 12, 450 and 2750), and yet every slot must be the same size.
The idea
If every saved game has the same size, the position of any of them can be computed: slot n starts at byte 20 · n. That’s what programs 2 and 3 will rely on, so here we make sure the format is fixed:
To get exactly those 20 bytes we use snprintf (which writes formatted text into an array, like printf but in memory) and then pass the result to write. The steps: open with O_TRUNC (saving = starting from scratch), a loop that formats and writes each slot, close, and open again to read and dump it to the screen.
Before coding, think about what snprintf returns: the number of characters it wrote, not counting the '\0'. That number is exactly what write needs.
The code
#include <stdio.h>
#include <fcntl.h> /* open and the O_... constants */
#include <unistd.h> /* read, write, lseek, close */
#define SLOT_SIZE 20
int main(void) {
const char *names[] = {"Lia", "Max", "Ane", "Teo"};
int levels[] = {3, 12, 5, 7};
int points[] = {450, 2750, 980, 1200};
/* O_TRUNC: every time we save, the file starts from scratch */
int fd = open("saves.sav", O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd == -1) {
perror("saves.sav");
return 1;
}
char slot[64]; /* plenty: snprintf never overflows */
for (int i = 0; i < 4; i++) {
/* %-6s: name left-aligned in 6 places; %02d and %04d: leading zeros */
int n = snprintf(slot, sizeof slot, "%-6snivel %02d %04d\n",
names[i], levels[i], points[i]);
ssize_t written = write(fd, slot, n); /* n = 20: without the '\0' */
if (written != SLOT_SIZE) {
perror("write");
close(fd);
return 1;
}
printf("Slot %d: write returns %zd, the pointer ends at %ld\n",
i, written, (long) lseek(fd, 0, SEEK_CUR));
}
close(fd);
/* Re-read the file and dump it to standard output (fd 1) */
fd = open("saves.sav", O_RDONLY);
if (fd == -1) {
perror("saves.sav");
return 1;
}
char buf[64];
ssize_t nread;
printf("--- saves.sav ---\n");
fflush(stdout); /* flushes printf before using write */
while ((nread = read(fd, buf, sizeof buf)) > 0)
write(1, buf, nread); /* nread, not sizeof buf */
close(fd);
return 0;
}
The key line is the snprintf format. Let’s take a closer look:
for (int i = 0; i < 4; i++) {
/* %-6s: name left-aligned in 6 places; %02d and %04d: leading zeros */
int n = snprintf(slot, sizeof slot, "%-6snivel %02d %04d\n",
names[i], levels[i], points[i]);
ssize_t written = write(fd, slot, n); /* n = 20: without the '\0' */
if (written != SLOT_SIZE) {
perror("write");
close(fd);
return 1;
}
printf("Slot %d: write returns %zd, the pointer ends at %ld\n",
i, written, (long) lseek(fd, 0, SEEK_CUR));
}
%-6s: the name, left-aligned and padded with spaces up to 6 characters."Lia"becomes"Lia "(3 letters + 3 spaces). Without the-, the spaces would go in front.levelis fixed text (5 letters and a space).%02d: the level with at least 2 digits, with leading zeros: 3 →03, 12 →12.%04d: the points with 4 digits: 450 →0450.\nat the end.
Count: 6 + 6 + 2 + 1 + 4 + 1 = 20. And snprintf returns 20, which is what we pass to write. The slot array is 64 bytes, far more than needed: that way snprintf never has to cut anything, and there’s room for the '\0' it adds (and which we don’t write to the file).
The loop trace:
i | Name | snprintf returns | write returns | Pointer after the write |
|---|---|---|---|---|
| 0 | Lia | 20 | 20 | 20 |
| 1 | Max | 20 | 20 | 40 |
| 2 | Ane | 20 | 20 | 60 |
| 3 | Teo | 20 | 20 | 80 |
The second part is a home-made cat:
char buf[64];
ssize_t nread;
printf("--- saves.sav ---\n");
fflush(stdout); /* flushes printf before using write */
while ((nread = read(fd, buf, sizeof buf)) > 0)
write(1, buf, nread); /* nread, not sizeof buf */
close(fd);
It reads 64 bytes at a time and writes to descriptor 1 (the screen) the bytes it has read. With 80 bytes, the first round reads 64, the second 16 and the third 0, which ends the while.
Output
Slot 0: write returns 20, the pointer ends at 20 Slot 1: write returns 20, the pointer ends at 40 Slot 2: write returns 20, the pointer ends at 60 Slot 3: write returns 20, the pointer ends at 80 --- saves.sav --- Lia level 03 0450 Max level 12 2750 Ane level 05 0980 Teo level 07 1200
Check: each write returned 20 and the pointer moved forward 20 at a time up to 80 (4 · 20 = 80). The four lines have their columns perfectly aligned, even though Lia and Max have one- and two-digit levels: %02d and %04d make sure 3 takes up the same space as 12. And the dump comes out after the --- saves.sav --- line thanks to fflush (why, in the next section).
Questions you might ask
**Why fflush(stdout) before the write loop?** Because printf doesn’t write directly: it keeps the text in a library buffer and sends it with write(1, …) later (when it sees a \n if the output is the terminal, or when the buffer fills up if you redirect to a file). Our write(1, …) goes straight to the kernel. If you mix both without emptying the buffer, the printf text could come out after the dump. fflush empties it at that moment.
**Why do I compare written with SLOT_SIZE and not with −1?** Because write can write fewer bytes than you ask for (for instance, if the disk fills up) without returning −1. Comparing with the expected value catches both things: the error and the half-done write.
**Why O_TRUNC?** Because of the story in the theory: if the file already existed and was longer (say, with 5 saved games), without O_TRUNC we’d overwrite the first 4 and the fifth would still be there. Saving must leave exactly what you save.
Why does the dump read 64 at a time and not 20? Because here we don’t care about slots, only about copying bytes to the screen. The buffer size is up to you: with 64 there are 2 reads with data; with 20 there would be 4. The result is the same because we write nread.
What if a name had more than 6 letters? %-6s sets a minimum, not a maximum: “Teodoro” would take 7 and the slot would be 21 bytes. That’s why the program checks that write returned 20. If you wanted to cut it, the format would be %-6.6s.
Writing with write(fd, slot, sizeof slot). It would write all 64 bytes of the array (the 20 good ones, the '\0' and 43 bytes of garbage) and the file would be 256 bytes instead of 80.
Try it yourself
- Add a fifth player,
{"Sam", 1, 50}, and check that the final size is 100 and that her line isSam level 01 0050. - Use a 7-letter name and see what the
writtencheck does. Then fix it with%-6.6s.
Program 2: backup
Before touching a saves file, the sensible thing is to copy it. The program creates saves.sav (the same four games, 80 bytes) and copies it to saves.bak with a copy function that reads and writes in 32-byte chunks. It must count the bytes copied, say how big the copy is and behave well if the source file doesn’t exist. What makes it tricky: 80 is not a multiple of 32.
The idea
The copy pattern is always the same and it’s worth learning by heart:
- Open the source for reading and the destination for writing (with
O_CREAT | O_TRUNC). - While
readreturns more than 0: write those bytes to the destination. - Close both.
With a 32-byte buffer and an 80-byte file, the loop does 3 rounds with data (32, 32 and 16) and a fourth in which read returns 0. The diagram shows the danger: in round 3, half the buffer is leftovers from round 2.
Before coding, think about what must happen if the second open (the destination’s) fails. The first one is already open: it has to be closed before leaving.
The code
#include <stdio.h>
#include <fcntl.h> /* open and the O_... constants */
#include <unistd.h> /* read, write, lseek, close */
#define SLOT_SIZE 20
#define SAVEDATA "Lia level 03 0450\n" "Max level 12 2750\n" \
"Ane level 05 0980\n" "Teo level 07 1200\n"
#define BUF_SIZE 32
/* Creates the saves file with the four sample slots */
static int prepare(const char *name) {
int fd = open(name, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd == -1) {
perror(name);
return -1;
}
write(fd, SAVEDATA, 4 * SLOT_SIZE);
close(fd);
return 0;
}
/* Copies src into dest in chunks; returns the bytes copied or -1 */
static long copy(const char *src, const char *dest) {
int in = open(src, O_RDONLY);
if (in == -1) {
perror(src);
return -1;
}
int out = open(dest, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (out == -1) {
perror(dest);
close(in); /* don't leave the other one open */
return -1;
}
char buf[BUF_SIZE];
ssize_t nread;
long total = 0;
int round = 1;
while ((nread = read(in, buf, BUF_SIZE)) > 0) {
ssize_t written = write(out, buf, nread); /* only what was read */
if (written != nread) {
perror("write");
break;
}
total += written;
printf(" round %d: read %zd, written %zd, total %ld\n",
round, nread, written, total);
round++;
}
printf(" round %d: read returns %zd -> end\n", round, nread);
close(in);
close(out);
return total;
}
int main(void) {
if (prepare("saves.sav") == -1)
return 1;
printf("Copying saves.sav to saves.bak:\n");
long n = copy("saves.sav", "saves.bak");
printf("Copied: %ld bytes\n", n);
int fd = open("saves.bak", O_RDONLY);
printf("saves.bak is %ld bytes\n", (long) lseek(fd, 0, SEEK_END));
close(fd);
printf("Copying a file that does not exist:\n");
n = copy("saves_2019.sav", "old.bak");
printf("copy returns %ld\n", n);
return 0;
}
A closer look at the copy function, which is the one that matters:
static long copy(const char *src, const char *dest) {
int in = open(src, O_RDONLY);
if (in == -1) {
perror(src);
return -1;
}
int out = open(dest, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (out == -1) {
perror(dest);
close(in); /* don't leave the other one open */
return -1;
}
char buf[BUF_SIZE];
ssize_t nread;
long total = 0;
int round = 1;
while ((nread = read(in, buf, BUF_SIZE)) > 0) {
ssize_t written = write(out, buf, nread); /* only what was read */
if (written != nread) {
perror("write");
break;
}
total += written;
printf(" round %d: read %zd, written %zd, total %ld\n",
round, nread, written, total);
round++;
}
printf(" round %d: read returns %zd -> end\n", round, nread);
close(in);
close(out);
return total;
}
- **The two
opens.** The source withO_RDONLY: we’re only going to read it, and that way we can’t damage it by accident. The destination withO_WRONLY | O_CREAT | O_TRUNC, 0644: if it doesn’t exist it’s created, and if it does (an old backup) it’s emptied. - **The
close(in)in the secondif.** If the destination fails, the source was already open. Without thatclose, every failed call would leave a descriptor taken. - **The
while.** The assignment goes in brackets,(nread = read(...)) > 0: first whatreadreturns is stored, and then it’s compared. Without the brackets,nreadwould get the result of the comparison (1 or 0). - **
write(out, buf, nread).** The heart of the practice: the bytes read are written, notBUF_SIZE. - **
written != nread.** If the disk fills up,writewrites fewer bytes; the copy would be incomplete and we report it withperror. - **The function returns
long** with the total, or −1 if it couldn’t open something. That waymaincan tell “I copied 0 bytes” (an empty file) from “I couldn’t copy”.
| Round | read returns | write writes | total | Source pointer |
|---|---|---|---|---|
| 1 | 32 | 32 | 32 | 32 |
| 2 | 32 | 32 | 64 | 64 |
| 3 | 16 | 16 | 80 | 80 |
| 4 | 0 | — | 80 | 80 |
Output
Copying saves.sav to saves.bak: round 1: read 32, written 32, total 32 round 2: read 32, written 32, total 64 round 3: read 16, written 16, total 80 round 4: read returns 0 -> end Copied: 80 bytes saves.bak is 80 bytes Copying a file that does not exist: saves_2019.sav: No such file or directory copy returns -1
Check: 32 + 32 + 16 = 80, and lseek(fd, 0, SEEK_END) on the copy confirms it’s 80 bytes, the same as the original. In the second call, saves_2019.sav doesn’t exist: perror(src) prints the file name and the reason, and copy returns −1 without creating old.bak (the destination is never even opened).
Questions you might ask
Why a 32-byte buffer and not the size of the file? Because you don’t know how big the file you’ll be given is: it could be 80 bytes or 2 GB. The loop works with any size. Real programs use buffers of 4096 or 8192 bytes (fewer system calls), but with 32 you can see the rounds clearly.
**Why don’t I add the '\0' to the buffer?** Because here we never treat it as a string: we pass it as it is to write. The '\0' is only needed if you’re going to use %s, strlen and company. In fact, if the file were an image, it could have zeros in the middle, and a “string” copy would stop there.
**What happens if read returns −1 halfway?** The while ends (−1 isn’t greater than 0) and the line after it prints read returns -1. A more complete version would check it and return −1 too.
**Why printf with two spaces in front?** Only so the rounds look “inside” the call to copy in the output. It has nothing to do with files.
write(out, buf, BUF_SIZE). That’s my 96-byte backup: the last round writes 16 good bytes and 16 of leftovers, and the copy ends up bigger than the original. If you compare the sizes with ls -l, you spot it immediately.
Try it yourself
- Change
BUF_SIZEto 7 and predict how many rounds there will be and how much the last one will read (80 = 7 · 11 + 3). Check it. - Make
copyrefuse to overwrite an existing backup usingO_EXCL, and try running it twice.
Program 3: editing a saved game
Ane has got 3100 points and her slot, number 2, must be updated without rewriting the whole file. The program reads slot 2, changes its points, reads it again to check, reads the last slot without knowing how many there are and tries to read a slot that doesn’t exist (7). What makes it tricky: you have to read and write the same file, and land exactly on the 4 bytes of the points.
The idea
Thanks to the fixed format, it’s all arithmetic:
- Slot n starts at
n * SLOT_SIZE. - Its points start 15 bytes further on:
n * SLOT_SIZE + POINTS_POS. For slot 2, 2 · 20 + 15 = 55. - The last slot starts 20 bytes before the end:
lseek(fd, -SLOT_SIZE, SEEK_END).
The file is opened with O_RDWR (read and write with the same descriptor) and without O_TRUNC, which would delete the saved games.
Before coding, think about how many bytes you have to write. If you write 5 (the 4 digits and the '\0'), you eat the slot’s \n and the next game ends up glued to this one.
The code
#include <stdio.h>
#include <fcntl.h> /* open and the O_... constants */
#include <unistd.h> /* read, write, lseek, close */
#include <string.h> /* strlen */
#define SLOT_SIZE 20
#define SAVEDATA "Lia level 03 0450\n" "Max level 12 2750\n" \
"Ane level 05 0980\n" "Teo level 07 1200\n"
#define POINTS_POS 15 /* the points start at byte 15 of the slot */
/* Creates the saves file with the four sample slots */
static int prepare(const char *name) {
int fd = open(name, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd == -1) {
perror(name);
return -1;
}
write(fd, SAVEDATA, 4 * SLOT_SIZE);
close(fd);
return 0;
}
/* Reads slot n into buf (21 bytes); returns what read returns */
static ssize_t read_slot(int fd, int n, char *buf) {
lseek(fd, (off_t) n * SLOT_SIZE, SEEK_SET); /* direct jump, without reading the earlier ones */
ssize_t nread = read(fd, buf, SLOT_SIZE);
buf[nread > 0 ? nread : 0] = '\0';
return nread;
}
/* Overwrites the 4 points digits of slot n */
static void change_points(int fd, int n, int new_points) {
char digits[8];
snprintf(digits, sizeof digits, "%04d", new_points); /* always 4 digits */
off_t pos = lseek(fd, (off_t) n * SLOT_SIZE + POINTS_POS, SEEK_SET);
write(fd, digits, strlen(digits)); /* 4 bytes, not one more */
printf("Writing \"%s\" at byte %ld\n", digits, (long) pos);
}
int main(void) {
if (prepare("saves.sav") == -1)
return 1;
int fd = open("saves.sav", O_RDWR); /* read AND write */
if (fd == -1) {
perror("saves.sav");
return 1;
}
char slot[SLOT_SIZE + 1];
read_slot(fd, 2, slot);
printf("Before: %s", slot);
change_points(fd, 2, 3100);
read_slot(fd, 2, slot);
printf("After: %s", slot);
/* The last slot, without knowing how many there are: 20 bytes before the end */
off_t pos = lseek(fd, -SLOT_SIZE, SEEK_END);
read(fd, slot, SLOT_SIZE);
slot[SLOT_SIZE] = '\0';
printf("Last (byte %ld): %s", (long) pos, slot);
/* A slot that does not exist: read returns 0 */
ssize_t n = read_slot(fd, 7, slot);
printf("Slot 7: read returns %zd (empty)\n", n);
printf("Size: %ld bytes (it has not grown)\n", (long) lseek(fd, 0, SEEK_END));
close(fd);
return 0;
}
The two functions, up close:
/* Reads slot n into buf (21 bytes); returns what read returns */
static ssize_t read_slot(int fd, int n, char *buf) {
lseek(fd, (off_t) n * SLOT_SIZE, SEEK_SET); /* direct jump, without reading the earlier ones */
ssize_t nread = read(fd, buf, SLOT_SIZE);
buf[nread > 0 ? nread : 0] = '\0';
return nread;
}
/* Overwrites the 4 points digits of slot n */
static void change_points(int fd, int n, int new_points) {
char digits[8];
snprintf(digits, sizeof digits, "%04d", new_points); /* always 4 digits */
off_t pos = lseek(fd, (off_t) n * SLOT_SIZE + POINTS_POS, SEEK_SET);
write(fd, digits, strlen(digits)); /* 4 bytes, not one more */
printf("Writing \"%s\" at byte %ld\n", digits, (long) pos);
}
- **
read_slot** jumps withSEEK_SETto the start of the slot (there’s no need to read the earlier ones) and reads 20 bytes. The(off_t)converts before multiplying, so that with huge files the calculation doesn’t overflow anint. The'\0'goes atnreadif something was read and at 0 if not: ifreadreturns 0 (a slot that doesn’t exist) or −1,bufbecomes an empty string instead of holding garbage. That’s whybufhas to be 21 bytes. - **
change_points** formats the points with%04d(always 4 digits: 3100 →3100, 50 →0050), jumps ton · 20 + 15and writesstrlen(digits)bytes: 4, without the'\0'. It prints the positionlseekreturns so you can see where it wrote.
The positions the pointer goes through:
| Step | Call | Pointer |
|---|---|---|
| Read slot 2 | lseek(fd, 40, SEEK_SET) + read of 20 | 40 → 60 |
| Change points | lseek(fd, 55, SEEK_SET) + write of 4 | 55 → 59 |
| Re-read slot 2 | lseek(fd, 40, SEEK_SET) + read of 20 | 40 → 60 |
| Last slot | lseek(fd, -20, SEEK_END) + read of 20 | 60 → 80 |
| Slot 7 | lseek(fd, 140, SEEK_SET) + read | 140 (returns 0) |
Output
Before: Ane level 05 0980 Writing "3100" at byte 55 After: Ane level 05 3100 Last (byte 60): Teo level 07 1200 Slot 7: read returns 0 (empty) Size: 80 bytes (it has not grown)
Check: 0980 becomes 3100 at byte 55 (2 · 20 + 15 = 55) and the rest of Ane’s line doesn’t change. The last slot starts at byte 60 (80 − 20 = 60) and it’s Teo’s. Slot 7 would start at 7 · 20 = 140, beyond the 80 bytes: lseek doesn’t complain (moving outside is legal) and read returns 0. And the size is still 80: we’ve overwritten, not added.
Questions you might ask
**Why O_RDWR and not open twice, once for reading and once for writing?** You could, but you’d have two open-file entries with two pointers (we saw it in the theory) and you’d have to lseek each one. With O_RDWR it’s a single descriptor and a single pointer.
Why don’t we have to read slots 0 and 1 to get to slot 2? Because the format is fixed: we know slot 2 starts at 40. With variable-length lines (a normal CSV) you couldn’t: you’d have to read from the start counting \ns. That’s the great advantage of fixed-size records.
Why doesn’t reading slot 7 give an error? Because an lseek past the end is valid, and a read from there simply finds no bytes: it returns 0, just like at the end of a file. If you wrote there, the file would grow and there’d be a gap of zeros between 80 and 140.
**Why do I check nread > 0 before adding the '\0'?** Because if read returned −1, buf[-1] = '\0' would write outside the array. With the ternary operator, in that case the '\0' goes in buf[0].
Opening with O_RDWR | O_TRUNC “out of habit”. The file is emptied on opening, slot 2 doesn’t exist and the write at 55 creates a 59-byte file with 55 zeros in front.
To know how many slots the file has without reading it, lseek(fd, 0, SEEK_END) / SLOT_SIZE. With 80 bytes, 80 / 20 = 4.
Try it yourself
- Write
change_level(fd, n, new_level)that changes the 2 digits of the level (they’re at byte 12 of the slot) and moves Teo up to level 8. - Print the number of slots with the
SEEK_ENDtrick and go through all of them from the last to the first usinglseekbackwards.
Common mistakes in this practice
- **Writing the buffer size instead of
nread:** the copy grows with garbage (my 96-byte one). - **Using
sizeofwith arrays bigger than what you write:** 64 bytes per slot instead of 20. - **Writing the
'\0'to the file:** withstrlen(digits) + 1orsizeof digitsyou eat the next byte (the\n). - **Forgetting
closeon the error paths:** if the secondopenfails, the first one is still open. - **Opening with
O_TRUNCwhen you want to edit:** it deletes what you were going to modify. - **Leaving out the brackets in
(nread = read(...)) > 0:**nreadends up being 1 or 0.
Watch it step by step in Python Tutor
Python Tutor draws the variables and arrays at every step; choose C. Since it doesn’t work with real files on disk, here the “file” is a 14-byte array and the copy is done with a 4-byte buffer, with the same steps as copy:
#include <stdio.h>
int main(void) {
char src[] = "Lia 03\nMax 12\n"; /* 14 bytes, like a file */
char dest[16] = {0};
char buf[4]; /* the copy buffer */
int size = 14, pos = 0, total = 0, nread;
do {
nread = 0; /* "read" of up to 4 bytes */
while (nread < 4 && pos < size)
buf[nread++] = src[pos++];
for (int i = 0; i < nread; i++) /* "write" of nread bytes */
dest[total + i] = buf[i];
total += nread;
} while (nread > 0); /* 4, 4, 4, 2 and 0 */
printf("Copied %d bytes\n%s", total, dest);
return 0;
}
Its output:
Copied 14 bytes Lia 03 Max 12
What to watch:
- In the first round of the
do, see howbuffills up withL,i,aand the space, andposgoes from 0 to 4. - In the fourth round only 2 bytes are left:
nreadis 2, andbuf[2]andbuf[3]still hold the leftovers from the previous round. Notice that theforonly copies 2. - Watch
destgrow 4 at a time (4, 8, 12) and then only 2 (14). - In the fifth round
nreadis 0, thefordoes nothing and thedo-whileends: the same ending as thereadthat returns 0.
Summary
- With a fixed format (
%-6s,%02d,%04d) every slot has the same size and the position of any field can be computed. writereturns how many bytes it wrote: compare it with the number you expected, not only with −1.- The copy loop is
while ((nread = read(...)) > 0) write(out, buf, nread);, and the third argument ofwriteis alwaysnread. - To edit a file in place:
O_RDWR, noO_TRUNC,lseekto the exact position and awriteof just the right bytes. lseekpast the end isn’t an error:readreturns 0 andwritecreates a gap of zeros.- Close everything you open, also on the error paths.
Checklist: have you practised it?
- Have you run the three programs and checked the outputs?
- Could you work out which byte the points of slot 3 are at?
- Can you explain why the copy with
write(out, buf, 32)would be 96 bytes long? - Do you know what
readreturns when you read a slot that doesn’t exist? - Have you tried the “Try it yourself” tasks without looking at the theory?
If you answered yes to almost everything, you’re ready for the system calls exercises, where I no longer give you the code. See you there!
Next step: exercisesSystem calls in C — exercises with byte counters, O_APPEND, a ranking of saved games and three lseek and read bugs (and the saved game that refused to go first)Did this article help you? Here is how you can support SergioLearns →
