1 /* ISC license. */
2
3 #include <sys/uio.h>
4 #include <string.h>
5 #include <stdint.h>
6 #include <errno.h>
7 #include <signal.h>
8 #include <regex.h>
9
10 #include <skalibs/posixplz.h>
11 #include <skalibs/posixishard.h>
12 #include <skalibs/types.h>
13 #include <skalibs/allreadwrite.h>
14 #include <skalibs/error.h>
15 #include <skalibs/strerr2.h>
16 #include <skalibs/buffer.h>
17 #include <skalibs/stralloc.h>
18 #include <skalibs/sig.h>
19 #include <skalibs/tai.h>
20 #include <skalibs/djbunix.h>
21 #include <skalibs/iopause.h>
22 #include <skalibs/textmessage.h>
23 #include <skalibs/textclient.h>
24
25 #include "ftrig1.h"
26 #include <s6/ftrigr.h>
27
28 #define FTRIGRD_MAXREADS 32
29 #define FTRIGRD_BUFSIZE 17
30
31 #define dienomem() strerr_diefu1sys(111, "stralloc_catb")
32
33 typedef struct ftrigio_s ftrigio_t, *ftrigio_t_ref ;
34 struct ftrigio_s
35 {
36 unsigned int xindex ;
37 ftrig1_t trig ;
38 buffer b ;
39 char buf[FTRIGRD_BUFSIZE] ;
40 regex_t re ;
41 stralloc sa ;
42 uint32_t options ;
43 uint16_t id ; /* given by client */
44 } ;
45 #define FTRIGIO_ZERO { .xindex = 0, .trig = FTRIG1_ZERO, .b = BUFFER_INIT(0, -1, 0, 0), .buf = "", .sa = STRALLOC_ZERO, .options = 0, .id = 0 }
46
47 static ftrigio_t a[FTRIGR_MAX] ;
48 static unsigned int n = 0 ;
49
ftrigio_deepfree(ftrigio_t * p)50 static void ftrigio_deepfree (ftrigio_t *p)
51 {
52 ftrig1_free(&p->trig) ;
53 stralloc_free(&p->sa) ;
54 regfree(&p->re) ;
55 }
56
cleanup(void)57 static void cleanup (void)
58 {
59 unsigned int i = 0 ;
60 for (; i < n ; i++) ftrigio_deepfree(a + i) ;
61 n = 0 ;
62 }
63
trig(uint16_t id,char what,char info)64 static void trig (uint16_t id, char what, char info)
65 {
66 char pack[4] ;
67 uint16_pack_big(pack, id) ;
68 pack[2] = what ; pack[3] = info ;
69 if (!textmessage_put(textmessage_sender_x, pack, 4))
70 {
71 cleanup() ;
72 strerr_diefu1sys(111, "build answer") ;
73 }
74 }
75
answer(char c)76 static void answer (char c)
77 {
78 if (!textmessage_put(textmessage_sender_1, &c, 1))
79 {
80 cleanup() ;
81 strerr_diefu1sys(111, "textmessage_put") ;
82 }
83 }
84
s6_remove(unsigned int i)85 static void s6_remove (unsigned int i)
86 {
87 ftrigio_deepfree(a + i) ;
88 a[i] = a[--n] ;
89 a[i].b.c.x = a[i].buf ;
90 }
91
ftrigio_read(ftrigio_t * p)92 static inline int ftrigio_read (ftrigio_t *p)
93 {
94 unsigned int i = FTRIGRD_MAXREADS ;
95 while (i--)
96 {
97 regmatch_t pmatch ;
98 size_t blen ;
99 ssize_t r = sanitize_read(buffer_fill(&p->b)) ;
100 if (!r) break ;
101 if (r < 0) return (trig(p->id, 'd', errno), 0) ;
102 blen = buffer_len(&p->b) ;
103 if (!stralloc_readyplus(&p->sa, blen+1)) dienomem() ;
104 buffer_getnofill(&p->b, p->sa.s + p->sa.len, blen) ;
105 p->sa.len += blen ;
106 p->sa.s[p->sa.len] = 0 ;
107 while (!regexec(&p->re, p->sa.s, 1, &pmatch, REG_NOTBOL | REG_NOTEOL))
108 {
109 trig(p->id, '!', p->sa.s[pmatch.rm_eo - 1]) ;
110 if (!(p->options & FTRIGR_REPEAT)) return 0 ;
111 memmove(p->sa.s, p->sa.s + pmatch.rm_eo, p->sa.len + 1 - pmatch.rm_eo) ;
112 p->sa.len -= pmatch.rm_eo ;
113 }
114 }
115 return 1 ;
116 }
117
parse_protocol(struct iovec const * v,void * context)118 static int parse_protocol (struct iovec const *v, void *context)
119 {
120 char const *s = v->iov_base ;
121 uint16_t id ;
122 if (v->iov_len < 3)
123 {
124 cleanup() ;
125 strerr_dief1x(100, "invalid client request") ;
126 }
127 uint16_unpack_big(s, &id) ;
128 switch (s[2])
129 {
130 case 'U' : /* unsubscribe */
131 {
132 unsigned int i = 0 ;
133 for (; i < n ; i++) if (a[i].id == id) break ;
134 if (i < n)
135 {
136 s6_remove(i) ;
137 answer(0) ;
138 }
139 else answer(EINVAL) ;
140 break ;
141 }
142 case 'L' : /* subscribe to path and match re */
143 {
144 uint32_t options, pathlen, relen ;
145 int r ;
146 if (v->iov_len < 19)
147 {
148 answer(EPROTO) ;
149 break ;
150 }
151 uint32_unpack_big(s + 3, &options) ;
152 uint32_unpack_big(s + 7, &pathlen) ;
153 uint32_unpack_big(s + 11, &relen) ;
154 if (((pathlen + relen + 17) != v->iov_len) || s[15 + pathlen] || s[v->iov_len - 1])
155 {
156 answer(EPROTO) ;
157 break ;
158 }
159 if (n >= FTRIGR_MAX)
160 {
161 answer(ENFILE) ;
162 break ;
163 }
164 r = skalibs_regcomp(&a[n].re, s + 16 + pathlen, REG_EXTENDED) ;
165 if (r)
166 {
167 answer(r == REG_ESPACE ? ENOMEM : EINVAL) ;
168 break ;
169 }
170 if (!ftrig1_make(&a[n].trig, s + 15))
171 {
172 regfree(&a[n].re) ;
173 answer(errno) ;
174 break ;
175 }
176 buffer_init(&a[n].b, &buffer_read, a[n].trig.fd, a[n].buf, FTRIGRD_BUFSIZE) ;
177 a[n].options = options ;
178 a[n].id = id ;
179 a[n].sa = stralloc_zero ;
180 n++ ;
181 answer(0) ;
182 break ;
183 }
184 default :
185 {
186 cleanup() ;
187 strerr_dief1x(100, "invalid client request") ;
188 }
189 }
190 (void)context ;
191 return 1 ;
192 }
193
main(void)194 int main (void)
195 {
196 PROG = "s6-ftrigrd" ;
197
198 if (ndelay_on(0) < 0) strerr_diefu2sys(111, "ndelay_on ", "0") ;
199 if (ndelay_on(1) < 0) strerr_diefu2sys(111, "ndelay_on ", "1") ;
200 if (sig_ignore(SIGPIPE) < 0) strerr_diefu1sys(111, "ignore SIGPIPE") ;
201
202 {
203 tain_t deadline ;
204 tain_now_set_stopwatch_g() ;
205 tain_addsec_g(&deadline, 2) ;
206 if (!textclient_server_01x_init_g(FTRIGR_BANNER1, FTRIGR_BANNER1_LEN, FTRIGR_BANNER2, FTRIGR_BANNER2_LEN, &deadline))
207 strerr_diefu1sys(111, "sync with client") ;
208 }
209
210 for (;;)
211 {
212 iopause_fd x[3 + n] ;
213 unsigned int i = 0 ;
214
215 x[0].fd = 0 ; x[0].events = IOPAUSE_EXCEPT | IOPAUSE_READ ;
216 x[1].fd = 1 ; x[1].events = IOPAUSE_EXCEPT | (textmessage_sender_isempty(textmessage_sender_1) ? 0 : IOPAUSE_WRITE) ;
217 x[2].fd = textmessage_sender_fd(textmessage_sender_x) ;
218 x[2].events = IOPAUSE_EXCEPT | (textmessage_sender_isempty(textmessage_sender_x) ? 0 : IOPAUSE_WRITE) ;
219 for (; i < n ; i++)
220 {
221 a[i].xindex = 3 + i ;
222 x[3+i].fd = a[i].trig.fd ;
223 x[3+i].events = IOPAUSE_READ ;
224 }
225
226 if (iopause(x, 3 + n, 0, 0) < 0)
227 {
228 cleanup() ;
229 strerr_diefu1sys(111, "iopause") ;
230 }
231
232 /* client closed */
233 if ((x[0].revents | x[1].revents) & IOPAUSE_EXCEPT) break ;
234
235 /* client is reading */
236 if (x[1].revents & IOPAUSE_WRITE)
237 if (!textmessage_sender_flush(textmessage_sender_1) && !error_isagain(errno))
238 {
239 cleanup() ;
240 strerr_diefu1sys(111, "flush stdout") ;
241 }
242 if (x[2].revents & IOPAUSE_WRITE)
243 if (!textmessage_sender_flush(textmessage_sender_x) && !error_isagain(errno))
244 {
245 cleanup() ;
246 return 1 ;
247 }
248
249 /* scan listening ftrigs */
250 for (i = 0 ; i < n ; i++)
251 {
252 if (x[a[i].xindex].revents & IOPAUSE_READ)
253 if (!ftrigio_read(a+i)) s6_remove(i--) ;
254 }
255
256 /* client is writing */
257 if (!textmessage_receiver_isempty(textmessage_receiver_0) || x[0].revents & IOPAUSE_READ)
258 {
259 if (textmessage_handle(textmessage_receiver_0, &parse_protocol, 0) < 0)
260 {
261 if (errno == EPIPE) break ; /* normal exit */
262 cleanup() ;
263 strerr_diefu1sys(111, "handle messages from client") ;
264 }
265 }
266 }
267 cleanup() ;
268 return 0 ;
269 }
270