/* Copyright 2016 The Chromium OS Authors. All rights reserved. * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include #include #include #include #include #include #include #include #include #include #include "cras_file_wait.h" #define CRAS_FILE_WAIT_EVENT_MIN_SIZE sizeof(struct inotify_event) #define CRAS_FILE_WAIT_EVENT_SIZE (CRAS_FILE_WAIT_EVENT_MIN_SIZE + NAME_MAX + 1) #define CRAS_FILE_WAIT_FLAG_MOCK_RACE (1u << 31) struct cras_file_wait { cras_file_wait_callback_t callback; void *callback_context; const char *file_path; size_t file_path_len; char *watch_path; char *watch_dir; char *watch_file_name; size_t watch_file_name_len; int inotify_fd; int watch_id; char event_buf[CRAS_FILE_WAIT_EVENT_SIZE]; cras_file_wait_flag_t flags; }; int cras_file_wait_get_fd(struct cras_file_wait *file_wait) { if (!file_wait) return -EINVAL; if (file_wait->inotify_fd < 0) return -EINVAL; return file_wait->inotify_fd; } /* Defined for the unittest. */ void cras_file_wait_mock_race_condition(struct cras_file_wait *file_wait); void cras_file_wait_mock_race_condition(struct cras_file_wait *file_wait) { if (file_wait) file_wait->flags |= CRAS_FILE_WAIT_FLAG_MOCK_RACE; } void cras_file_wait_destroy(struct cras_file_wait *file_wait) { if (!file_wait) return; if (file_wait->inotify_fd >= 0) close(file_wait->inotify_fd); free(file_wait); } static int cras_file_wait_rm_watch(struct cras_file_wait *file_wait) { int rc; file_wait->watch_path[0] = 0; file_wait->watch_dir[0] = 0; file_wait->watch_file_name[0] = 0; file_wait->watch_file_name_len = 0; if (file_wait->inotify_fd >= 0 && file_wait->watch_id >= 0) { rc = inotify_rm_watch(file_wait->inotify_fd, file_wait->watch_id); file_wait->watch_id = -1; if (rc < 0) return -errno; } return 0; } int cras_file_wait_process_event(struct cras_file_wait *file_wait, struct inotify_event *event) { cras_file_wait_event_t file_wait_event; syslog(LOG_DEBUG, "file_wait->watch_id: %d, event->wd: %d" ", event->mask: %x, event->name: %s", file_wait->watch_id, event->wd, event->mask, event->len ? event->name : ""); if (event->wd != file_wait->watch_id) return 0; if (event->mask & IN_IGNORED) { /* The watch has been removed. */ file_wait->watch_id = -1; return cras_file_wait_rm_watch(file_wait); } if (event->len == 0 || memcmp(event->name, file_wait->watch_file_name, file_wait->watch_file_name_len + 1) != 0) { /* Some file we don't care about. */ return 0; } if ((event->mask & (IN_CREATE | IN_MOVED_TO)) != 0) file_wait_event = CRAS_FILE_WAIT_EVENT_CREATED; else if ((event->mask & (IN_DELETE | IN_MOVED_FROM)) != 0) file_wait_event = CRAS_FILE_WAIT_EVENT_DELETED; else return 0; /* Found the file! */ if (strcmp(file_wait->watch_path, file_wait->file_path) == 0) { /* Tell the caller about this creation or deletion. */ file_wait->callback(file_wait->callback_context, file_wait_event, event->name); } else { /* Remove the watch for this file, move on. */ return cras_file_wait_rm_watch(file_wait); } return 0; } int cras_file_wait_dispatch(struct cras_file_wait *file_wait) { struct inotify_event *event; char *watch_dir_end; size_t watch_dir_len; char *watch_file_start; size_t watch_path_len; int rc = 0; int flags; ssize_t read_rc; ssize_t read_offset; if (!file_wait) return -EINVAL; /* If we have a file-descriptor, then read it and see what's up. */ if (file_wait->inotify_fd >= 0) { read_offset = 0; read_rc = read(file_wait->inotify_fd, file_wait->event_buf, CRAS_FILE_WAIT_EVENT_SIZE); if (read_rc < 0) { rc = -errno; if ((rc == -EAGAIN || rc == -EWOULDBLOCK) && file_wait->watch_id < 0) { /* Really nothing to read yet: we need to * setup a watch. */ rc = 0; } } else if (read_rc < CRAS_FILE_WAIT_EVENT_MIN_SIZE) { rc = -EIO; } else if (file_wait->watch_id < 0) { /* Processing messages related to old watches. */ rc = 0; } else while (rc == 0 && read_offset < read_rc) { event = (struct inotify_event *)(file_wait->event_buf + read_offset); read_offset += sizeof(*event) + event->len; rc = cras_file_wait_process_event(file_wait, event); } } /* Report errors from above here. */ if (rc < 0) return rc; if (file_wait->watch_id >= 0) { /* Assume that the watch that we have is the right one. */ return 0; } /* Initialize inotify if we haven't already. */ if (file_wait->inotify_fd < 0) { file_wait->inotify_fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC); if (file_wait->inotify_fd < 0) return -errno; } /* Figure out what we need to watch next. */ rc = -ENOENT; strcpy(file_wait->watch_dir, file_wait->file_path); watch_dir_len = file_wait->file_path_len; while (rc == -ENOENT) { strcpy(file_wait->watch_path, file_wait->watch_dir); watch_path_len = watch_dir_len; /* Find the end of the parent directory. */ watch_dir_end = file_wait->watch_dir + watch_dir_len - 1; while (watch_dir_end > file_wait->watch_dir && *watch_dir_end != '/') watch_dir_end--; watch_file_start = watch_dir_end + 1; /* Treat consecutive '/' characters as one. */ while (watch_dir_end > file_wait->watch_dir && *(watch_dir_end - 1) == '/') watch_dir_end--; watch_dir_len = watch_dir_end - file_wait->watch_dir; if (watch_dir_len == 0) { /* We're looking for a file in the current directory. */ strcpy(file_wait->watch_file_name, file_wait->watch_path); file_wait->watch_file_name_len = watch_path_len; strcpy(file_wait->watch_dir, "."); watch_dir_len = 1; } else { /* Copy out the file name that we're looking for, and * mark the end of the directory path. */ strcpy(file_wait->watch_file_name, watch_file_start); file_wait->watch_file_name_len = watch_path_len - (watch_file_start - file_wait->watch_dir); *watch_dir_end = 0; } if (file_wait->flags & CRAS_FILE_WAIT_FLAG_MOCK_RACE) { /* For testing only. */ mknod(file_wait->watch_path, S_IFREG | 0600, 0); file_wait->flags &= ~CRAS_FILE_WAIT_FLAG_MOCK_RACE; } flags = IN_CREATE | IN_MOVED_TO | IN_DELETE | IN_MOVED_FROM; file_wait->watch_id = inotify_add_watch( file_wait->inotify_fd, file_wait->watch_dir, flags); if (file_wait->watch_id < 0) { rc = -errno; continue; } /* Satisfy the race condition between existence of the * file and creation of the watch. */ rc = access(file_wait->watch_path, F_OK); if (rc < 0) { rc = -errno; if (rc == -ENOENT) { /* As expected, the file still doesn't exist. */ rc = 0; } continue; } /* The file we're looking for exists. */ if (strcmp(file_wait->watch_path, file_wait->file_path) == 0) { file_wait->callback(file_wait->callback_context, CRAS_FILE_WAIT_EVENT_CREATED, file_wait->watch_file_name); return 0; } /* Start over again. */ rc = cras_file_wait_rm_watch(file_wait); if (rc < 0) return rc; rc = -ENOENT; strcpy(file_wait->watch_dir, file_wait->file_path); watch_dir_len = file_wait->file_path_len; } /* Get out for permissions problems for example. */ return rc; } int 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) { struct cras_file_wait *file_wait; size_t file_path_len; int rc; if (!file_path || !*file_path || !callback || !file_wait_out) return -EINVAL; *file_wait_out = NULL; /* Create a struct cras_file_wait to track waiting for this file. */ file_path_len = strlen(file_path); file_wait = (struct cras_file_wait *)calloc( 1, sizeof(*file_wait) + ((file_path_len + 1) * 5)); if (!file_wait) return -ENOMEM; file_wait->callback = callback; file_wait->callback_context = callback_context; file_wait->inotify_fd = -1; file_wait->watch_id = -1; file_wait->file_path_len = file_path_len; file_wait->flags = flags; /* We've allocated memory such that the file_path, watch_path, * watch_dir, and watch_file_name data are appended to the end of * our cras_file_wait structure. */ file_wait->file_path = (const char *)file_wait + sizeof(*file_wait); file_wait->watch_path = (char *)file_wait->file_path + file_path_len + 1; file_wait->watch_dir = file_wait->watch_path + file_path_len + 1; file_wait->watch_file_name = file_wait->watch_dir + file_path_len + 1; memcpy((void *)file_wait->file_path, file_path, file_path_len + 1); /* Setup the first watch. If that fails unexpectedly, then we destroy * the file wait structure immediately. */ rc = cras_file_wait_dispatch(file_wait); if (rc < 0) { cras_file_wait_destroy(file_wait); return rc; } *file_wait_out = file_wait; return 0; }