aboutsummaryrefslogtreecommitdiff
path: root/slowbaud.c
diff options
context:
space:
mode:
authorB. Watson <yalhcru@gmail.com>2021-07-21 00:12:31 -0400
committerB. Watson <yalhcru@gmail.com>2021-07-21 00:12:31 -0400
commitaa956853cc37d163113cafb2f1982b7a3a306fd1 (patch)
tree48aa7c82c29c24f38e65018d4c2d049af4018c1c /slowbaud.c
downloadslowbaud-aa956853cc37d163113cafb2f1982b7a3a306fd1.tar.gz
initial commit
Diffstat (limited to 'slowbaud.c')
-rw-r--r--slowbaud.c307
1 files changed, 307 insertions, 0 deletions
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 <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/select.h>
+#include <sys/wait.h>
+#include <termios.h>
+#include <sys/ioctl.h>
+#include <signal.h>
+#include <time.h>
+#include <string.h>
+
+/* portability stuff only tested on Linux and OSX. Hope it's OK. */
+#if defined(__FreeBSD__) || defined(HAVE_LIBUTIL_H)
+ #include <libutil.h>
+#elif defined (__OpenBSD__) || defined (__NetBSD__) || defined (__APPLE__) || defined(HAVE_UTIL_H)
+ #include <util.h>
+#else
+ #include <pty.h>
+#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 [<bits-per-sec>] [<file> [<file> ...]]\n", self);
+ printf(" With no filenames, reads stdin.\n");
+ printf("or: %s [<bits-per-sec>] -c [<command> [<arg> ...]]\n", self);
+ printf(" With no command, spawns a shell.\n");
+ printf("or: %s [<bits-per-sec>] -e <string> [<string> ...]\n", self);
+ printf("With no <bits-per-sec>, 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);
+}