adapted to argp, added options to control the tty.

This commit is contained in:
Gabe Venberg 2023-09-13 12:47:59 -05:00
parent 85c2e213b5
commit ae1e7979c7
4 changed files with 464 additions and 144 deletions

View file

@ -1,143 +0,0 @@
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
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;
}

View file

@ -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

5
serial_logger/echoterm.sh Executable file
View file

@ -0,0 +1,5 @@
#/bin/bash
while true; do
echo "test" > $1
done

View file

@ -0,0 +1,458 @@
#include <argp.h>
#include <bits/types/error_t.h>
#include <errno.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <time.h>
#include <unistd.h>
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;
}