Rubbing a File Raw

About six years ago, I wrote about opening a file in the raw. Specifically, using the open() function to access file data at a low level. This approach uses file descriptors (integers) to track file I/O as opposed to the file handles used by the high-level fopen() family of functions. It turns out that you can do more with raw file access than just read and write data.

The following code provides an example of raw file access. It creates a file test.txt and writes the word “Hello!” to the file. The operations follow the same order as when using formatted file I/O: The file is opened. Data is written to the file. The file is closed. These functions — open(), write(), and close() — use an integer file descriptor as their reference, not a file handle.

2026_08_08-Lesson.c

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

int main()
{
    char filename[] = "test.txt";
    int fd;
    mode_t filemode = O_CREAT | O_WRONLY;

    /* open/create the file */
    fd = open(filename,filemode);
    if( fd==-1 )
    {
        fprintf(stderr,"Unable to create %s\n",filename);
        perror("ERROR: ");
        exit(1);
    }

    /* write data to the file */
    write(fd,"Hello!\n",8);

    /* clean-up */
    close(fd);
    return 0;
}

The open() function returns a file descriptor value, saved in int variable fd. If the value returned is -1, some sorta error happened, in which case appropriate messages are output and the program exits.

The file is opened with two modes applied, saved in mode_t variable filemode:

O_CREAT creates the file if it doesn’t exist.

O_WRONLY opens the file for writing only.

Upon opening/creating the file successfully, the write() function sends the string "Hello!\n" to the file; the function requires the file descriptor number, string, and the number of bytes to write: write(fd,"Hello!\n",8); The value 8 includes all characters in the string, plus the terminating null character.

After the close() function closes the file, the program ends. No output is generated. To confirm that everything worked, use the ls command in a terminal window. Better, cat the file to check its contents:

$ cat test.txt
Hello!

This code successfully wrote the text Hello (newline) to a file, test.txt, which it created. But keep in mind that the open() function is directed to open/create the file. This effect has unusual consequences when you’re not careful.

Change the string in the write() function to read "X\n" and set the number of bytes to write to 3:

write(fd,"X\n",3);

Rebuild and re-run the program, and then examine the contents of the test.txt file to see the following:

$ cat test.txt
X
llo

The O_CREAT flag creates or opens a file, but any existing file isn’t destroyed nor is any data deleted. Further, the file position indicator is set to zero when the file is opened. The effect is that any new information written to the file overwrites what’s already there.

To remedy this situation, and to have the open() function overwrite an existing file, add the O_TRUNC mode at Line 10:

mode_t filemode = O_CREAT | O_WRONLY | O_TRUNC;

Running this update, creates a file test.txt that only contains the new data written to it. The O_TRUNC flag resizes the file to zero as it’s opened, removing any existing contents.

As an alternative, you can use the creat() function instead of open(). This function works identically to the open() function with the O_CREAT, O_WRONLY, and O_TRUNC flags specified. Update the code to replace open() with this statement:

fd = creat(filename,0);

And remove the statement declaring mode_t variable filemode to avoid an unused variable warning.

The updated program creates a new file to replace any existing file.

Yes, you can always surrender and use fopen() instead of open(). Life is easier that way. But then you’ll never discover the wonders of the dup() function, which I cover in next week’s Lesson.

Leave a Reply