Skip to content

Commit

Permalink
cores/epoxy/main.cpp: Enter raw mode only if both STDIN and STDOUT ar…
Browse files Browse the repository at this point in the history
…e terminals (see #2, see #25)
  • Loading branch information
bxparks committed Sep 30, 2021
1 parent 65ff939 commit bf6248f
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 47 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
* Add `toString()` to `IPAddress` class, activated with `EPOXY_CORE_ESP8266`
for compatibility with ESP8266 Core.
* Add `strstr_P()` to `pgmspace.h`.
* Finally fix [Issue #2](https://github.com/bxparks/EpoxyDuino/issues/2) and
[Issue #25](https://github.com/bxparks/EpoxyDuino/issues/25).
* 0.8 (2021-08-08)
* Add `EpoxyMockTimerOne` mock library for `TimerOne`
(https://github.com/PaulStoffregen/TimerOne).
Expand Down
14 changes: 1 addition & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -854,19 +854,7 @@ This library has been tested on:
<a name="Bugs"></a>
## Bugs and Limitations

If the executable (e.g. `SampleTest.out`) is piped to the `less(1)` or `more(1)`
command, sometimes (not all the time) the executable hangs and displays nothing
on the pager program. I don't know why, it probably has to do with the way that
the `less` or `more` programs manipulate the `stdin`. The solution is to
explicitly redirect the `stdin`:

```
$ ./SampleTest.out | grep failed # works
$ ./SampleTest.out | less # hangs
$ ./SampleTest.out < /dev/null | less # works
```
None that I am aware of.

<a name="FeedbackAndSupport"></a>
## Feedback and Support
Expand Down
93 changes: 60 additions & 33 deletions cores/epoxy/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
#include <signal.h> // SIGINT
#include <stdlib.h> // exit()
#include <stdio.h> // perror()
#include <unistd.h> // isatty(), STDIN_FILENO
#include <unistd.h> // isatty(), STDIN_FILENO, STDOUT_FILENO
#include <fcntl.h>
#include <termios.h>

Expand All @@ -34,61 +34,78 @@
static struct termios orig_termios;
static int orig_stdin_flags;
static bool inRawMode = false;
static bool inNonBlockingMode = false;

static void die(const char* s) {
perror(s);
exit(1);
}

static void disableRawMode() {
if (!isatty(STDIN_FILENO)) return;
if (!inRawMode) return;
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios) == -1) {
inRawMode = false; // prevent exit(1) from being called twice
die("disableRawMode(): tcsetattr() failure");
if (inRawMode) {
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios) == -1) {
inRawMode = false; // prevent exit(1) from being called twice
die("disableRawMode(): tcsetattr() failure");
}
}

if (fcntl(STDIN_FILENO, F_SETFL, orig_stdin_flags) == -1) {
die("enableRawMode(): fcntl() failure");
if (inNonBlockingMode) {
if (fcntl(STDIN_FILENO, F_SETFL, orig_stdin_flags) == -1) {
die("enableRawMode(): fcntl() failure");
}
}
}

static void enableRawMode() {
// If STDIN is not a real tty, simply return instead of dying so that the
// unit tests can run in a continuous integration framework, e.g. Jenkins.
if (!isatty(STDIN_FILENO)) return;
if (tcgetattr(STDIN_FILENO, &orig_termios) == -1) {
die("enableRawMode(): tcgetattr() failure");
}
// Enter raw mode only if STDIN is a terminal. If the input is a file or a
// pipe (or even /dev/null), then raw mode does not make sense.
//
// In addition, enter raw mode only if STDOUT is also a terminal. If the
// output is a file or a pipe, it could be piping to a pager, like the less(1)
// program which wants to handle keyboard inputs in raw mode by itself.
//
// I believe this fixes https://github.com/bxparks/EpoxyDuino/issues/2 and
// https://github.com/bxparks/EpoxyDuino/issues/25 finally.
if (isatty(STDOUT_FILENO) && isatty(STDIN_FILENO)) {

// Save the original config.
if (tcgetattr(STDIN_FILENO, &orig_termios) == -1) {
die("enableRawMode(): tcgetattr() failure");
}

// The 'Enter' key in raw mode is ^M (\r, CR). But internally, we want this
// to be ^J (\n, NL), so ICRNL and INLCR causes the ^M to become a \n.
struct termios raw = orig_termios;
raw.c_iflag &= ~(/*ICRNL | INLCR |*/ INPCK | ISTRIP | IXON);

// Set the output into cooked mode, to handle NL and CR properly.
// Print.println() sends CR-NL (\r\n). But some code will send just \n. The
// ONLCR translates \n into \r\n. So '\r\n' will become \r\r\n, which is
// just fine.
raw.c_oflag |= (OPOST | ONLCR);
raw.c_cflag |= (CS8);

// Enable ISIG to allow Ctrl-C to kill the program.
raw.c_lflag &= ~(/*ECHO | ISIG |*/ ICANON | IEXTEN);
raw.c_cc[VMIN] = 0;
raw.c_cc[VTIME] = 0;
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1) {
die("enableRawMode(): tcsetattr() failure");
}

struct termios raw = orig_termios;
// The 'Enter' key in raw mode is ^M (\r, CR). But internally, we want this
// to be ^J (\n, NL), so ICRNL and INLCR causes the ^M to become a \n.
raw.c_iflag &= ~(/*ICRNL | INLCR |*/ INPCK | ISTRIP | IXON);
// Set the output into cooked mode, to handle NL and CR properly.
// Print.println() sends CR-NL (\r\n). But some code will send just \n. The
// ONLCR translates \n into \r\n. So '\r\n' will become \r\r\n, which is just
// fine.
raw.c_oflag |= (OPOST | ONLCR);
raw.c_cflag |= (CS8);
// Enable ISIG to allow Ctrl-C to kill the program.
raw.c_lflag &= ~(/*ECHO | ISIG |*/ ICANON | IEXTEN);
raw.c_cc[VMIN] = 0;
raw.c_cc[VTIME] = 0;
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1) {
die("enableRawMode(): tcsetattr() failure");
inRawMode = true;
}

// Always set input into non-blocking mode so that yield() does not block when
// it reads one character, emulating a read from the Serial port.
orig_stdin_flags = fcntl(STDIN_FILENO, F_GETFL, 0);
if (fcntl(STDIN_FILENO, F_SETFL, orig_stdin_flags | O_NONBLOCK) == -1) {
die("enableRawMode(): fcntl() failure");
}

inRawMode = true;
inNonBlockingMode = true;
}

static void handleControlC(int /*sig*/) {
if (!isatty(STDIN_FILENO)) return;
if (inRawMode) {
// If this returns an error, don't call die() because it will call exit(),
// which may call this again, causing an infinite recursion.
Expand All @@ -97,6 +114,16 @@ static void handleControlC(int /*sig*/) {
}
inRawMode = false;
}

if (inNonBlockingMode) {
// If this returns an error, don't call die() because it will call exit(),
// which may call this again, causing an infinite recursion.
if (fcntl(STDIN_FILENO, F_SETFL, orig_stdin_flags) == -1) {
perror("handleControlC(): fcntl() failure");
}
inNonBlockingMode = false;
}

exit(1);
}

Expand Down
2 changes: 1 addition & 1 deletion examples/PipeFail/PipeFail.ino
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
// * Ubuntu 20.04: 300-1000
// * MacOS 10.13: ~1000
// * FreeBSD 12: 1000-2000
const int NUM_LINES = 2000;
const int NUM_LINES = 100000;
const char LINE[] = "Reproduce https://github.com/bxparks/EpoxyDuino/issues/2";

void setup() {
Expand Down

0 comments on commit bf6248f

Please sign in to comment.