mesa/src/util/os_file_notify.c
Gurchetan Singh 4baae4c4d4 meson: check for <poll.h>
Otherwise, the following warning is observed with musl
headers, which translates to an error on Android.

external/musl/include/sys/poll.h:1:2: error:
  redirecting incorrect #include <sys/poll.h> to <poll.h> [-Werror,-W#warnings]
  warning redirecting incorrect #include <sys/poll.h> to <poll.h>

Reviewed-by: Eric Engestrom <eric@igalia.com>
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/39279>
2026-01-20 17:17:57 +00:00

254 lines
7.5 KiB
C

/*
* Copyright © 2025 Igalia S.L.
* SPDX-License-Identifier: MIT
*/
#include "os_file_notify.h"
#include "detect_os.h"
#include "log.h"
#include "u_thread.h"
#if DETECT_OS_LINUX
#include <sys/eventfd.h>
#include <sys/inotify.h>
#ifdef HAVE_POLL_H
#include <poll.h>
#else
#include <sys/poll.h>
#endif
#include <errno.h>
#include <stdatomic.h>
#include <stdlib.h>
struct os_file_notifier {
int ifd;
int file_wd, dir_wd;
int efd;
os_file_notify_cb cb;
void *data;
thrd_t thread;
atomic_bool quit;
const char *filename;
char file_path[PATH_MAX], dir_path[PATH_MAX];
};
#define INOTIFY_BUF_LEN ((10 * (sizeof(struct inotify_event) + NAME_MAX + 1)))
static int
os_file_notifier_thread(void *data)
{
os_file_notifier_t notifier = data;
char buf[INOTIFY_BUF_LEN]
__attribute__((aligned(__alignof__(struct inotify_event))));
ssize_t len;
u_thread_setname("File Notifier");
/* To ensure the callback sets the file up to the initial state. */
if (access(notifier->file_path, F_OK) != -1) {
notifier->cb(notifier->data, notifier->file_path, false, false, false);
} else {
/* The file doesn't exist, we cannot watch it. */
notifier->cb(notifier->data, notifier->file_path, false, true, false);
}
while (!notifier->quit) {
/* Poll on the inotify file descriptor and the eventfd file descriptor. */
struct pollfd fds[] = {
{notifier->ifd, POLLIN, 0},
{notifier->efd, POLLIN, 0},
};
if (poll(fds, ARRAY_SIZE(fds), -1) == -1) {
if (errno == EINTR || errno == EAGAIN)
continue;
mesa_logw("Failed to poll on file notifier FDs: %s",
strerror(errno));
return -1;
}
if (fds[1].revents & POLLIN) {
/* The eventfd is used to wake up the thread when the notifier is
* destroyed. */
eventfd_t val;
eventfd_read(notifier->efd, &val);
if (val == 1)
return 0; /* Quit the thread. */
}
len = read(notifier->ifd, buf, sizeof(buf));
if (len == -1) {
if (errno == EINTR || errno == EAGAIN)
continue;
mesa_logw("Failed to read inotify events: %s", strerror(errno));
return -1;
} else {
for (char *ptr = buf; ptr < buf + len;
ptr += ((struct inotify_event *)ptr)->len +
sizeof(struct inotify_event)) {
struct inotify_event *event = (struct inotify_event *)ptr;
bool created = false, deleted = false, dir_deleted = false;
if (event->wd == notifier->dir_wd) {
/* Check if the event is about the directory itself or the
* file. */
if (event->mask & (IN_DELETE_SELF | IN_MOVE_SELF)) {
/* The directory was deleted/moved, we cannot watch it or
* the file anymore. */
dir_deleted = true;
} else if (strcmp(event->name, notifier->filename) == 0) {
/* If it's a file event, ensure that the event is about the
* file we are watching. */
if (event->mask & IN_CREATE) {
created = true;
/* The file was just created, add a watch to it. */
notifier->file_wd = inotify_add_watch(
notifier->ifd, notifier->file_path, IN_CLOSE_WRITE);
if (notifier->file_wd == -1) {
mesa_logw("Failed to add inotify watch for file");
return -1;
}
} else if (event->mask & IN_DELETE) {
deleted = true;
/* The file was deleted, we cannot watch it anymore. */
inotify_rm_watch(notifier->ifd, notifier->file_wd);
notifier->file_wd = -1;
}
}
}
notifier->cb(notifier->data, notifier->file_path, created,
deleted, dir_deleted);
if (dir_deleted)
return 0;
}
}
}
return 0;
}
os_file_notifier_t
os_file_notifier_create(const char *file_path, os_file_notify_cb cb,
void *data, const char **error_str)
{
#define RET_ERR(s) \
do { \
if (error_str) { \
*error_str = s; \
} \
goto cleanup; \
} while (0)
os_file_notifier_t notifier = calloc(1, sizeof(struct os_file_notifier));
if (!notifier)
RET_ERR("Failed to allocate memory for file notifier");
notifier->ifd = -1;
notifier->efd = -1;
size_t path_len = strlen(file_path);
if (path_len == 0)
RET_ERR("File path is empty");
else if (path_len >= PATH_MAX)
RET_ERR("File path is longer than PATH_MAX");
memcpy(notifier->file_path, file_path, path_len + 1);
notifier->ifd = inotify_init1(IN_NONBLOCK);
if (notifier->ifd == -1)
RET_ERR("Failed to initialize inotify");
notifier->file_wd =
inotify_add_watch(notifier->ifd, notifier->file_path, IN_CLOSE_WRITE);
if (notifier->file_wd == -1 && errno != ENOENT)
RET_ERR("Failed to add inotify watch for file");
/* Determine the parent directory path of the file. */
char *last_slash = strrchr(notifier->file_path, '/');
if (last_slash) {
size_t len = last_slash - notifier->file_path;
memcpy(notifier->dir_path, notifier->file_path, len);
notifier->dir_path[len] = 0;
notifier->filename = last_slash + 1;
} else {
notifier->dir_path[0] = '.';
notifier->dir_path[1] = 0;
notifier->filename = notifier->file_path;
}
notifier->dir_wd =
inotify_add_watch(notifier->ifd, notifier->dir_path,
IN_CREATE | IN_MOVE | IN_DELETE | IN_DELETE_SELF |
IN_MOVE_SELF | IN_ONLYDIR);
if (notifier->dir_wd == -1) {
if (errno == ENOENT)
RET_ERR("The folder containing the watched file doesn't exist");
RET_ERR("Failed to add inotify watch for directory");
}
notifier->efd = eventfd(0, EFD_NONBLOCK);
if (notifier->efd == -1)
RET_ERR("Failed to create eventfd");
notifier->cb = cb;
notifier->data = data;
if (u_thread_create(&notifier->thread, os_file_notifier_thread,
notifier) != 0)
RET_ERR("Failed to create file notifier thread");
return notifier;
cleanup:
if (notifier) {
if (notifier->ifd != -1)
close(notifier->ifd);
if (notifier->efd != -1)
close(notifier->efd);
free(notifier);
}
return NULL;
#undef RET_ERR
}
void
os_file_notifier_destroy(os_file_notifier_t notifier)
{
if (!notifier)
return;
notifier->quit = true;
eventfd_write(notifier->efd, 1);
thrd_join(notifier->thread, NULL);
close(notifier->ifd);
close(notifier->efd);
free(notifier);
}
#else /* !DETECT_OS_LINUX */
os_file_notifier_t
os_file_notifier_create(const char *file_path, os_file_notify_cb cb,
void *data, const char **error_str)
{
(void)file_path;
(void)cb;
(void)data;
return NULL;
}
void
os_file_notifier_destroy(os_file_notifier_t notifier)
{
(void)notifier;
}
#endif