summaryrefslogtreecommitdiffstats
path: root/blkshowr.c
diff options
context:
space:
mode:
Diffstat (limited to 'blkshowr.c')
-rw-r--r--blkshowr.c402
1 files changed, 402 insertions, 0 deletions
diff --git a/blkshowr.c b/blkshowr.c
new file mode 100644
index 0000000..bbb0ea0
--- /dev/null
+++ b/blkshowr.c
@@ -0,0 +1,402 @@
+/* See LICENSE file for copyright and license details. */
+#include <termios.h>
+#include <libsimple.h>
+#include <libsimple-arg.h>
+#include <libterminput.h>
+
+USAGE("[-b block-size] file [block-list]");
+
+
+#define IS_KEY(INPUT, KEY)\
+ ((INPUT)->keypress.key == LIBTERMINPUT_SYMBOL &&\
+ toupper((INPUT)->keypress.symbol[0]) == toupper((KEY)) &&\
+ !(INPUT)->keypress.symbol[1])
+
+#define IS_CTRL_KEY(INPUT, KEY)\
+ (((INPUT)->keypress.mods & (enum libterminput_mod)~LIBTERMINPUT_SHIFT) == LIBTERMINPUT_CTRL && IS_KEY((INPUT), (KEY)))
+
+
+static void configure_terminal(void);
+static void restore_terminal(void);
+
+
+static size_t blksize = 4ul << 10;
+static off_t *blocks = NULL;
+static size_t blocks_size = 0u;
+static size_t nblocks = 0u;
+static int max_block_len;
+
+static volatile sig_atomic_t term_resized = 0;
+static struct termios term_attributes_stdin;
+static int term_attributes_stdin_saved = 0;
+static int term_entered_submode_stdout = 0;
+static int ttyfd = -1;
+
+static size_t term_width;
+static size_t term_height;
+static int exiting = 0;
+static int interrupt_deferred = 0;
+
+
+static void
+do_redraw(void)
+{
+ dprintf(ttyfd, "\033[H\033[2J");
+
+ /* TODO draw everything */
+}
+
+
+static void
+handle_keyboard_input(union libterminput_input *input)
+{
+ if (input->type == LIBTERMINPUT_KEYPRESS) {
+ if (IS_CTRL_KEY(input, 'Z')) {
+ restore_terminal();
+ raise(SIGTSTP);
+ configure_terminal();
+ redraw:
+ interrupt_deferred = 1;
+ term_resized = 1;
+ } else if (IS_CTRL_KEY(input, 'L')) {
+ goto redraw;
+ } else if (IS_CTRL_KEY(input, 'Q')) {
+ exiting = 1;
+ }
+ }
+}
+
+
+static void
+enter_subterminal(void)
+{
+ dprintf(ttyfd,
+ "\033[?1049h" /* enter subterminal */
+ "\033[?25l" /* hide cursor */
+ "\033[H\033[2J" /* clear terminal */
+ );
+ term_entered_submode_stdout = 1;
+}
+
+
+static void
+exit_subterminal(void)
+{
+ if (term_entered_submode_stdout) {
+ term_entered_submode_stdout = 0;
+ dprintf(ttyfd,
+ "\033[H\033[2J" /* clear terminal */
+ "\033[?25h" /* show cursor */
+ "\033[?1049l" /* exit subterminal */
+ );
+ }
+}
+
+
+static void
+set_term_attributes(void)
+{
+ struct termios new_term_attributes_stdin;
+
+ if (tcgetattr(ttyfd, &term_attributes_stdin))
+ eprintf("tcgetattr /dev/tty:");
+ term_attributes_stdin_saved = 1;
+
+ memcpy(&new_term_attributes_stdin, &term_attributes_stdin, sizeof(term_attributes_stdin));
+ new_term_attributes_stdin.c_iflag &= (tcflag_t)~(IXON | IXANY | IXOFF);
+ new_term_attributes_stdin.c_lflag &= (tcflag_t)~(ISIG | ICANON | ECHO | ECHOE | ECHOK | ECHONL);
+
+ if (tcsetattr(ttyfd, TCSANOW, &new_term_attributes_stdin))
+ weprintf("tcsetattr /dev/tty TCSANOW:");
+}
+
+
+static void
+restore_term_attributes(void)
+{
+ if (term_attributes_stdin_saved) {
+ term_attributes_stdin_saved = 0;
+ if (tcsetattr(ttyfd, TCSAFLUSH, &term_attributes_stdin))
+ weprintf("tcsetattr /dev/tty TCSAFLUSH:");
+ }
+}
+
+
+static void
+configure_terminal(void)
+{
+ set_term_attributes();
+ enter_subterminal();
+}
+
+
+static void
+restore_terminal(void)
+{
+ restore_term_attributes();
+ exit_subterminal();
+}
+
+
+static void
+cleanup(void)
+{
+ restore_terminal();
+}
+
+
+static void
+term_resized_callback(int signo)
+{
+ (void) signo;
+ term_resized = 1;
+}
+
+
+static void
+subscribe_term_resize(void)
+{
+ struct sigaction sa;
+
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_handler = &term_resized_callback;
+
+ if (sigaction(SIGWINCH, &sa, NULL))
+ eprintf("sigaction SIGWINCH:");
+}
+
+
+static void
+update_term_size(void)
+{
+ struct winsize ws;
+
+ if (ioctl(ttyfd, TIOCGWINSZ, &ws))
+ eprintf("ioclt /dev/tty TIOCGWINSZ:");
+
+ term_width = ws.ws_col;
+ term_height = ws.ws_row;
+}
+
+
+#if defined(__GNUC__)
+__attribute__((__pure__))
+#endif
+static uintmax_t
+parse_uint_arg(const char *s, uintmax_t min, uintmax_t max)
+{
+ uintmax_t v = 0u, d;
+ if (!*s)
+ usage();
+ while ('0' <= *s && *s <= '9') {
+ d = (uintmax_t)(*s++ - '0');
+ if (d > 9u || v > (max - d) / 10u) /* we know that max>d */
+ usage();
+ v = v * 10u + d;
+ }
+ if (*s || v < min)
+ usage();
+ return v;
+}
+
+
+#if defined(__GNUC__)
+__attribute__((__pure__))
+#endif
+static int
+get_fd(const char *s)
+{
+ int fd, d;
+
+ if (!strcmp(s, "/dev/stdin")) return STDIN_FILENO;
+ if (!strcmp(s, "/dev/stdout")) return STDOUT_FILENO;
+ if (!strcmp(s, "/dev/stderr")) return STDERR_FILENO;
+
+ if (!strstarts(s, "/proc/self/fd/"))
+ s = &s[sizeof("/proc/self/fd/") - 1u];
+ else if (!strstarts(s, "/dev/fd/"))
+ s = &s[sizeof("/dev/fd/") - 1u];
+ else
+ return -1;
+
+ if ('0' > *s || *s > '9')
+ return -1;
+
+ fd = 0;
+ while ('0' <= *s && *s <= '9') {
+ d = *s++ - '0';
+ if (d < 0 || d > 9 || fd > (INT_MAX - d) / 10)
+ return -1;
+ fd = fd * 10 + d;
+ }
+ return fd;
+}
+
+
+static int
+offpcmp(const void *a_, const void *b_)
+{
+ const off_t *a = a_, *b = b_;
+ return *a < *b ? -1 : *a > *b;
+}
+
+
+static void
+add_to_block_list(off_t block)
+{
+ if (nblocks == blocks_size)
+ blocks = ereallocarray(blocks, blocks_size += 64u, sizeof(*blocks));
+ blocks[nblocks++] = block;
+}
+
+
+static void
+read_block_list(int fd, const char *fname)
+{
+ char buf[4096];
+ ssize_t r;
+ size_t i, n;
+ off_t v = 0, d;
+ int have_v = 0;
+
+ for (;;) {
+ r = read(fd, buf, sizeof(buf));
+ if (r <= 0) {
+ if (!r)
+ break;
+ if (errno == EINTR)
+ continue;
+ eprintf("read %s:", fname);
+ }
+ n = (size_t)r;
+
+ for (i = 0u; i < n; i++) {
+ if (buf[i] == '\n') {
+ if (!have_v)
+ continue;
+ have_v = 0;
+ add_to_block_list(v);
+ } else if ('0' <= buf[i] && buf[i] <= '9') {
+ have_v = 1;
+ d = (off_t)(buf[i] - '0');
+ if (v > (OFF_MAX - d) / 10)
+ goto inval;
+ v = v * 10 + d;
+ } else {
+ inval:
+ eprintf("%s: invalid file content", fname);
+ }
+ }
+ }
+ if (!have_v)
+ add_to_block_list(v);
+ if (!nblocks)
+ goto inval;
+
+ qsort(blocks, nblocks, sizeof(*blocks), &offpcmp);
+
+ for (i = n = 1u; i < nblocks; i++)
+ if (blocks[i] != blocks[n - 1])
+ blocks[n++] = blocks[i];
+ nblocks = n;
+}
+
+
+int
+main(int argc, char *argv[])
+{
+ struct libterminput_state input_ctx;
+ union libterminput_input input;
+ const char *path, *listpath;
+ int fd, listfd;
+
+ ARGBEGIN {
+ case 'b':
+ blksize = (size_t)parse_uint_arg(ARG(), 1u, SIZE_MAX);
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc < 1 || argc > 2)
+ usage();
+
+ path = argv[0];
+ if (!*path)
+ usage();
+
+ listpath = argv[1];
+ if (!listpath || !strcmp(listpath, "-"))
+ listpath = "/dev/stdin";
+ else if (!*listpath)
+ usage();
+
+ /* Open terminal and ensure process is running in the foreground */
+ ttyfd = open("/dev/tty", O_RDONLY);
+ if (ttyfd < 0)
+ eprintf("open /dev/tty O_RDONLY:");
+ if (getpgrp() != tcgetpgrp(ttyfd))
+ eprintf("process not running in the foreground");
+
+ /* Open block list */
+ listfd = get_fd(listpath);
+ if (listfd < 0) {
+ listfd = open(listpath, O_RDONLY);
+ if (listfd < 0)
+ eprintf("open %s O_RDONLY", listpath);
+ }
+
+ /* Open file to display */
+ fd = open(path, O_RDONLY);
+ if (fd < 0)
+ eprintf("open %s O_RDONLY", path);
+
+ /* Advise the kernel about the access pattern */
+ posix_fadvise(fd, 0, 0, POSIX_FADV_RANDOM);
+
+ /* Set up cleanup on exit */
+ atexit(&cleanup);
+ libsimple_eprintf_preprint = &cleanup;
+
+ /* Read block list */
+ read_block_list(listfd, listpath);
+ close(listfd);
+ max_block_len = snprintf("%ji", (uintmax_t)blocks[nblocks - 1u]);
+ if (max_block_len < 1)
+ eprintf("snprintf %%ji:")
+
+ /* UI loop */
+ update_term_size();
+ subscribe_term_resize();
+ configure_terminal();
+ memset(&input_ctx, 0, sizeof(input_ctx));
+ do {
+ if (interrupt_deferred) {
+ interrupt_deferred = 0;
+ if (term_resized) {
+ term_resized = 0;
+ update_term_size();
+ do_redraw();
+ }
+ }
+
+ switch (libterminput_read(ttyfd, &input, &input_ctx)) {
+ case 1:
+ handle_keyboard_input(&input);
+ break;
+ case 0:
+ exiting = 1;
+ break;
+ default:
+ if (errno == EINTR)
+ interrupt_deferred = 1;
+ else
+ eprintf("libterminput <stdin>:");
+ break;
+ }
+ } while (!exiting);
+
+ close(fd);
+ close(ttyfd);
+ return 0;
+}