1

Today I Learned: openat()

 2 years ago
source link: https://dev.to/yoursunny/today-i-learned-openat-8h1
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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);
Enter fullscreen modeExit fullscreen mode

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);
Enter fullscreen modeExit fullscreen mode

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
}
Enter fullscreen modeExit fullscreen mode

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);
Enter fullscreen modeExit fullscreen mode

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
}
Enter fullscreen modeExit fullscreen mode

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):

Caution: this proof-of-concept code assumes that key is a valid filename.
It cannot safely handle untrusted and potentially malicious input.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK