Tanmay Goyal
Tanmay Goyal

Reputation: 31

Appending to Start of File in C

How can I append to the start of a file (instead of the end) using system calls in C?

I have already read the man page of open(). I did not find any flag codes ( similar to O_APPEND) allowing me to append to the start of the file.

(Clarification - System Calls in Linux)

Upvotes: 2

Views: 338

Answers (2)

mediocrevegetable1
mediocrevegetable1

Reputation: 4207

With truncate and mmap, it seems like this is possible without having to create a buffer file:

#include <stdbool.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <assert.h>

bool file_prepend(int fd, const char *add)
{
    struct stat old;
    if (fstat(fd, &old) != 0)
        return false;
    size_t add_len = strlen(add);

    // Increase the file size to make space for the string
    if (ftruncate(fd, old.st_size + add_len) != 0)
        return false;
    char *mapped = mmap(NULL, old.st_size + add_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (!mapped)
        return false;

    // Now we can essentially treat the file as raw memory
    memmove(mapped + add_len, mapped, old.st_size);
    memcpy(mapped, add, add_len);
    munmap(mapped, old.st_size + add_len);
    return true;
}

int main(void)
{
    int fd = open("foo.txt", O_RDWR);
    assert(fd != -1);
    bool ok = file_prepend(fd, "Hello ");
    assert(ok);
    close(fd);
}

Before I run this program foo.txt looks like:

World

And after running the program it looks like:

Hello World

By the way, I'm not an expert in mmap so my prot and flags params might be incorrect (though it works for me), feel free to correct me if I have done something wrong.

Upvotes: 2

Nate Eldredge
Nate Eldredge

Reputation: 58132

Versions of Linux since 4.1 have fallocate(2) with the FALLOC_FL_INSERT_RANGE flag, which allows you to insert a "hole" into a file which you can then write data into. You can insert a hole at the beginning, fill it with data, then insert a new hole after the first one, and so on. However, there are major caveats:

  • It's only supported by certain filesystems (the man page mentions XFS and ext4)

  • Holes can only be inserted in multiples of the filesystem block size (commonly 4 KB). So this will only work if your data happens to be a multiple of that size, or if its format is such that you can safely pad it up to that size (e.g. if the file will be processed by a tool that ignores whitespace, in which case you could pad up to 4K with spaces or newlines).

Otherwise, there is nothing as general as O_APPEND for inserting at the start of a file or anywhere else. Most filesystems are simply not designed in such a way that would make it possible to implement this efficiently. Files are usually set up as a list of fixed-size blocks with a partial block at the end. The OS can append to a file by updating the last partial block, then adding new blocks to the end of the list. It may even be able to insert new blocks at other points in the list, as with FALLOC_FL_INSERT_RANGE. But there's usually no provision for partial blocks anywhere in the list except at the end, so inserting data that is not a full block would require shifting the data in all other blocks, which requires rewriting all of it.

An extent-based filesystem like btrfs could in principle support inserting chunks of arbitrary lengths at the start of a file, by adding extents. However, at least of as Linux 5.4.0, it's not actually supported, as far as I can tell.

Upvotes: 8

Related Questions