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 (1 << 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 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); 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 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 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 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 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 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