Aldan Creo
Aldan Creo

Reputation: 854

Is ftruncate() always precise?

I am trying to ftruncate a shared memory object to a specific length. For example, I want to set its length to 1 byte using the following snippet:

#include <stdlib.h>
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>

int main() {
    struct stat fd_stat;
    int fd;

    fd = shm_open("NAME", O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
    fstat(fd, &fd_stat);

    printf("Size before: %lld\n", fd_stat.st_size);

    ftruncate(fd, 1);
    fstat(fd, &fd_stat);

    printf("Size after: %lld\n", fd_stat.st_size);
}

Which in Ubuntu 20.04 prints:

Size before: 0
Size after: 1

That's the output I'd expect.

However, in macOS X Big Sur I get:

Size before: 0
Size after: 4096

As you see, it seems to be expanding the size to the size of a page.

The ftruncate Linux man page reads:

The truncate() and ftruncate() functions cause the regular file named by path or referenced by fd to be truncated to a size of precisely length bytes.

Nonetheless, the POSIX specification is not as specific (pun intended):

If fildes refers to a regular file, the ftruncate() function shall cause the size of the file to be truncated to length. [...] If the file previously was smaller than this size, ftruncate() shall increase the size of the file.

Does that mean that ftruncate always sets the length to exactly the specified number of bytes? If indeed it does, it would entail that macOS X Big Sur is not fully POSIX-compliant (even though it is certified to be so). If not, how can I guarantee that it truncates fd to the size I want?

Upvotes: 2

Views: 656

Answers (1)

Aldan Creo
Aldan Creo

Reputation: 854

In short, you cannot get a guarantee that your shared memory object will be of precisely the size you ask ftruncate to be. That's because, as @user3386109 said, "The portion of the POSIX spec that you quoted starts with "If fildes refers to a regular file"".

If you want to constrain yourself to an arbitrary length, you can always use an auxiliary variable to keep track of the size you assume it to be (even if the actual size might actually differ, which may not be that important after all). Your code would look like:

#include <stdlib.h>
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>

int main() {
    struct stat fd_stat;
    int fd;
    off_t fd_size;

    fd = shm_open("NAME", O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
    fstat(fd, &fd_stat);

    printf("Size before: %lld\n", fd_stat.st_size);

    fd_size = 1;

    ftruncate(fd, fd_size);
    fstat(fd, &fd_stat);

    printf("Actual size: %lld\n", fd_stat.st_size);
    printf("Perceived size: %lld\n", fd_size);
}

On top of that, if you want to share the size among different processes, you can turn fd_size into a mmaped shared memory object to keep track of the size you assume it to be across them all.

Upvotes: 1

Related Questions