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