Home | History | Annotate | Download | only in common
      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