/mbdemux.c
1 /* Copyright 2018, 2019 Vasilii Kolobkov */
2
3 /* This program is free software: you can redistribute it and/or modify */
4 /* it under the terms of the GNU General Public License as published by */
5 /* the Free Software Foundation, either version 3 of the License, or */
6 /* (at your option) any later version. */
7
8 /* This program is distributed in the hope that it will be useful, */
9 /* but WITHOUT ANY WARRANTY; without even the implied warranty of */
10 /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
11 /* GNU General Public License for more details. */
12
13 /* You should have received a copy of the GNU General Public License */
14 /* along with this program. If not, see <https://www.gnu.org/licenses/>. */
15
16 #include <errno.h>
17 #include <signal.h>
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <string.h>
21 #include <unistd.h>
22
23 #include <sys/types.h>
24 #include <sys/wait.h>
25
26 #include "err.h"
27
28 char *prog = "mbdemux";
29
30 enum mbstate { MB_INIT, MB_MSG, MB_EMPTY, MB_EOF, MB_ERR };
31 enum error { E_OK = 0, E_MEM, E_IO, E_MB_EOF };
32
33 struct buf {
34 char *data;
35 size_t len;
36 size_t cap;
37 };
38
39 void
40 buf_pop(struct buf *b, size_t len)
41 {
42 memmove(b->data, b->data + len, b->len - len);
43 b->len -= len;
44 }
45
46 int
47 buf_draw(struct buf *b, int src)
48 {
49 size_t newcap;
50 char *newdata;
51 ssize_t res;
52
53 if (b->len >= b->cap) {
54 newcap = b->cap == 0 ? 1024 : b->cap * 2;
55 if (newcap < b->cap) {
56 return E_MEM;
57 }
58 newdata = realloc(b->data, newcap);
59 if (!newdata) {
60 return E_MEM;
61 }
62 b->data = newdata;
63 b->cap = newcap;
64 }
65
66 res = read(src, b->data + b->len, b->cap - b->len);
67 switch (res) {
68 case 0: return E_MB_EOF;
69 case -1: return E_IO;
70 }
71 b->len += res;
72 return E_OK;
73 }
74
75 int
76 seekeol(int in, struct buf *b, size_t start, size_t *end)
77 {
78 size_t i;
79 char *c, *bend;
80 int e;
81
82 i = start;
83 while (1) {
84 c = b->data + i;
85 bend = b->data + b->len;
86 while (c < bend && *c != '\n') {
87 ++c;
88 }
89 i = c - b->data;
90
91 if (c != bend) {
92 break;
93 }
94 if (e = buf_draw(b, in)) {
95 return e;
96 }
97 }
98
99 *end = i + 1;
100 return E_OK;
101 }
102
103 int
104 matchfrom(char *s, size_t len)
105 {
106 return len >= 5 && s[0] == 'F' && s[1] == 'r' &&
107 s[2] == 'o' && s[3] == 'm' && s[4] == ' ';
108 }
109
110 void
111 pipemsg(char *msg, size_t len, char **argv)
112 {
113 pid_t s;
114 int p[2];
115 void (*osigpipe)(int);
116 ssize_t n;
117 int ss;
118
119 if ((osigpipe = signal(SIGPIPE, SIG_IGN)) == SIG_ERR) {
120 err(1, "failed to install SIGPIPE handler");
121 }
122 if (pipe(p)) {
123 err(1, "failed to create a pipe");
124 }
125
126 s = fork();
127 if (s == 0) {
128 if (dup2(p[0], 0) == -1 || close(p[1]) == -1) {
129 warn("failed to initialize sink descriptors");
130 _exit(1);
131 }
132 execvp(argv[0], argv);
133 warn("failed to launch sink");
134 _exit(1);
135 } else if (s == -1) {
136 err(1, "failed to fork");
137 }
138 if (close(p[0]) == -1) {
139 warn("failed to let read end of the pipe loose");
140 }
141 while (len > 0) {
142 n = write(p[1], msg, len);
143 if (n == -1) {
144 if ( errno == EINTR) continue;
145 else break;
146 }
147 msg += n;
148 len -= n;
149 }
150 if (n == -1 && errno != EPIPE) {
151 err(1, "failed to write to the sink");
152 }
153 if (close(p[1]) == -1) {
154 err(1, "failed to let write end of the pipe loose");
155 }
156 if (signal(SIGPIPE, osigpipe) == SIG_ERR) {
157 err(1, "failed to restore SIGPIPE handler");
158 }
159 if (waitpid(s, &ss, 0) == -1) {
160 err(1, "failed to randezvous with sink");
161 }
162 if (!WIFEXITED(ss)) {
163 err(1, "sink terminated abnormally");
164 }
165 if (WEXITSTATUS(ss)) {
166 err(1, "sink terminated abnormally with status of %d", WEXITSTATUS(ss));
167 }
168 }
169
170 int
171 main(int argc, char **argv)
172 {
173 struct buf b;
174 size_t c, eol;
175 size_t msgend;
176 int e, eof, state;
177
178 b = (struct buf){ 0, 0, 0 };
179 c = 0;
180 state = MB_INIT;
181
182 if (argc <= 1) {
183 printf("Usage: %s sink ...\n", prog);
184 exit(1);
185 }
186
187 while (!(state == MB_EOF || state == MB_ERR)) {
188 switch (e = seekeol(STDIN_FILENO, &b, c, &eol)) {
189 case E_MEM: errx(1, "out of memory");
190 case E_IO: err(1, "failed to read mbox");
191 }
192 eof = e == E_MB_EOF;
193
194 switch (state) {
195 case MB_INIT:
196 if (eof) {
197 state = MB_EOF;
198 } else if (matchfrom(b.data + c, eol - c)) {
199 buf_pop(&b, eol);
200 eol = 0;
201 state = MB_MSG;
202 } else {
203 state = MB_ERR;
204 }
205 break;
206 case MB_MSG:
207 if (eof || matchfrom(b.data + c, eol - c)) {
208 state = MB_ERR;
209 } else if (b.data[c] == '\n') {
210 msgend = c;
211 state = MB_EMPTY;
212 }
213 break;
214 case MB_EMPTY:
215 if (eof || matchfrom(b.data + c, eol - c)) {
216 pipemsg(b.data, msgend, argv + 1);
217 buf_pop(&b, eol);
218 eol = 0;
219 state = eof ? MB_EOF : MB_MSG;
220 } else if (b.data[c] == '\n') {
221 msgend = c;
222 state = MB_EMPTY;
223 } else {
224 state = MB_MSG;
225 }
226 break;
227 }
228 c = eol;
229 }
230 return 0;
231 }