Today I Learned: openat()

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…


This content originally appeared on DEV Community and was authored by Junxiao Shi

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

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


This content originally appeared on DEV Community and was authored by Junxiao Shi


Print Share Comment Cite Upload Translate Updates
APA

Junxiao Shi | Sciencx (2021-08-28T17:53:27+00:00) Today I Learned: openat(). Retrieved from https://www.scien.cx/2021/08/28/today-i-learned-openat/

MLA
" » Today I Learned: openat()." Junxiao Shi | Sciencx - Saturday August 28, 2021, https://www.scien.cx/2021/08/28/today-i-learned-openat/
HARVARD
Junxiao Shi | Sciencx Saturday August 28, 2021 » Today I Learned: openat()., viewed ,<https://www.scien.cx/2021/08/28/today-i-learned-openat/>
VANCOUVER
Junxiao Shi | Sciencx - » Today I Learned: openat(). [Internet]. [Accessed ]. Available from: https://www.scien.cx/2021/08/28/today-i-learned-openat/
CHICAGO
" » Today I Learned: openat()." Junxiao Shi | Sciencx - Accessed . https://www.scien.cx/2021/08/28/today-i-learned-openat/
IEEE
" » Today I Learned: openat()." Junxiao Shi | Sciencx [Online]. Available: https://www.scien.cx/2021/08/28/today-i-learned-openat/. [Accessed: ]
rf:citation
» Today I Learned: openat() | Junxiao Shi | Sciencx | https://www.scien.cx/2021/08/28/today-i-learned-openat/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.