This post is originally published on yoursunny.com blog https://yoursunny.com/t/2021/openat/
fopen and open
In C programming language, the <stdio.h>
header supplies functions for file input and output.
To open a file, we usually use the fopen function.
It is defined by the C language standard and works in every operating system.
Working at a lower level, there's also the open function.
It is a system call provided by the Linux kernel and exposed through glibc.
Both fopen
and open
have an input parameter: the file pathname, as a NUL-terminated string.
These two functions are declared like this:
FILE* fopen(const char* filename, const char* mode);
int open(const char* pathname, int flags);
If the file we want to access is in the current working directory, or we have the full pathname of the file as a string, this is easy to use.
However, sometimes we want to access a file relative to another directory, and the above API isn't so easy to use.
Directory Path + Filename
One such occasion is in my NDNph library: I wanted to use a directory in the filesystem as a persistent key-value store, where object keys are used as filenames, and object value is written as file content.
The API for this key-value store looks like this:
typedef struct KV KV;
/**
* @brief Open a key-value store at specified path
* @param[out] kv key-value store object
* @param dir directory pathname
* @return whether success
*/
bool KV_Open(KV* kv, const char* dir);
/**
* @brief Write @p value to file @p dir "/" @p key
* @param kv key-value store object
* @param key filename
* @param value file content
* @param size size of file content
* @return whether success
*/
bool KV_Save(KV* kv, const char* key, const uint8_t* value, size_t size);
Since fopen
and open
want the file pathname as a single string, I have to concatenate dir
and key
in KV_Save
.
This in turn requires saving a copy of dir
in KV_Open
function.
typedef struct KV
{
char* dir;
} KV;
bool KV_Open(KV* kv, const char* dir)
{
struct stat st;
if (stat(dir, &st) != 0 || !S_ISDIR(st.st_mode)) {
return false;
}
kv->dir = strdup(dir);
return true;
}
bool KV_Save(KV* kv, const char* key, const uint8_t* value, size_t size)
{
char pathname[PATH_MAX];
int res = snprintf(pathname, sizeof(pathname), "%s/%s", kv->dir, key);
if (res < 0 || res >= sizeof(pathname)) {
return false;
}
int fd = open(pathname, O_WRONLY | O_CREAT, 0644);
if (fd < 0) {
return false;
}
// TODO write and close file
}
openat
This week, I came across a new function: openat.
It operates in the same way as open
, except that it supports specifying a relative pathname interpreted relative to another directory, which is represented by a file descriptor.
The function signature of openat is:
int openat(int dirfd, const char* pathname, int flags);
This allows me to simplify the key-value store:
typedef struct KV
{
int dirfd;
} KV;
bool KV_Open(KV* kv, const char* dir)
{
kv->dirfd = open(dir, O_RDONLY | O_DIRECTORY);
return kv->dirfd >= 0;
}
bool KV_Save(KV* kv, const char* key, const uint8_t* value, size_t size)
{
int fd = openat(kv->dirfd, key, O_WRONLY | O_CREAT, 0644);
if (fd < 0) {
return false;
}
// TODO write and close file
}
KV_Open
opens the directory as a file descriptor with the open
function.
The O_DIRECTORY
flag ensures we are opening a directory instead of a regular file.
It's no longer necessary to save a copy of the directory path.
KV_Save
calls openat
with the directory file descriptor and the filename key
.
It's no longer necessary to perform string concatenation.
The code is 5 lines shorter than the open
-based solution.
Conclusion and Code Download
This article introduces Linux openat syscall that I recently discovered.
The openat
function enables resolving a filename or relative path, relative to another directory that is not the current working directory.
It can do so without requiring manual string concatenation.
Code samples (whole program including load/save/delete functions):
-
open.c: key-value store implemented with
open
-
openat.c: key-value store implemented with
openat
- view on GitHub Gist
Caution: this proof-of-concept code assumes that key
is a valid filename.
It cannot safely handle untrusted and potentially malicious input.
Top comments (0)