Introduction

Reading and writing files in C isn’t as difficult as it sounds. A few simple loops are all you really need. That said, it’s nice to have a few helper functions ready to drop into a project.

Before we write anything we need to think about the choice between fopen and open. The major differences are fopen is portable, part of the C standard, and unbuffered. While open is technically not portable it’s ubiquitous across *nix systems. Even Windows as _open. That said, on Windows you really should use the win32 API functions and not the _ prefixed POSIX compatibility ones.

I’m going to use fopen because it is 100% portable (assuming the compiler supports C89 or higher).

For reading a file I’m going to read into a string builder because it’s makes the process very easy. This is going to read the entire file into memory so care should be taken before using this read function.

Includes

#include <stdlib.h>
#include <stdio.h>

#include <str_builder.h>

Read

The heart of this function is a do...while loop which keeps reading in chunks of the file and putting them into the string builder. The chunk size could be changed and optimized based on the expected data and system the application will be running on. However, 8192 is a typical multiple of a disk block size so it should be fairly efficient.

unsigned char *read_file(const char *filename, size_t *len)
{
    FILE          *f;
    str_builder_t *sb;
    char          *out;
    char           temp[8192];
    size_t         mylen;
    size_t         r;

    if (len == NULL)
        len = &mylen;
    *len = 0;

    if (filename == NULL || *filename == '\0' || len == NULL)
        return NULL;

    f = fopen(filename, "r");
    if (f == NULL)
        return NULL;
    
    sb = str_builder_create();
    do {
        r = fread(temp, sizeof(*temp), sizeof(temp), f);
        str_builder_add_str(sb, temp, r);
        *len += r;
    } while (r > 0);

    if (str_builder_len(sb) == 0) {
        fclose(f);
        str_builder_destroy(sb);
        return NULL;
    }
    fclose(f);

    out = str_builder_dump(sb, NULL);
    str_builder_destroy(sb);
    return (unsigned char *)out;
}

Write

Writing the file takes the data directly and can either truncate or append to the file. Much like read, the main logic is a do...while loop that moves though the data.

size_t write_file(const char *filename, const unsigned char *data, size_t len, bool append)
{
    FILE          *f;
    const char    *mode  = "w";
    size_t         wrote = 0;
    size_t         r;

    if (filename == NULL || *filename == '\0' || data == NULL)
        return 0;

    if (append)
        mode = "a";

    f = fopen(filename, mode);
    if (f == NULL)
        return 0;
    
    do {
        r = fwrite(data, sizeof(*data), len, f);
        wrote += r;
        len   -= r;
        data  += r;
    } while (r > 0 && len != 0);

    fclose(f);
    return wrote;
}