• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright © 2025 Igalia S.L.
3  * SPDX-License-Identifier: MIT
4  */
5 
6 #include "os_file_notify.h"
7 #include "detect_os.h"
8 #include "log.h"
9 #include "u_thread.h"
10 
11 #if DETECT_OS_LINUX
12 #include <sys/eventfd.h>
13 #include <sys/inotify.h>
14 #include <sys/poll.h>
15 
16 #include <errno.h>
17 #include <stdatomic.h>
18 #include <stdlib.h>
19 
20 struct os_file_notifier {
21    int ifd;
22    int file_wd, dir_wd;
23    int efd;
24    os_file_notify_cb cb;
25    void *data;
26    thrd_t thread;
27    atomic_bool quit;
28    const char *filename;
29    char file_path[PATH_MAX], dir_path[PATH_MAX];
30 };
31 
32 #define INOTIFY_BUF_LEN ((10 * (sizeof(struct inotify_event) + NAME_MAX + 1)))
33 
34 static int
os_file_notifier_thread(void * data)35 os_file_notifier_thread(void *data)
36 {
37    os_file_notifier_t notifier = data;
38    char buf[INOTIFY_BUF_LEN]
39       __attribute__((aligned(__alignof__(struct inotify_event))));
40    ssize_t len;
41 
42    u_thread_setname("File Notifier");
43 
44    /* To ensure the callback sets the file up to the initial state. */
45    if (access(notifier->file_path, F_OK) != -1) {
46       notifier->cb(notifier->data, notifier->file_path, false, false, false);
47    } else {
48       /* The file doesn't exist, we cannot watch it. */
49       notifier->cb(notifier->data, notifier->file_path, false, true, false);
50    }
51 
52    while (!notifier->quit) {
53       /* Poll on the inotify file descriptor and the eventfd file descriptor. */
54       struct pollfd fds[] = {
55          {notifier->ifd, POLLIN, 0},
56          {notifier->efd, POLLIN, 0},
57       };
58       if (poll(fds, ARRAY_SIZE(fds), -1) == -1) {
59          if (errno == EINTR || errno == EAGAIN)
60             continue;
61 
62          mesa_logw("Failed to poll on file notifier FDs: %s",
63                    strerror(errno));
64          return -1;
65       }
66 
67       if (fds[1].revents & POLLIN) {
68          /* The eventfd is used to wake up the thread when the notifier is
69           * destroyed. */
70          eventfd_t val;
71          eventfd_read(notifier->efd, &val);
72          if (val == 1)
73             return 0; /* Quit the thread. */
74       }
75 
76       len = read(notifier->ifd, buf, sizeof(buf));
77       if (len == -1) {
78          if (errno == EINTR || errno == EAGAIN)
79             continue;
80 
81          mesa_logw("Failed to read inotify events: %s", strerror(errno));
82          return -1;
83       } else {
84          for (char *ptr = buf; ptr < buf + len;
85               ptr += ((struct inotify_event *)ptr)->len +
86                      sizeof(struct inotify_event)) {
87             struct inotify_event *event = (struct inotify_event *)ptr;
88 
89             bool created = false, deleted = false, dir_deleted = false;
90             if (event->wd == notifier->dir_wd) {
91                /* Check if the event is about the directory itself or the
92                 * file. */
93                if (event->mask & (IN_DELETE_SELF | IN_MOVE_SELF)) {
94                   /* The directory was deleted/moved, we cannot watch it or
95                    * the file anymore. */
96                   dir_deleted = true;
97                } else if (strcmp(event->name, notifier->filename) == 0) {
98                   /* If it's a file event, ensure that the event is about the
99                    * file we are watching. */
100                   if (event->mask & IN_CREATE) {
101                      created = true;
102 
103                      /* The file was just created, add a watch to it. */
104                      notifier->file_wd = inotify_add_watch(
105                         notifier->ifd, notifier->file_path, IN_CLOSE_WRITE);
106                      if (notifier->file_wd == -1) {
107                         mesa_logw("Failed to add inotify watch for file");
108                         return -1;
109                      }
110                   } else if (event->mask & IN_DELETE) {
111                      deleted = true;
112 
113                      /* The file was deleted, we cannot watch it anymore. */
114                      inotify_rm_watch(notifier->ifd, notifier->file_wd);
115                      notifier->file_wd = -1;
116                   }
117                }
118             }
119 
120             notifier->cb(notifier->data, notifier->file_path, created,
121                          deleted, dir_deleted);
122             if (dir_deleted)
123                return 0;
124          }
125       }
126    }
127 
128    return 0;
129 }
130 
131 os_file_notifier_t
os_file_notifier_create(const char * file_path,os_file_notify_cb cb,void * data,const char ** error_str)132 os_file_notifier_create(const char *file_path, os_file_notify_cb cb,
133                         void *data, const char **error_str)
134 {
135 #define RET_ERR(s)                                                           \
136    do {                                                                      \
137       if (error_str) {                                                       \
138          *error_str = s;                                                     \
139       }                                                                      \
140       goto cleanup;                                                          \
141    } while (0)
142 
143    os_file_notifier_t notifier = calloc(1, sizeof(struct os_file_notifier));
144    if (!notifier)
145       RET_ERR("Failed to allocate memory for file notifier");
146    notifier->ifd = -1;
147    notifier->efd = -1;
148 
149    size_t path_len = strlen(file_path);
150    if (path_len == 0)
151       RET_ERR("File path is empty");
152    else if (path_len >= PATH_MAX)
153       RET_ERR("File path is longer than PATH_MAX");
154 
155    memcpy(notifier->file_path, file_path, path_len + 1);
156 
157    notifier->ifd = inotify_init1(IN_NONBLOCK);
158    if (notifier->ifd == -1)
159       RET_ERR("Failed to initialize inotify");
160 
161    notifier->file_wd =
162       inotify_add_watch(notifier->ifd, notifier->file_path, IN_CLOSE_WRITE);
163    if (notifier->file_wd == -1 && errno != ENOENT)
164       RET_ERR("Failed to add inotify watch for file");
165 
166    /* Determine the parent directory path of the file. */
167    char *last_slash = strrchr(notifier->file_path, '/');
168    if (last_slash) {
169       size_t len = last_slash - notifier->file_path;
170       memcpy(notifier->dir_path, notifier->file_path, len);
171       notifier->dir_path[len] = 0;
172       notifier->filename = last_slash + 1;
173    } else {
174       notifier->dir_path[0] = '.';
175       notifier->dir_path[1] = 0;
176       notifier->filename = notifier->file_path;
177    }
178 
179    notifier->dir_wd =
180       inotify_add_watch(notifier->ifd, notifier->dir_path,
181                         IN_CREATE | IN_MOVE | IN_DELETE | IN_DELETE_SELF |
182                            IN_MOVE_SELF | IN_ONLYDIR);
183    if (notifier->dir_wd == -1) {
184       if (errno == ENOENT)
185          RET_ERR("The folder containing the watched file doesn't exist");
186       RET_ERR("Failed to add inotify watch for directory");
187    }
188 
189    notifier->efd = eventfd(0, EFD_NONBLOCK);
190    if (notifier->efd == -1)
191       RET_ERR("Failed to create eventfd");
192 
193    notifier->cb = cb;
194    notifier->data = data;
195 
196    if (u_thread_create(&notifier->thread, os_file_notifier_thread,
197                        notifier) != 0)
198       RET_ERR("Failed to create file notifier thread");
199 
200    return notifier;
201 
202 cleanup:
203    if (notifier) {
204       if (notifier->ifd != -1)
205          close(notifier->ifd);
206       if (notifier->efd != -1)
207          close(notifier->efd);
208       free(notifier);
209    }
210    return NULL;
211 
212 #undef RET_ERR
213 }
214 
215 void
os_file_notifier_destroy(os_file_notifier_t notifier)216 os_file_notifier_destroy(os_file_notifier_t notifier)
217 {
218    if (!notifier)
219       return;
220 
221    notifier->quit = true;
222    eventfd_write(notifier->efd, 1);
223    thrd_join(notifier->thread, NULL);
224 
225    close(notifier->ifd);
226    close(notifier->efd);
227    free(notifier);
228 }
229 
230 #else /* !DETECT_OS_LINUX */
231 
232 os_file_notifier_t
os_file_notifier_create(const char * file_path,os_file_notify_cb cb,void * data,const char ** error_str)233 os_file_notifier_create(const char *file_path, os_file_notify_cb cb,
234                         void *data, const char **error_str)
235 {
236    (void)file_path;
237    (void)cb;
238    (void)data;
239 
240    return NULL;
241 }
242 
243 void
os_file_notifier_destroy(os_file_notifier_t notifier)244 os_file_notifier_destroy(os_file_notifier_t notifier)
245 {
246    (void)notifier;
247 }
248 
249 #endif