Home > OS >  type-safe calculation of off_t offset for ftruncate() in C
type-safe calculation of off_t offset for ftruncate() in C

Time:08-01

Having parsed the logs of a job to know from where to restart it, I may find some corruption at the end (eg if the system crashed); in that case I want to truncate the bad data before continuing.

I'm currently opening the file with fopen(path, "a ");, and have read a line with nread = getline(&buf, &len, fp);. If I detect corruption I now want to truncate it with something like ftruncate(fileno(fp), ftell(fp) - nread) - but ftell() returns a long, getline() returns a ssize_t, and ftruncate() wants an off_t. So what is a correct, type-safe way to calculate the offset?

(And yes, I know that I also need to handle the state of the FILE * around the ftruncate() call, that will be my next problem.)

CodePudding user response:

Use ftello instead of ftell. It's identical except for returning an off_t rather than a long. This avoids the problem that the value that ftell should return might overflow long, before any operations even happen. This is a real problem on 32-bit systems that support files larger than 2GB.

ftello is POSIX, so it should be available on any system where ftruncate and other file descriptor functions are available except truly antique ones.

The arithmetic is in fact not an issue. The operands will be promoted to a type that can accommodate both operands' value. That in itself doesn't guarantee that the result will fit, but you know that ftello(np) is non-negative and nread is non-negative and less than ftello(np), so the result is between 0 and ftello(np) and thus can fit in any type where ftello(np) fits.

Note that ftello, like ftell, returns -1 on error. Make sure to handle that case.

while (!end_of_file) {
    ssize_t nread = getline(fp, ...);
    if (nread == -1) goto error;
    off_t offset = ftello(fp);
    if (offset == -1) goto error;
    if (!valid_record(...)) {
        ftruncate(fp, offset - nread);
        ...
    }
}

But actually, I think it would make more sense to remember the last known-good offset, and truncate to that point if needed.

while (!end_of_file) {
    off_t good_offset = ftello(fp);
    if (good_offset == -1) goto error;
    ssize_t nread = getline(fp, ...);
    if (nread == -1) goto error;
    if (!valid_record(...)) {
        ftruncate(fp, good_offset);
        ...
    }
}
  • Related