1 /* Copyright 2016 The Chromium OS Authors. All rights reserved.
2 * Use of this source code is governed by a BSD-style license that can be
3 * found in the LICENSE file.
4 */
5
6 #include <errno.h>
7 #include <fcntl.h>
8 #include <stdlib.h>
9 #include <string.h>
10 #include <syslog.h>
11 #include <sys/inotify.h>
12 #include <sys/param.h>
13 #include <sys/stat.h>
14 #include <sys/types.h>
15 #include <unistd.h>
16
17 #include "cras_file_wait.h"
18
19 #define CRAS_FILE_WAIT_EVENT_MIN_SIZE sizeof(struct inotify_event)
20 #define CRAS_FILE_WAIT_EVENT_SIZE (CRAS_FILE_WAIT_EVENT_MIN_SIZE + NAME_MAX + 1)
21 #define CRAS_FILE_WAIT_FLAG_MOCK_RACE (1u << 31)
22
23 struct cras_file_wait {
24 cras_file_wait_callback_t callback;
25 void *callback_context;
26 const char *file_path;
27 size_t file_path_len;
28 char *watch_path;
29 char *watch_dir;
30 char *watch_file_name;
31 size_t watch_file_name_len;
32 int inotify_fd;
33 int watch_id;
34 char event_buf[CRAS_FILE_WAIT_EVENT_SIZE];
35 cras_file_wait_flag_t flags;
36 };
37
cras_file_wait_get_fd(struct cras_file_wait * file_wait)38 int cras_file_wait_get_fd(struct cras_file_wait *file_wait)
39 {
40 if (!file_wait)
41 return -EINVAL;
42 if (file_wait->inotify_fd < 0)
43 return -EINVAL;
44 return file_wait->inotify_fd;
45 }
46
47 /* Defined for the unittest. */
48 void cras_file_wait_mock_race_condition(struct cras_file_wait *file_wait);
cras_file_wait_mock_race_condition(struct cras_file_wait * file_wait)49 void cras_file_wait_mock_race_condition(struct cras_file_wait *file_wait)
50 {
51 if (file_wait)
52 file_wait->flags |= CRAS_FILE_WAIT_FLAG_MOCK_RACE;
53 }
54
cras_file_wait_destroy(struct cras_file_wait * file_wait)55 void cras_file_wait_destroy(struct cras_file_wait *file_wait)
56 {
57 if (!file_wait)
58 return;
59 if (file_wait->inotify_fd >= 0)
60 close(file_wait->inotify_fd);
61 free(file_wait);
62 }
63
cras_file_wait_rm_watch(struct cras_file_wait * file_wait)64 static int cras_file_wait_rm_watch(struct cras_file_wait *file_wait)
65 {
66 int rc;
67
68 file_wait->watch_path[0] = 0;
69 file_wait->watch_dir[0] = 0;
70 file_wait->watch_file_name[0] = 0;
71 file_wait->watch_file_name_len = 0;
72 if (file_wait->inotify_fd >= 0 && file_wait->watch_id >= 0) {
73 rc = inotify_rm_watch(file_wait->inotify_fd,
74 file_wait->watch_id);
75 file_wait->watch_id = -1;
76 if (rc < 0)
77 return -errno;
78 }
79 return 0;
80 }
81
cras_file_wait_process_event(struct cras_file_wait * file_wait,struct inotify_event * event)82 int cras_file_wait_process_event(struct cras_file_wait *file_wait,
83 struct inotify_event *event)
84 {
85 cras_file_wait_event_t file_wait_event;
86
87 syslog(LOG_DEBUG,
88 "file_wait->watch_id: %d, event->wd: %d"
89 ", event->mask: %x, event->name: %s",
90 file_wait->watch_id, event->wd, event->mask,
91 event->len ? event->name : "");
92
93 if (event->wd != file_wait->watch_id)
94 return 0;
95
96 if (event->mask & IN_IGNORED) {
97 /* The watch has been removed. */
98 file_wait->watch_id = -1;
99 return cras_file_wait_rm_watch(file_wait);
100 }
101
102 if (event->len == 0 ||
103 memcmp(event->name, file_wait->watch_file_name,
104 file_wait->watch_file_name_len + 1) != 0) {
105 /* Some file we don't care about. */
106 return 0;
107 }
108
109 if ((event->mask & (IN_CREATE | IN_MOVED_TO)) != 0)
110 file_wait_event = CRAS_FILE_WAIT_EVENT_CREATED;
111 else if ((event->mask & (IN_DELETE | IN_MOVED_FROM)) != 0)
112 file_wait_event = CRAS_FILE_WAIT_EVENT_DELETED;
113 else
114 return 0;
115
116 /* Found the file! */
117 if (strcmp(file_wait->watch_path, file_wait->file_path) == 0) {
118 /* Tell the caller about this creation or deletion. */
119 file_wait->callback(file_wait->callback_context,
120 file_wait_event, event->name);
121 } else {
122 /* Remove the watch for this file, move on. */
123 return cras_file_wait_rm_watch(file_wait);
124 }
125 return 0;
126 }
127
cras_file_wait_dispatch(struct cras_file_wait * file_wait)128 int cras_file_wait_dispatch(struct cras_file_wait *file_wait)
129 {
130 struct inotify_event *event;
131 char *watch_dir_end;
132 size_t watch_dir_len;
133 char *watch_file_start;
134 size_t watch_path_len;
135 int rc = 0;
136 int flags;
137 ssize_t read_rc;
138 ssize_t read_offset;
139
140 if (!file_wait)
141 return -EINVAL;
142
143 /* If we have a file-descriptor, then read it and see what's up. */
144 if (file_wait->inotify_fd >= 0) {
145 read_offset = 0;
146 read_rc = read(file_wait->inotify_fd, file_wait->event_buf,
147 CRAS_FILE_WAIT_EVENT_SIZE);
148 if (read_rc < 0) {
149 rc = -errno;
150 if ((rc == -EAGAIN || rc == -EWOULDBLOCK) &&
151 file_wait->watch_id < 0) {
152 /* Really nothing to read yet: we need to
153 * setup a watch. */
154 rc = 0;
155 }
156 } else if (read_rc < CRAS_FILE_WAIT_EVENT_MIN_SIZE) {
157 rc = -EIO;
158 } else if (file_wait->watch_id < 0) {
159 /* Processing messages related to old watches. */
160 rc = 0;
161 } else
162 while (rc == 0 && read_offset < read_rc) {
163 event = (struct inotify_event
164 *)(file_wait->event_buf +
165 read_offset);
166 read_offset += sizeof(*event) + event->len;
167 rc = cras_file_wait_process_event(file_wait,
168 event);
169 }
170 }
171
172 /* Report errors from above here. */
173 if (rc < 0)
174 return rc;
175
176 if (file_wait->watch_id >= 0) {
177 /* Assume that the watch that we have is the right one. */
178 return 0;
179 }
180
181 /* Initialize inotify if we haven't already. */
182 if (file_wait->inotify_fd < 0) {
183 file_wait->inotify_fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
184 if (file_wait->inotify_fd < 0)
185 return -errno;
186 }
187
188 /* Figure out what we need to watch next. */
189 rc = -ENOENT;
190 strcpy(file_wait->watch_dir, file_wait->file_path);
191 watch_dir_len = file_wait->file_path_len;
192
193 while (rc == -ENOENT || rc == -EACCES) {
194 strcpy(file_wait->watch_path, file_wait->watch_dir);
195 watch_path_len = watch_dir_len;
196
197 /* Find the end of the parent directory. */
198 watch_dir_end = file_wait->watch_dir + watch_dir_len - 1;
199 while (watch_dir_end > file_wait->watch_dir &&
200 *watch_dir_end != '/')
201 watch_dir_end--;
202 watch_file_start = watch_dir_end + 1;
203 /* Treat consecutive '/' characters as one. */
204 while (watch_dir_end > file_wait->watch_dir &&
205 *(watch_dir_end - 1) == '/')
206 watch_dir_end--;
207 watch_dir_len = watch_dir_end - file_wait->watch_dir;
208
209 if (watch_dir_len == 0) {
210 /* We're looking for a file in the current directory. */
211 strcpy(file_wait->watch_file_name,
212 file_wait->watch_path);
213 file_wait->watch_file_name_len = watch_path_len;
214 strcpy(file_wait->watch_dir, ".");
215 watch_dir_len = 1;
216 } else {
217 /* Copy out the file name that we're looking for, and
218 * mark the end of the directory path. */
219 strcpy(file_wait->watch_file_name, watch_file_start);
220 file_wait->watch_file_name_len =
221 watch_path_len -
222 (watch_file_start - file_wait->watch_dir);
223 *watch_dir_end = 0;
224 }
225
226 if (file_wait->flags & CRAS_FILE_WAIT_FLAG_MOCK_RACE) {
227 /* For testing only. */
228 mknod(file_wait->watch_path, S_IFREG | 0600, 0);
229 file_wait->flags &= ~CRAS_FILE_WAIT_FLAG_MOCK_RACE;
230 }
231
232 flags = IN_CREATE | IN_MOVED_TO | IN_DELETE | IN_MOVED_FROM;
233 file_wait->watch_id = inotify_add_watch(
234 file_wait->inotify_fd, file_wait->watch_dir, flags);
235 if (file_wait->watch_id < 0) {
236 rc = -errno;
237 continue;
238 }
239
240 /* Satisfy the race condition between existence of the
241 * file and creation of the watch. */
242 rc = access(file_wait->watch_path, F_OK);
243 if (rc < 0) {
244 rc = -errno;
245 if (rc == -ENOENT) {
246 /* As expected, the file still doesn't exist. */
247 rc = 0;
248 }
249 continue;
250 }
251
252 /* The file we're looking for exists. */
253 if (strcmp(file_wait->watch_path, file_wait->file_path) == 0) {
254 file_wait->callback(file_wait->callback_context,
255 CRAS_FILE_WAIT_EVENT_CREATED,
256 file_wait->watch_file_name);
257 return 0;
258 }
259
260 /* Start over again. */
261 rc = cras_file_wait_rm_watch(file_wait);
262 if (rc < 0)
263 return rc;
264 rc = -ENOENT;
265 strcpy(file_wait->watch_dir, file_wait->file_path);
266 watch_dir_len = file_wait->file_path_len;
267 }
268
269 /* Get out for permissions problems for example. */
270 return rc;
271 }
272
cras_file_wait_create(const char * file_path,cras_file_wait_flag_t flags,cras_file_wait_callback_t callback,void * callback_context,struct cras_file_wait ** file_wait_out)273 int cras_file_wait_create(const char *file_path, cras_file_wait_flag_t flags,
274 cras_file_wait_callback_t callback,
275 void *callback_context,
276 struct cras_file_wait **file_wait_out)
277 {
278 struct cras_file_wait *file_wait;
279 size_t file_path_len;
280 int rc;
281
282 if (!file_path || !*file_path || !callback || !file_wait_out)
283 return -EINVAL;
284 *file_wait_out = NULL;
285
286 /* Create a struct cras_file_wait to track waiting for this file. */
287 file_path_len = strlen(file_path);
288 file_wait = (struct cras_file_wait *)calloc(
289 1, sizeof(*file_wait) + ((file_path_len + 1) * 5));
290 if (!file_wait)
291 return -ENOMEM;
292 file_wait->callback = callback;
293 file_wait->callback_context = callback_context;
294 file_wait->inotify_fd = -1;
295 file_wait->watch_id = -1;
296 file_wait->file_path_len = file_path_len;
297 file_wait->flags = flags;
298
299 /* We've allocated memory such that the file_path, watch_path,
300 * watch_dir, and watch_file_name data are appended to the end of
301 * our cras_file_wait structure. */
302 file_wait->file_path = (const char *)file_wait + sizeof(*file_wait);
303 file_wait->watch_path =
304 (char *)file_wait->file_path + file_path_len + 1;
305 file_wait->watch_dir = file_wait->watch_path + file_path_len + 1;
306 file_wait->watch_file_name = file_wait->watch_dir + file_path_len + 1;
307 memcpy((void *)file_wait->file_path, file_path, file_path_len + 1);
308
309 /* Setup the first watch. If that fails unexpectedly, then we destroy
310 * the file wait structure immediately. */
311 rc = cras_file_wait_dispatch(file_wait);
312 if (rc < 0) {
313 cras_file_wait_destroy(file_wait);
314 return rc;
315 }
316
317 *file_wait_out = file_wait;
318 return 0;
319 }
320