diff options
Diffstat (limited to '')
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | Makefile | 22 | ||||
| -rw-r--r-- | TODO | 2 | ||||
| -rw-r--r-- | blkseekr.c | 63 | ||||
| -rw-r--r-- | blkshowr.c | 402 | ||||
| -rw-r--r-- | config.mk | 2 |
6 files changed, 465 insertions, 27 deletions
@@ -13,3 +13,4 @@ *.gcno *.gcda /blkseekr +/blkshowr @@ -4,32 +4,38 @@ CONFIGFILE = config.mk include $(CONFIGFILE) OBJ =\ - blkseekr.o + blkseekr.o\ + blkshowr.o HDR = -all: blkseekr +all: blkseekr blkshowr $(OBJ): $(HDR) .c.o: $(CC) -c -o $@ $< $(CFLAGS) $(CPPFLAGS) -blkseekr: $(OBJ) - $(CC) -o $@ $(OBJ) $(LDFLAGS) +blkseekr: blkseekr.o + $(CC) -o $@ $@.o $(LDFLAGS) -install: blkseekr +blkshowr: blkshowr.o + $(CC) -o $@ $@.o $(LDFLAGS) + +install: blkseekr blkshowr mkdir -p -- "$(DESTDIR)$(PREFIX)/bin" mkdir -p -- "$(DESTDIR)$(MANPREFIX)/man1/" - cp -- blkseekr "$(DESTDIR)$(PREFIX)/bin/" -# TODO cp -- blkseekr.1 "$(DESTDIR)$(MANPREFIX)/man1/" + cp -- blkseekr blkshowr "$(DESTDIR)$(PREFIX)/bin/" +# TODO cp -- blkseekr.1 blkshowr.1 "$(DESTDIR)$(MANPREFIX)/man1/" uninstall: -rm -f -- "$(DESTDIR)$(PREFIX)/bin/blkseekr" + -rm -f -- "$(DESTDIR)$(PREFIX)/bin/blkshowr" -rm -f -- "$(DESTDIR)$(MANPREFIX)/man1/blkseekr.1" + -rm -f -- "$(DESTDIR)$(MANPREFIX)/man1/blkshowr.1" clean: -rm -f -- *.o *.a *.lo *.su *.so *.so.* *.gch *.gcov *.gcno *.gcda - -rm -f -- blkseekr + -rm -f -- blkseekr blkshowr .SUFFIXES: .SUFFIXES: .o .c @@ -0,0 +1,2 @@ +Add support for NUL bytes in regex (once supported in libautomata) +On SIGINT stop and print last completed block @@ -1,7 +1,7 @@ /* See LICENSE file for copyright and license details. */ -#include <libsimple-arg.h> -#include <libsimple.h> #include <regex.h> +#include <libsimple.h> +#include <libsimple-arg.h> #include <libautomata.h> USAGE("[-b block-size] [-B buffer-size] [-h] " @@ -9,14 +9,6 @@ USAGE("[-b block-size] [-B buffer-size] [-h] " "file [first-block [last-block]]"); -/* - * TODO on SIGINT stop and print last completed block - * TODO show progress - * TODO make program that extracts (binary) or displays (hex + text) the blocks - * TODO add support for NUL support in regex (once supported in libautomata) - */ - - enum automata_type { FIXED, FIXED_ICASE, @@ -49,6 +41,8 @@ static char *blkmarks; static size_t nblkmarks; static size_t low_mark; static size_t high_mark; +static int stdout_is_a_tty; +static int stderr_is_a_tty; static void @@ -146,8 +140,10 @@ analyse_block(char *block, size_t start_end, size_t available) } for (i = 0u; i < nblkmarks; i++) { - printf("Found expression beginning in block %ju\n", (uintmax_t)position / blksize + i); - fflush(stdout); + if (blkmarks[i]) { + printf("%ju%s\n", (uintmax_t)position / blksize + i, stdout_is_a_tty ? "\033[K" : ""); + fflush(stdout); + } } } @@ -272,7 +268,7 @@ parse_uint_arg(const char *s, uintmax_t min, uintmax_t max) d = (uintmax_t)(*s++ - '0'); if (d > 9u || v > (max - d) / 10u) /* we know that max>d */ usage(); - v = v * 10u + v; + v = v * 10u + d; } if (*s || v < min) usage(); @@ -402,7 +398,17 @@ main(int argc, char *argv[]) if (argc < 1 || argc > 3 || have_unused_flags || !nautomata) usage(); + stderr_is_a_tty = isatty(STDERR_FILENO); + if (!stderr_is_a_tty && errno == EBADF) + exit(1); + + stdout_is_a_tty = isatty(STDOUT_FILENO); + if (!stdout_is_a_tty && errno == EBADF) + eprintf("isatty STDOUT_FILENO:"); + path = argv[0]; + if (!*path) + usage(); if (argc >= 2) first_block = (off_t)parse_uint_arg(argv[1], 1u, (uintmax_t)OFF_MAX); @@ -427,11 +433,11 @@ main(int argc, char *argv[]) /* Open file to analyse */ fd = open(path, O_RDONLY); if (fd < 0) - eprintf("open %s O_RDONLY", path); + eprintf("open %s O_RDONLY:", path); /* Advise the kernel about the access pattern */ posix_fadvise(fd, 0, first_block, POSIX_FADV_DONTNEED); - if (last_block >= 0) { + if (last_block > 0) { posix_fadvise(fd, last_block, 0, POSIX_FADV_DONTNEED); posix_fadvise(fd, first_block, last_block, POSIX_FADV_SEQUENTIAL); posix_fadvise(fd, first_block, last_block, POSIX_FADV_NOREUSE); @@ -449,6 +455,11 @@ main(int argc, char *argv[]) /* Skip to first block to analyse */ if (first_block && lseek(fd, first_block, SEEK_SET) == (off_t)-1) { off_t remaining = first_block; + if (stderr_is_a_tty) { + /* TODO show progress */ + fprintf(stderr, "Skipping...\n\033[A"); + fflush(stderr); + } while (remaining) { uintmax_t n = MIN((uintmax_t)remaining, (uintmax_t)bufsize); ssize_t r = read(fd, text, (size_t)n); @@ -466,6 +477,12 @@ main(int argc, char *argv[]) /* Analyse to last block (to end of file if last_block < 0) */ for (; last_block < 0 || first_block < last_block; first_block = data) { + if (stderr_is_a_tty) { + /* TODO improve output */ + fprintf(stderr, "Reading at %ju\033[K\n\033[A", (uintmax_t)position + available); + fflush(stderr); + } + /* Seek to data to find end of hole, and analyse the hole */ if (skip_holes) { data = lseek(fd, first_block, SEEK_DATA); @@ -474,7 +491,7 @@ main(int argc, char *argv[]) if (errno != EBADF) skip_holes = 0; else - eprintf("lseek %s %ji SEEK_DATA", path, (intmax_t)first_block); + eprintf("lseek %s %ji SEEK_DATA:", path, (intmax_t)first_block); } else { available = encountered_hole(text, available, data - first_block); } @@ -489,10 +506,10 @@ main(int argc, char *argv[]) if (errno != EBADF) skip_holes = 0; else - eprintf("lseek %s %ji SEEK_HOLE", path, (intmax_t)data); + eprintf("lseek %s %ji SEEK_HOLE:", path, (intmax_t)data); } else { if (lseek(fd, data, SEEK_SET) == (off_t)-1) - eprintf("lseek %s %ji SEEK_SET", path, (intmax_t)data); + eprintf("lseek %s %ji SEEK_SET:", path, (intmax_t)data); } } else { hole = (off_t)-1; @@ -513,6 +530,11 @@ main(int argc, char *argv[]) } else { break; } + if (stderr_is_a_tty) { + /* TODO improve output */ + fprintf(stderr, "Reading at %ju\033[K\n\033[A", (uintmax_t)position + available); + fflush(stderr); + } r = read(fd, &text[available], n); if (r <= 0) { if (!r) @@ -535,6 +557,11 @@ eof: encountered_eof(text, available); out: + if (stderr_is_a_tty) { + fprintf(stderr, "\033[K"); + fflush(stderr); + } + close(fd); while (nautomata--) { 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; +} @@ -5,4 +5,4 @@ CC = c99 CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_GNU_SOURCE CFLAGS = -LDFLAGS = -lsimple -lautomata +LDFLAGS = -lsimple -lautomata -lterminput |
