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