summaryrefslogtreecommitdiff
path: root/src/look.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/look.zig')
-rw-r--r--src/look.zig30
1 files changed, 29 insertions, 1 deletions
diff --git a/src/look.zig b/src/look.zig
index f5d1488c..8c7014f5 100644
--- a/src/look.zig
+++ b/src/look.zig
@@ -857,7 +857,15 @@ pub fn readFile(gpa: std.mem.Allocator, path: []const u8) ![]u8 {
}
var pathbuf: [4096]u8 = undefined;
const path_z = std.fmt.bufPrintSentinel(&pathbuf, "{s}", .{path}, 0) catch return error.PathTooLong;
- const fd = libc.open(path_z, .{ .ACCMODE = .RDONLY });
+ // NONBLOCK, and it is the difference between an error and a dead editor.
+ // A plain blocking `open` of a FIFO waits for a writer that may never come,
+ // and this call runs INSIDE the keystroke that asked for it — no frame, no
+ // message row, and in the tty shell no Ctrl-C either, because the terminal
+ // is in raw mode. `Look` on a named pipe (or on a device that blocks until
+ // carrier) froze the whole program with nothing on screen to say why. The
+ // flag is cleared again below for the file kinds that are worth reading;
+ // the ones that are not are refused by name.
+ const fd = libc.open(path_z, .{ .ACCMODE = .RDONLY, .NONBLOCK = true });
// WHY it would not open, not just that it would not. Every one of these is
// an ordinary thing to do by accident — `pardes /root`, a file left at mode
// 000, a name that was deleted between resolving and reading — and a caller
@@ -872,7 +880,20 @@ pub fn readFile(gpa: std.mem.Allocator, path: []const u8) ![]u8 {
else => error.OpenFailed,
};
defer _ = libc.close(fd);
+
+ // The flag STAYS SET, and the read loops below answer `EAGAIN` with
+ // `NotAFile`. A regular file ignores `O_NONBLOCK` entirely — the kernel
+ // never short-reads one for it — so this costs ordinary opens nothing and
+ // turns the one case that would have hung into an error with a name.
const end = libc.lseek(fd, 0, libc.SEEK.END);
+ // NOT SEEKABLE IS NOT A DOCUMENT. `lseek` answers `ESPIPE` for a pipe, a
+ // socket and a terminal, and those are exactly the things whose "contents"
+ // are a future rather than a file — with `O_NONBLOCK` above they no longer
+ // hang the editor, but an unwritten FIFO then reads as EOF and opened as a
+ // silent empty pane, which says even less than the hang did. The zero-size
+ // files that ARE worth streaming — procfs and its kin — seek fine and
+ // report 0, so they take the branch below untouched.
+ if (end < 0 and libc.errno(end) == .SPIPE) return error.NotAFile;
const size: usize = if (end < 0) 0 else @intCast(end);
if (end >= 0 and libc.lseek(fd, 0, libc.SEEK.SET) < 0) return error.ReadFailed;
if (size == 0) {
@@ -885,6 +906,9 @@ pub fn readFile(gpa: std.mem.Allocator, path: []const u8) ![]u8 {
const n = libc.read(fd, &first, first.len);
if (n < 0) {
if (libc.errno(n) == .INTR) continue;
+ // Nothing to read AND nothing that will end: an empty pipe with
+ // no writer. This is the hang, reported instead of waited on.
+ if (libc.errno(n) == .AGAIN) return error.NotAFile;
return error.ReadFailed;
}
first_len = @intCast(n);
@@ -899,6 +923,9 @@ pub fn readFile(gpa: std.mem.Allocator, path: []const u8) ![]u8 {
const n = libc.read(fd, stream[stream_len..].ptr, stream.len - stream_len);
if (n < 0) {
if (libc.errno(n) == .INTR) continue;
+ // A stream that has paused is a stream that has ended, as far
+ // as one keystroke is concerned: keep what came.
+ if (libc.errno(n) == .AGAIN) break;
return error.ReadFailed;
}
if (n == 0) break;
@@ -909,6 +936,7 @@ pub fn readFile(gpa: std.mem.Allocator, path: []const u8) ![]u8 {
while (true) {
const n = libc.read(fd, &extra, 1);
if (n < 0 and libc.errno(n) == .INTR) continue;
+ if (n < 0 and libc.errno(n) == .AGAIN) break;
if (n < 0) return error.ReadFailed;
if (n > 0) return error.FileTooLarge;
break;