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, "file_wait->watch_id: %d, event->wd: %d"
88 ", event->mask: %x, event->name: %s",
89 file_wait->watch_id, event->wd, event->mask,
90 event->len ? event->name : "");
91
92 if (event->wd != file_wait->watch_id)
93 return 0;
94
95 if (event->mask & IN_IGNORED) {
96 /* The watch has been removed. */
97 file_wait->watch_id = -1;
98 return cras_file_wait_rm_watch(file_wait);
99 }
100
101 if (event->len == 0 ||
102 memcmp(event->name, file_wait->watch_file_name,
103 file_wait->watch_file_name_len + 1) != 0) {
104 /* Some file we don't care about. */
105 return 0;
106 }
107
108 if ((event->mask & (IN_CREATE|IN_MOVED_TO)) != 0)
109 file_wait_event = CRAS_FILE_WAIT_EVENT_CREATED;
110 else if ((event->mask & (IN_DELETE|IN_MOVED_FROM)) != 0)
111 file_wait_event = CRAS_FILE_WAIT_EVENT_DELETED;
112 else
113 return 0;
114
115 /* Found the file! */
116 if (strcmp(file_wait->watch_path, file_wait->file_path) == 0) {
117 /* Tell the caller about this creation or deletion. */
118 file_wait->callback(file_wait->callback_context,
119 file_wait_event, event->name);
120 } else {
121 /* Remove the watch for this file, move on. */
122 return cras_file_wait_rm_watch(file_wait);
123 }
124 return 0;
125 }
126
cras_file_wait_dispatch(struct cras_file_wait * file_wait)127 int cras_file_wait_dispatch(struct cras_file_wait *file_wait)
128 {
129 struct inotify_event *event;
130 char *watch_dir_end;
131 size_t watch_dir_len;
132 char *watch_file_start;
133 size_t watch_path_len;
134 int rc = 0;
135 int flags;
136 ssize_t read_rc;
137 ssize_t read_offset;
138
139 if (!file_wait)
140 return -EINVAL;
141
142 /* If we have a file-descriptor, then read it and see what's up. */
143 if (file_wait->inotify_fd >= 0) {
144 read_offset = 0;
145 read_rc = read(file_wait->inotify_fd, file_wait->event_buf,
146 CRAS_FILE_WAIT_EVENT_SIZE);
147 if (read_rc < 0) {
148 rc = -errno;
149 if ((rc == -EAGAIN || rc == -EWOULDBLOCK)
150 && file_wait->watch_id < 0) {
151 /* Really nothing to read yet: we need to
152 * setup a watch. */
153 rc = 0;
154 }
155 } else if (read_rc < CRAS_FILE_WAIT_EVENT_MIN_SIZE) {
156 rc = -EIO;
157 } else if (file_wait->watch_id < 0) {
158 /* Processing messages related to old watches. */
159 rc = 0;
160 } else while (rc == 0 && read_offset < read_rc) {
161 event = (struct inotify_event *)
162 (file_wait->event_buf + read_offset);
163 read_offset += sizeof(*event) + event->len;
164 rc = cras_file_wait_process_event(file_wait, event);
165 }
166 }
167
168 /* Report errors from above here. */
169 if (rc != 0)
170 return rc;
171
172 if (file_wait->watch_id >= 0) {
173 /* Assume that the watch that we have is the right one. */
174 return 0;
175 }
176
177 /* Initialize inotify if we haven't already. */
178 if (file_wait->inotify_fd < 0) {
179 file_wait->inotify_fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC);
180 if (file_wait->inotify_fd < 0)
181 return -errno;
182 }
183
184 /* Figure out what we need to watch next. */
185 rc = -ENOENT;
186 strcpy(file_wait->watch_dir, file_wait->file_path);
187 watch_dir_len = file_wait->file_path_len;
188
189 while (rc == -ENOENT) {
190 strcpy(file_wait->watch_path, file_wait->watch_dir);
191 watch_path_len = watch_dir_len;
192
193 /* Find the end of the parent directory. */
194 watch_dir_end = file_wait->watch_dir + watch_dir_len - 1;
195 while (watch_dir_end > file_wait->watch_dir &&
196 *watch_dir_end != '/')
197 watch_dir_end--;
198 watch_file_start = watch_dir_end + 1;
199 /* Treat consecutive '/' characters as one. */
200 while (watch_dir_end > file_wait->watch_dir &&
201 *(watch_dir_end - 1) == '/')
202 watch_dir_end--;
203 watch_dir_len = watch_dir_end - file_wait->watch_dir;
204
205 if (watch_dir_len == 0) {
206 /* We're looking for a file in the current directory. */
207 strcpy(file_wait->watch_file_name,
208 file_wait->watch_path);
209 file_wait->watch_file_name_len = watch_path_len;
210 strcpy(file_wait->watch_dir, ".");
211 watch_dir_len = 1;
212 } else {
213 /* Copy out the file name that we're looking for, and
214 * mark the end of the directory path. */
215 strcpy(file_wait->watch_file_name, watch_file_start);
216 file_wait->watch_file_name_len =
217 watch_path_len -
218 (watch_file_start - file_wait->watch_dir);
219 *watch_dir_end = 0;
220 }
221
222 if (file_wait->flags & CRAS_FILE_WAIT_FLAG_MOCK_RACE) {
223 /* For testing only. */
224 mknod(file_wait->watch_path, S_IFREG | 0600, 0);
225 file_wait->flags &= ~CRAS_FILE_WAIT_FLAG_MOCK_RACE;
226 }
227
228 flags = IN_CREATE|IN_MOVED_TO|IN_DELETE|IN_MOVED_FROM;
229 file_wait->watch_id =
230 inotify_add_watch(file_wait->inotify_fd,
231 file_wait->watch_dir, flags);
232 if (file_wait->watch_id < 0) {
233 rc = -errno;
234 continue;
235 }
236
237 /* Satisfy the race condition between existence of the
238 * file and creation of the watch. */
239 rc = access(file_wait->watch_path, F_OK);
240 if (rc < 0) {
241 rc = -errno;
242 if (rc == -ENOENT) {
243 /* As expected, the file still doesn't exist. */
244 rc = 0;
245 }
246 continue;
247 }
248
249 /* The file we're looking for exists. */
250 if (strcmp(file_wait->watch_path, file_wait->file_path) == 0) {
251 file_wait->callback(file_wait->callback_context,
252 CRAS_FILE_WAIT_EVENT_CREATED,
253 file_wait->watch_file_name);
254 return 0;
255 }
256
257 /* Start over again. */
258 rc = cras_file_wait_rm_watch(file_wait);
259 if (rc != 0)
260 return rc;
261 rc = -ENOENT;
262 strcpy(file_wait->watch_dir, file_wait->file_path);
263 watch_dir_len = file_wait->file_path_len;
264 }
265
266 /* Get out for permissions problems for example. */
267 return rc;
268 }
269
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)270 int cras_file_wait_create(const char *file_path,
271 cras_file_wait_flag_t flags,
272 cras_file_wait_callback_t callback,
273 void *callback_context,
274 struct cras_file_wait **file_wait_out)
275 {
276 struct cras_file_wait *file_wait;
277 size_t file_path_len;
278 int rc;
279
280 if (!file_path || !*file_path || !callback || !file_wait_out)
281 return -EINVAL;
282 *file_wait_out = NULL;
283
284 /* Create a struct cras_file_wait to track waiting for this file. */
285 file_path_len = strlen(file_path);
286 file_wait = (struct cras_file_wait *)
287 calloc(1, sizeof(*file_wait) + ((file_path_len + 1) * 5));
288 if (!file_wait)
289 return -ENOMEM;
290 file_wait->callback = callback;
291 file_wait->callback_context = callback_context;
292 file_wait->inotify_fd = -1;
293 file_wait->watch_id = -1;
294 file_wait->file_path_len = file_path_len;
295 file_wait->flags = flags;
296
297 /* We've allocated memory such that the file_path, watch_path,
298 * watch_dir, and watch_file_name data are appended to the end of
299 * our cras_file_wait structure. */
300 file_wait->file_path = (const char *)file_wait + sizeof(*file_wait);
301 file_wait->watch_path = (char *)file_wait->file_path +
302 file_path_len + 1;
303 file_wait->watch_dir = file_wait->watch_path + file_path_len + 1;
304 file_wait->watch_file_name = file_wait->watch_dir + file_path_len + 1;
305 memcpy((void *)file_wait->file_path, file_path, file_path_len + 1);
306
307 /* Setup the first watch. If that fails unexpectedly, then we destroy
308 * the file wait structure immediately. */
309 rc = cras_file_wait_dispatch(file_wait);
310 if (rc != 0)
311 cras_file_wait_destroy(file_wait);
312 else
313 *file_wait_out = file_wait;
314 return rc;
315 }
316