From ae1e7979c799990ea0e72cb8198de2f7b18919fa Mon Sep 17 00:00:00 2001 From: Gabe Venberg Date: Wed, 13 Sep 2023 12:47:59 -0500 Subject: [PATCH] adapted to argp, added options to control the tty. --- file_tee/src/file_tee.c | 143 --------- {file_tee => serial_logger}/Makefile | 2 +- serial_logger/echoterm.sh | 5 + serial_logger/src/serial_logger.c | 458 +++++++++++++++++++++++++++ 4 files changed, 464 insertions(+), 144 deletions(-) delete mode 100644 file_tee/src/file_tee.c rename {file_tee => serial_logger}/Makefile (98%) create mode 100755 serial_logger/echoterm.sh create mode 100644 serial_logger/src/serial_logger.c diff --git a/file_tee/src/file_tee.c b/file_tee/src/file_tee.c deleted file mode 100644 index a338ae5..0000000 --- a/file_tee/src/file_tee.c +++ /dev/null @@ -1,143 +0,0 @@ -#include -#include -#include -#include -#include - -static const char doc[] = "Reads a file and outputs to a file while also printing contents.\n" - "If LOG_FILE is not given, will only output to stdout"; -static const char args_doc[] = "INPUT_FILE [LOG_FILE]"; - -struct Arguments { - char* inFile; - char* logFile; -}; - -static bool parseArgs(int argc, char* argv[], struct Arguments* arguments) { - bool succsess = true; - switch (argc) { - case 3: - arguments->logFile = argv[2]; - // fallthrough - case 2: - arguments->inFile = argv[1]; - break; - default: - printf("%s\n%s\n", args_doc, doc); - succsess = false; - break; - } - return succsess; -} - -// puts the current time into buff in the form of YYYY-MM-DD HH:MM:SS. -// buff MUST BE at least 20 chars long. -static bool getTimeString(char* buff) { - bool succsess = true; - time_t posixTime = time(NULL); - if (posixTime != (time_t)(-1)) { - struct tm utcTime = *gmtime(&posixTime); - // strftime returns 0 if the buffer was not big enough. - if (strftime(buff, 20, "%F %T", &utcTime) == 0) { - succsess = false; - } - } else { - succsess = false; - } - - return succsess; -} - -static bool sendToLog(FILE* logFile, char* lineBuff) { - bool succsess = true; - char currentTime[20] = ""; - if (getTimeString(currentTime)) { - if (fprintf(logFile, "%s ", currentTime) >= 0) { - if (fputs(lineBuff, logFile) >= 0) { - fflush(logFile); - } else { - succsess = false; - } - } else { - succsess = false; - } - } else { - succsess = false; - } - - return succsess; -} - -static bool initInputFile(FILE** inFile, char* fileName) { - bool succsess = true; - *inFile = fopen(fileName, "r"); - if (!inFile) { - printf("error opening file %s\n", fileName); - succsess = false; - } - return succsess; -} - -static bool initLogFile(FILE** logFile, char* fileName) { - bool succsess = true; - if (fileName != NULL) { - *logFile = fopen(fileName, "w"); - if (logFile != NULL) { - if (fputs("start of log\n", *logFile) < 0) { - printf("could not write to %s\n", fileName); - succsess = false; - } - } else { - printf("error opening file %s\n", fileName); - succsess = false; - } - fflush(*logFile); - } - - return succsess; -} - -static void processLines(FILE* inFile, FILE* logFile) { - char* lineBuff = NULL; - size_t lineBuffSize = 0; - ssize_t lineSize; - - do { - lineSize = getline(&lineBuff, &lineBuffSize, inFile); - printf("%s", lineBuff); - if (logFile != NULL) { - sendToLog(logFile, lineBuff); - } - } while (lineSize >= 0); - - free(lineBuff); -} - -int main(int argc, char* argv[]) { - // for some reason, when doing this with a serial port, log.log never gets written to. I think this is becasue the - // program is terminated with ctrl-c, so the file descriptors dont close? adding fflushes fixes it, but I shouldnt - // need to add that. - - int ret = EXIT_SUCCESS; - struct Arguments arguments = {NULL, NULL}; - if (parseArgs(argc, argv, &arguments)) { - FILE* inFile = NULL; - if (initInputFile(&inFile, arguments.inFile)) { - FILE* outFile = NULL; - if (initLogFile(&outFile, arguments.logFile)) { - processLines(inFile, outFile); - - // these things should be done by the OS when we exit. - // fclose(inFile); - // fclose(outFile); - } else { - ret = EXIT_FAILURE; - } - } else { - ret = EXIT_FAILURE; - } - } else { - ret = EXIT_FAILURE; - } - return ret; -} diff --git a/file_tee/Makefile b/serial_logger/Makefile similarity index 98% rename from file_tee/Makefile rename to serial_logger/Makefile index 82811a6..5aa581d 100644 --- a/file_tee/Makefile +++ b/serial_logger/Makefile @@ -10,7 +10,7 @@ CFLAGS = ${OPTIMIZATION} -Wextra -Wall -Wpedantic # clean: # rm -f helloworld # -TARGET_EXEC := filetee +TARGET_EXEC := seriallogger BUILD_DIR := ./build SRC_DIRS := ./src diff --git a/serial_logger/echoterm.sh b/serial_logger/echoterm.sh new file mode 100755 index 0000000..414443f --- /dev/null +++ b/serial_logger/echoterm.sh @@ -0,0 +1,5 @@ +#/bin/bash + +while true; do + echo "test" > $1 +done diff --git a/serial_logger/src/serial_logger.c b/serial_logger/src/serial_logger.c new file mode 100644 index 0000000..6a32f33 --- /dev/null +++ b/serial_logger/src/serial_logger.c @@ -0,0 +1,458 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const char doc[] = + "Reads a serial terminal and outputs to a log file while also printing contents.\n" + "Any options that have a negation as well as an assertion will use " + "defaults for the provided tty if neither is provided.\n" + "If LOG_FILE is not given, will only output to stdout"; +static const char args_doc[] = "INPUT_FILE"; + +typedef enum { TRUE, FALSE, DEFAULT } OptionalBool; + +// starts at 128 to make sure none of them are ascii printable. +typedef enum { + NO_PARITY = 128, + NO_STOP_BIT, + ENABLE_ECHO, + NO_ECHO, + ENABLE_ONLCR, + NO_ONLCR, + ENABLE_IGNCR, + NO_IGNCR +} longOnlyOpts; + +struct Arguments { + char* inFile; + char* logFile; + OptionalBool parity; // wether to use stop bits. + OptionalBool stopBits; // whether to have 1 or 2 stop bits. + char bitsPerByte; // zero means use default, otherwise can be 5, 6, 7, or 8. + OptionalBool echo; + OptionalBool oNLCR; // controls whether to replace cr with crlf on output. + OptionalBool iGNCR; // controls whether to replace cr with crlf on input. + unsigned int baudRate; // 0 means unset. Apparently 0 is valid, but what does a baud rate of 0 even mean? +}; + +static struct argp_option options[] = { + {"log-file", 'l', "LOG_FILE", 0, "File to log serial messages to", 0}, + {"parity", 'p', 0, 0, "enable parity bit", 0}, + {"no-parity", NO_PARITY, 0, 0, "disable parity bit", 0}, + {"extra-stop-bit", 's', 0, 0, "use 2 stop bits", 0}, + {"no-extra-stop-bit", NO_STOP_BIT, 0, 0, "use 1 stop bit", 0}, + {"bits-in-byte", 'b', "BITS", 0, "number of bits in a byte. Can be 5-8 (inclusive)", 0}, + {"echo", ENABLE_ECHO, 0, 0, "enable tty echo", 0}, + {"disable-echo", NO_ECHO, 0, 0, "disable tty echo", 0}, + {"onlcr", ENABLE_ONLCR, 0, 0, "convert outputed nl to nlcr", 0}, + {"no-onlcr", NO_ONLCR, 0, 0, "do not convert outputed nl to nlcr", 0}, + {"igncr", ENABLE_IGNCR, 0, 0, "ignore cr", 0}, + {"no-igncr", NO_IGNCR, 0, 0, "do not ignore cr", 0}, + {"baud-rate", 'r', "BAUDRATE", 0, + "baud rate of the terminal.\nMust be a standard baud rate: 50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, " + "2400, 4800, 9600, 19200, 38400, 57600, 115200, 230400, or 460800", + 0}, + {0}, +}; + +static error_t parseArgs(int key, char* arg, struct argp_state* state) { + struct Arguments* arguments = state->input; + switch (key) { + case 'l': + arguments->logFile = arg; + break; + case 'p': + arguments->parity = TRUE; + break; + case NO_PARITY: + arguments->parity = FALSE; + break; + case 's': + arguments->stopBits = TRUE; + break; + case NO_STOP_BIT: + arguments->stopBits = FALSE; + break; + case 'b': + arguments->bitsPerByte = strtol(arg, NULL, 10); + if (arguments->bitsPerByte < 5 || arguments->bitsPerByte > 8) { + argp_failure(state, 1, 2, "invalid range in bits-per-byte"); + } + break; + case ENABLE_ECHO: + arguments->echo = TRUE; + break; + case NO_ECHO: + arguments->echo = FALSE; + break; + case NO_ONLCR: + arguments->oNLCR = FALSE; + break; + case ENABLE_ONLCR: + arguments->oNLCR = TRUE; + break; + case ENABLE_IGNCR: + arguments->iGNCR = TRUE; + break; + case NO_IGNCR: + arguments->iGNCR = FALSE; + break; + case 'r': + arguments->baudRate = strtol(arg, NULL, 10); + break; + case ARGP_KEY_ARG: + if (state->arg_num > 1) { + argp_failure(state, 1, 0, "to many arguments"); + } else { + arguments->inFile = arg; + } + break; + case ARGP_KEY_END: + if (state->arg_num < 1) { + argp_failure(state, 1, 0, "to few arguments"); + } + break; + default: + return ARGP_ERR_UNKNOWN; + break; + } + return 0; +} + +static struct argp argp = {options, parseArgs, args_doc, doc, 0, 0, 0}; + +// static bool parseArgs(int argc, char* argv[], struct Arguments* arguments) { +// bool succsess = true; +// switch (argc) { +// case 3: +// arguments->logFile = argv[2]; +// // fallthrough +// case 2: +// arguments->inFile = argv[1]; +// break; +// default: +// printf("%s\n%s\n", args_doc, doc); +// succsess = false; +// break; +// } +// return succsess; +// } + +// puts the current time into buff in the form of YYYY-MM-DD HH:MM:SS. +// buff MUST BE at least 20 chars long. +static bool getTimeString(char* buff) { + bool succsess = true; + time_t posixTime = time(NULL); + if (posixTime != (time_t)(-1)) { + struct tm utcTime = *gmtime(&posixTime); + // strftime returns 0 if the buffer was not big enough. + if (strftime(buff, 20, "%F %T", &utcTime) == 0) { + succsess = false; + } + } else { + succsess = false; + } + + return succsess; +} + +static bool sendToLog(FILE* logFile, char* lineBuff) { + bool succsess = true; + char currentTime[20] = ""; + if (getTimeString(currentTime)) { + if (fprintf(logFile, "%s ", currentTime) >= 0) { + if (fputs(lineBuff, logFile) >= 0) { + fflush(logFile); + } else { + succsess = false; + } + } else { + succsess = false; + } + } else { + succsess = false; + } + + return succsess; +} + +static void setParity(struct termios* tty, struct Arguments* arguments) { + switch (arguments->parity) { + case TRUE: + tty->c_cflag |= PARENB; + break; + case FALSE: + tty->c_cflag &= ~PARENB; + break; + case DEFAULT: + // do nothing + break; + } +} + +static void setStopBits(struct termios* tty, struct Arguments* arguments) { + switch (arguments->stopBits) { + case TRUE: + tty->c_cflag |= CSTOPB; + break; + case FALSE: + tty->c_cflag &= ~CSTOPB; + break; + case DEFAULT: + // do nothing + break; + } +} + +static bool setBitsPerByte(struct termios* tty, struct Arguments* arguments) { + bool success = true; + if (arguments->bitsPerByte >= 5 && arguments->bitsPerByte <= 8) { + tty->c_cflag &= ~CSIZE; + switch (arguments->bitsPerByte) { + case 5: + tty->c_cflag |= CS5; + break; + case 6: + tty->c_cflag |= CS6; + break; + case 7: + tty->c_cflag |= CS7; + break; + case 8: + tty->c_cflag |= CS8; + break; + } + } else if (arguments->bitsPerByte == 0) { + // do nothing + } else { + printf("bits-in-byte set to invalid value: %d", arguments->bitsPerByte); + success = false; + } + return success; +} + +static void setEcho(struct termios* tty, struct Arguments* arguments) { + switch (arguments->echo) { + case TRUE: + tty->c_lflag |= ECHO; + break; + case FALSE: + tty->c_lflag &= ~ECHO; + break; + case DEFAULT: + break; + } +} + +static void setOnlcr(struct termios* tty, struct Arguments* arguments) { + switch (arguments->oNLCR) { + case TRUE: + tty->c_oflag |= ONLCR; + break; + case FALSE: + tty->c_lflag &= ~ONLCR; + break; + case DEFAULT: + break; + } +} + +static void setIgncr(struct termios* tty, struct Arguments* arguments) { + switch (arguments->iGNCR) { + case TRUE: + tty->c_lflag |= IGNCR; + break; + case FALSE: + tty->c_lflag &= ~IGNCR; + break; + case DEFAULT: + break; + } +} + +static bool setBaudRate(struct termios* tty, struct Arguments* arguments) { + bool success = true; + switch (arguments->baudRate) { + case 0: + // do nothing + break; + case 50: + cfsetispeed(tty, B50); + break; + case 75: + cfsetispeed(tty, B75); + break; + case 110: + cfsetispeed(tty, B110); + break; + case 134: + cfsetispeed(tty, B134); + break; + case 150: + cfsetispeed(tty, B150); + break; + case 200: + cfsetispeed(tty, B200); + break; + case 300: + cfsetispeed(tty, B300); + break; + case 600: + cfsetispeed(tty, B600); + break; + case 1200: + cfsetispeed(tty, B1200); + break; + case 1800: + cfsetispeed(tty, B1800); + break; + case 2400: + cfsetispeed(tty, B2400); + break; + case 4800: + cfsetispeed(tty, B4800); + break; + case 9600: + cfsetispeed(tty, B9600); + break; + case 19200: + cfsetispeed(tty, B19200); + break; + case 38400: + cfsetispeed(tty, B38400); + break; + case 57600: + cfsetispeed(tty, B57600); + break; + case 115200: + cfsetispeed(tty, B115200); + break; + case 230400: + cfsetispeed(tty, B230400); + break; + case 460800: + cfsetispeed(tty, B460800); + break; + default: + printf("invalid baud rate %d", arguments->baudRate); + success = false; + break; + } + return success; +} + +static bool setTTY(struct termios* tty, struct Arguments* arguments) { + bool success = true; + if (setBitsPerByte(tty, arguments)) { + if (setBaudRate(tty, arguments)) { + setParity(tty, arguments); + setStopBits(tty, arguments); + setEcho(tty, arguments); + setOnlcr(tty, arguments); + setIgncr(tty, arguments); + } else { + success = false; + } + } else { + success = false; + } + return success; +} + +static bool initInputFile(FILE** inFile, struct Arguments* arguments) { + bool succsess = true; + *inFile = fopen(arguments->inFile, "r"); + int fd = fileno(*inFile); + if (inFile != NULL || fd >= 0) { + struct termios tty; + if (tcgetattr(fd, &tty) == 0) { + if (setTTY(&tty, arguments) != 0) { + // Save tty settings, also checking for error + if (tcsetattr(fd, TCSANOW, &tty) != 0) { + printf("Could not set tty settings for %s", arguments->inFile); + succsess = false; + } + } else { + printf("Could not set tty settings for %s", arguments->inFile); + succsess = false; + } + } else { + printf("error getting existing tty settings of %s\n", arguments->inFile); + succsess = false; + } + } else { + printf("error opening file %s\n", arguments->inFile); + succsess = false; + } + return succsess; +} + +static bool initLogFile(FILE** logFile, char* fileName) { + bool succsess = true; + if (fileName != NULL) { + *logFile = fopen(fileName, "w"); + if (logFile != NULL) { + if (fputs("start of log\n", *logFile) < 0) { + printf("could not write to %s\n", fileName); + succsess = false; + } + } else { + printf("error opening file %s\n", fileName); + succsess = false; + } + fflush(*logFile); + } + + return succsess; +} + +static void processLines(FILE* inFile, FILE* logFile) { + char* lineBuff = NULL; + size_t lineBuffSize = 0; + ssize_t lineSize; + + do { + lineSize = getline(&lineBuff, &lineBuffSize, inFile); + printf("%s", lineBuff); + if (logFile != NULL) { + sendToLog(logFile, lineBuff); + } + } while (lineSize >= 0); + + free(lineBuff); +} + +int main(int argc, char* argv[]) { + // for some reason, when doing this with a serial port, log.log never gets + // written to. I think this is becasue the program is terminated with + // ctrl-c, so the file descriptors dont close? adding fflushes fixes it, but + // I shouldnt need to add that. + + int ret = EXIT_SUCCESS; + static struct Arguments arguments = {NULL, NULL, DEFAULT, DEFAULT, 0, DEFAULT, DEFAULT, DEFAULT, 0}; + argp_parse(&argp, argc, argv, 0, 0, &arguments); + FILE* inFile = NULL; + if (initInputFile(&inFile, &arguments)) { + FILE* outFile = NULL; + if (initLogFile(&outFile, arguments.logFile)) { + processLines(inFile, outFile); + + // these things should be done by the OS when we exit. + // fclose(inFile); + // fclose(outFile); + } else { + ret = EXIT_FAILURE; + } + } else { + ret = EXIT_FAILURE; + } + return ret; +}