/* See LICENSE file for copyright and license details. */ #include #include #include #include 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 :"); break; } } while (!exiting); close(fd); close(ttyfd); return 0; }