1 /*******************************************************************************
2 Copyright (c) 2016-2018 Vladimir Kondratyev <vladimir@kondratyev.su>
3 SPDX-License-Identifier: MIT
4
5 Permission is hereby granted, free of charge, to any person obtaining a copy
6 of this software and associated documentation files (the "Software"), to deal
7 in the Software without restriction, including without limitation the rights
8 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 copies of the Software, and to permit persons to whom the Software is
10 furnished to do so, subject to the following conditions:
11
12 The above copyright notice and this permission notice shall be included in
13 all copies or substantial portions of the Software.
14
15 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 THE SOFTWARE.
22 *******************************************************************************/
23
24 #include "compat.h"
25
26 #include <sys/types.h> /* uint32_t */
27 #include <sys/ioctl.h> /* ioctl */
28 #include <sys/socket.h>/* SO_NOSIGPIPE */
29 #include <sys/uio.h> /* iovec */
30
31 #include <assert.h> /* assert */
32 #include <stddef.h> /* offsetof */
33 #include <stdlib.h> /* realloc */
34 #include <string.h> /* memmove */
35
36 #include "sys/inotify.h"
37 #include "config.h"
38 #include "event-queue.h"
39 #include "utils.h"
40 #include "worker.h"
41
42 /**
43 * Initialize resources associated with inotify event queue.
44 *
45 * @param[in] eq A pointer to #event_queue.
46 **/
47 void
event_queue_init(event_queue * eq)48 event_queue_init (event_queue *eq)
49 {
50 eq->allocated = 0;
51 eq->count = 0;
52 eq->iov = NULL;
53 eq->last = NULL;
54 event_queue_set_max_events (eq, IN_DEF_MAX_QUEUED_EVENTS);
55 }
56
57 /**
58 * Free resources associated with inotify event queue.
59 *
60 * @param[in] eq A pointer to #event_queue.
61 **/
62 void
event_queue_free(event_queue * eq)63 event_queue_free (event_queue *eq)
64 {
65 int i;
66
67 for (i = 0; i < eq->count; i++) {
68 free (eq->iov[i].iov_base);
69 }
70 free (eq->iov);
71 free (eq->last);
72 }
73
74 /**
75 * Set maximum length for inotify event queue
76 *
77 * @param[in] eq A pointer to #event_queue.
78 * @param[in] max_events A maximal length of queue (in events)
79 * @return 0 on success, -1 otherwise.
80 **/
81 int
event_queue_set_max_events(event_queue * eq,int max_events)82 event_queue_set_max_events (event_queue *eq, int max_events)
83 {
84 if (max_events <= 0) {
85 errno = EINVAL;
86 return -1;
87 }
88 /* TODO: Implement event queue truncation */
89 eq->max_events = max_events;
90 return 0;
91 }
92
93 /**
94 * Extend inotify event queue space by one item.
95 *
96 * @param[in] eq A pointer to #event_queue.
97 **/
98 static int
event_queue_extend(event_queue * eq)99 event_queue_extend (event_queue *eq)
100 {
101 if (eq->count >= eq->allocated) {
102 int to_allocate = eq->count + 1;
103 void *ptr = realloc (eq->iov, sizeof (struct iovec) * to_allocate);
104 if (ptr == NULL) {
105 perror_msg ("Failed to extend events to %d items", to_allocate);
106 return -1;
107 }
108 eq->iov = ptr;
109 eq->allocated = to_allocate;
110 }
111
112 return 0;
113 }
114
115 /**
116 * Place inotify event in to event queue.
117 *
118 * @param[in] eq A pointer to #event_queue.
119 * @param[in] wd An associated watch's id.
120 * @param[in] mask An inotify watch mask.
121 * @param[in] cookie Event cookie.
122 * @param[in] name File name (may be NULL).
123 * @return 0 on success, -1 otherwise.
124 **/
125 int
event_queue_enqueue(event_queue * eq,int wd,uint32_t mask,uint32_t cookie,const char * name)126 event_queue_enqueue (event_queue *eq,
127 int wd,
128 uint32_t mask,
129 uint32_t cookie,
130 const char *name)
131 {
132 struct inotify_event *prev_ie;
133 int retval = 0;
134
135 if (eq->count > eq->max_events) {
136 return -1;
137 }
138
139 if (event_queue_extend (eq) == -1) {
140 return -1;
141 }
142
143 if (eq->count == eq->max_events) {
144 wd = -1;
145 mask = IN_Q_OVERFLOW;
146 cookie = 0;
147 name = NULL;
148 retval = -1;
149 }
150
151 /*
152 * Find previous reported event. If event queue is not empty, get last
153 * event from tail. Otherwise get last event sent to communication pipe.
154 */
155 prev_ie =
156 eq->count > 0 ? eq->iov[eq->count - 1].iov_base : (void *)eq->last;
157
158 /* Compare current event with previous to decide if it can be coalesced */
159 if (prev_ie != NULL &&
160 prev_ie->wd == wd &&
161 prev_ie->mask == mask &&
162 prev_ie->cookie == cookie &&
163 ((prev_ie->len == 0 && name == NULL) ||
164 (prev_ie->len > 0 && name != NULL && !strcmp (prev_ie->name, name)))) {
165 /* Events are identical and queue is not empty. Skip current. */
166 if (eq->count > 0) {
167 return retval;
168 }
169 /* Event queue is empty. Check if any events remain in the pipe */
170 int fd = EQ_TO_WRK(eq)->io[INOTIFY_FD];
171 int buffered;
172 if (ioctl (fd, FIONREAD, &buffered) == 0 && buffered > 0) {
173 return retval;
174 }
175 }
176
177 eq->iov[eq->count].iov_base = (void *)create_inotify_event (
178 wd, mask, cookie, name, &eq->iov[eq->count].iov_len);
179 if (eq->iov[eq->count].iov_base == NULL) {
180 perror_msg ("Failed to create a inotify event %x", mask);
181 return -1;
182 }
183
184 ++eq->count;
185
186 return retval;
187 }
188
189 /**
190 * Flush inotify events queue to socket
191 *
192 * @param[in] eq A pointer to #event_queue.
193 * @param[in] sbspace Amount of space in socket buffer available to write
194 * w/o blocking
195 **/
event_queue_flush(event_queue * eq,size_t sbspace)196 void event_queue_flush (event_queue *eq, size_t sbspace)
197 {
198 int iovcnt, iovmax;
199 size_t iovlen = 0;
200
201 iovmax = eq->count;
202 if (iovmax > IOV_MAX) {
203 iovmax = IOV_MAX;
204 }
205
206 for (iovcnt = 0; iovcnt < iovmax; iovcnt++) {
207 if (iovlen + eq->iov[iovcnt].iov_len > sbspace) {
208 break;
209 }
210 iovlen += eq->iov[iovcnt].iov_len;
211 }
212
213 if (iovcnt == 0) {
214 return;
215 }
216
217 int send_flags = 0;
218 #if defined (MSG_NOSIGNAL)
219 send_flags |= MSG_NOSIGNAL;
220 #endif
221
222 int fd = EQ_TO_WRK(eq)->io[KQUEUE_FD];
223 if (safe_sendv (fd, eq->iov, iovcnt, send_flags) == -1) {
224 perror_msg ("Sending of inotify events to socket failed");
225 }
226
227 /* Save last event sent to communication pipe for coalecsing checks */
228 free (eq->last);
229 eq->last = (void *)eq->iov[iovcnt - 1].iov_base;
230
231 int i;
232 for (i = 0; i < iovcnt - 1; i++) {
233 free (eq->iov[i].iov_base);
234 }
235
236 memmove (&eq->iov[0],
237 &eq->iov[iovcnt],
238 sizeof(struct iovec) * (eq->count - iovcnt));
239 eq->count -= iovcnt;
240 }
241
242 /**
243 * Remove last event sent to communication pipe from internal buffer
244 *
245 * @param[in] eq A pointer to #event_queue.
246 **/
247 void
event_queue_reset_last(event_queue * eq)248 event_queue_reset_last (event_queue *eq)
249 {
250 assert (eq != NULL);
251
252 free (eq->last);
253 eq->last = NULL;
254 }
255