mbdemux

git clone https://orangeshoelaces.net/git/mbdemux.git

/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 }