From aa956853cc37d163113cafb2f1982b7a3a306fd1 Mon Sep 17 00:00:00 2001 From: "B. Watson" Date: Wed, 21 Jul 2021 00:12:31 -0400 Subject: initial commit --- slowbaud.c | 307 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 307 insertions(+) create mode 100644 slowbaud.c (limited to 'slowbaud.c') diff --git a/slowbaud.c b/slowbaud.c new file mode 100644 index 0000000..bbf31ac --- /dev/null +++ b/slowbaud.c @@ -0,0 +1,307 @@ +/* Simulate low bitrate serial connection, like a 1980s modem. + Author: B. Watson. License: WTFPL. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* portability stuff only tested on Linux and OSX. Hope it's OK. */ +#if defined(__FreeBSD__) || defined(HAVE_LIBUTIL_H) + #include +#elif defined (__OpenBSD__) || defined (__NetBSD__) || defined (__APPLE__) || defined(HAVE_UTIL_H) + #include +#else + #include +#endif + +#define DEFAULT_BPS 1200 +#define MIN_BPS 1 +#define MAX_BPS 500000 + +const char *self; +unsigned int bps; +unsigned long interval, starttime, outbytes = 0; + +int debug = 0; + +int timer; +struct timeval tv; +struct itimerval itv, itv_disarm; +sigset_t sigmask; + +#define NOW_USEC() ( (gettimeofday(&tv, NULL)), tv.tv_sec * 1000000L + tv.tv_usec ) + +void die(const char *msg) { + if(errno) { + fprintf(stderr, "%s: %s: %s\n", self, strerror(errno), msg); + } else { + fprintf(stderr, "%s: %s\n", self, msg); + } + exit(1); +} + +void setup_timer(void) { + itv_disarm.it_interval.tv_sec = + itv_disarm.it_interval.tv_usec = + itv_disarm.it_value.tv_sec = + itv_disarm.it_value.tv_usec = 0; + + itv = itv_disarm; + itv.it_interval.tv_usec = interval; + itv.it_value.tv_usec = interval; + + sigemptyset(&sigmask); + sigaddset(&sigmask, SIGALRM); + sigprocmask(SIG_BLOCK, &sigmask, NULL); +} + +void slow_write(int outfd, char c) { + int j; + unsigned long now, target, overslept; + + setitimer(ITIMER_REAL, &itv, NULL); + + target = NOW_USEC() + interval; + + write(outfd, &c, 1); + outbytes++; + sigwait(&sigmask, &j); + setitimer(ITIMER_REAL, &itv_disarm, NULL); + + now = NOW_USEC(); + if(now > target) { + overslept = now - target; + if(overslept < interval) { + unsigned long old = itv.it_interval.tv_usec; + itv.it_interval.tv_usec -= overslept; + itv.it_value.tv_usec = itv.it_interval.tv_usec; + if(debug) { + fprintf(stderr, "tv_usec was %ldms, overslept %ld, new tv_usec %ldms\n", + old, overslept, itv.it_interval.tv_usec); + } + } + } else { + while(NOW_USEC() < target) + /* NOP */ ; + } +} + +void debug_stats(void) { + if(outbytes && debug) { + unsigned long elapsed_us = NOW_USEC() - starttime; + double elapsed_sec = ((double)elapsed_us/1000000.0L); + double finterval = (1000000.0L / ((double)bps / 10.0L)); + double actual = ((double)outbytes * 10.0L) / elapsed_sec; + double offby = 100.0L * (((double)bps / actual) - 1.0L); + fprintf(stderr, + "outbytes %lu, elapsed_us %lu, requested bps %d (%.2fms), " + "actual %.2f, accuracy %.2f%%\n", + outbytes, elapsed_us, bps, finterval, actual, 100.0 - offby); + } + outbytes = 0; +} + +void slowcat(int infd) { + int c; + + starttime = NOW_USEC(); + while(read(infd, &c, 1) == 1) { + slow_write(1, c); + } + + if(debug) debug_stats(); +} + +void slowtty(char **args) { + char *dflt_args[] = { NULL, NULL }; + pid_t kidpid; + int master, status; + struct termios term, oldterm; + struct winsize winsz; + char c; + + if(!*args) { + args = dflt_args; + args[0] = getenv("SHELL"); + if(!*args) args[0] = "/bin/sh"; + } + if(debug) { + char **a = args; + fprintf(stderr, "executing cmd '%s', argv[]: ", a[0]); + while(*a) { + fprintf(stderr, "%s ", *a++); + } + fprintf(stderr, "\n"); + } + + if( (tcgetattr(0, &term)) ) die("tcgetattr"); + + if( (ioctl(0, TIOCGWINSZ, &winsz)) ) die("ioctl(TIOCGWINSZ)"); + + kidpid = forkpty(&master, NULL, &term, &winsz); + if(kidpid < 0) die("forkpty"); + + if(kidpid == 0) { + /* we are the child, do childish things */ + execvp(args[0], args); + perror("execvp"); /* don't use die() */ + exit(127); + } + + /* we are the parent, watch the child */ + oldterm = term; /* so we can restore it later */ + cfmakeraw(&term); + tcsetattr(0, TCSANOW, &term); + + while(1) { + ssize_t nbytes; + fd_set readfds; + + FD_ZERO(&readfds); + FD_SET(0, &readfds); + FD_SET(master, &readfds); + + if(select(master + 1, &readfds, NULL, NULL, NULL) < 0) { + if(errno == EINTR) continue; + break; + } + + if(FD_ISSET(0, &readfds)) { + nbytes = read(0, &c, 1); + if(nbytes < 1) break; + write(master, &c, 1); + } + + if(FD_ISSET(master, &readfds)) { + nbytes = read(master, &c, 1); + if(nbytes < 1) break; + slow_write(1, c); + } + } + + close(master); + while (waitpid(kidpid, &status, 0) < 0 && errno == EINTR) /* NOP */ ; + + tcsetattr(STDIN_FILENO, TCSANOW, &oldterm); + tcsetattr(STDOUT_FILENO, TCSANOW, &oldterm); + tcsetattr(STDERR_FILENO, TCSANOW, &oldterm); + + exit(WIFEXITED(status) ? WEXITSTATUS(status) : 127); +} + +void echo_args(char **args) { + starttime = NOW_USEC(); + while(*args) { + char *a = *args; + while(*a) slow_write(1, *a++); + args++; + if(*args) slow_write(1, ' '); + } + + slow_write(1, '\n'); + + if(debug) debug_stats(); + exit(0); +} + +void usage(int exitstat) { + printf("Usage: %s [] [ [ ...]]\n", self); + printf(" With no filenames, reads stdin.\n"); + printf("or: %s [] -c [ [ ...]]\n", self); + printf(" With no command, spawns a shell.\n"); + printf("or: %s [] -e [ ...]\n", self); + printf("With no , default rate is %d\n", DEFAULT_BPS); + exit(exitstat); +} + +int main(int argc, char **argv) { + int infd; + const char *p, *env_bps; + + if(getenv("SLOWBAUD_DEBUG")) debug = 1; + + self = argv[0]; + for(p = argv[0]; *p; p++) { + if(*p == '/') self = p + 1; + } + argv++, argc--; + + /* set bps. command line option overrides env, env overrides builtin */ + bps = DEFAULT_BPS; + + if( (env_bps = getenv("SLOWBAUD_BPS")) ) + bps = atoi(env_bps); + + if(*argv && (**argv > '0' && **argv <= '9')) { + char *e; + long b = strtol(*argv, &e, 0); + if(!*e) { + bps = (int)b; + argv++, argc--; + } + } + + if(bps < MIN_BPS || bps > MAX_BPS) { + fprintf(stderr, "%s: invalid bps, range %d - %d\n", self, MIN_BPS, MAX_BPS); + exit(1); + } + + /* if we used only integer math here, we couldn't support bps not + a multiple of 10 (e.g. 75 would be taken as 70). */ + interval = (unsigned long)(1000000.0L / ((double)bps / 10.0L)); + if(debug) fprintf(stderr, "interval %ld us\n", interval); + + if(*argv && argv[0][0] == '-' && argv[0][1] == '-' && !argv[0][2]) { + if(debug) fprintf(stderr, "skipping -- argument\n"); + argv++, argc--; + } + + setup_timer(); + + if(!argc) { + if(debug) fprintf(stderr, "no args, reading stdin\n"); + slowcat(0); + } else if(**argv == '-') { + switch(argv[0][1]) { + case 'c': + if(debug) fprintf(stderr, "-c given, creating a pty\n"); + slowtty(++argv); + break; + case 'e': + if(argc < 2) die("-e requires at least one argument"); + if(debug) fprintf(stderr, "-e given, echoing args\n"); + echo_args(++argv); + break; + case 'h': + case '?': + usage(0); + break; + default: + die("invalid option, try -h for help"); + break; + } + } else { + while(*argv) { + if(debug) fprintf(stderr, "opening file %s for reading\n", *argv); + if( (infd = open(*argv, O_RDONLY)) < 0) { + die(*argv); + } + slowcat(infd); + close(infd); + argv++; + } + } + exit(0); +} -- cgit v1.2.3