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