• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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