*/
//config:config LESS
-//config: bool "less"
+//config: bool "less (15 kb)"
//config: default y
//config: help
//config: 'less' is a pager, meaning that it displays text files. It possesses
//config: this option makes less perform a last-ditch effort to find it:
//config: position cursor to 999,999 and ask terminal to report real
//config: cursor position using "ESC [ 6 n" escape sequence, then read stdin.
-//config:
//config: This is not clean but helps a lot on serial lines and such.
//config:
//config:config FEATURE_LESS_DASHCMD
//config: less itself ('-' keyboard command).
//config:
//config:config FEATURE_LESS_LINENUMS
-//config: bool "Enable dynamic switching of line numbers"
+//config: bool "Enable -N (dynamic switching of line numbers)"
//config: default y
//config: depends on FEATURE_LESS_DASHCMD
-//config: help
-//config: Enables "-N" command.
+
+//applet:IF_LESS(APPLET(less, BB_DIR_USR_BIN, BB_SUID_DROP))
+
+//kbuild:lib-$(CONFIG_LESS) += less.o
//usage:#define less_trivial_usage
//usage: "[-E" IF_FEATURE_LESS_REGEXP("I")IF_FEATURE_LESS_FLAGS("Mm")
#include <sched.h> /* sched_yield() */
#include "libbb.h"
+#include "common_bufsiz.h"
#if ENABLE_FEATURE_LESS_REGEXP
#include "xregex.h"
#endif
enum { pattern_valid = 0 };
#endif
-enum {
- READING_FILE = -1,
- READING_STDIN = -2
-};
-
struct globals {
int cur_fline; /* signed */
int kbd_fd; /* fd to get input from */
+ int kbd_fd_orig_flags;
int less_gets_pos;
/* last position in last line, taking into account tabs */
size_t last_line_pos;
char *filename;
char **files;
#if ENABLE_FEATURE_LESS_FLAGS
- int num_lines; /* input source if < 0, line count if >= 0 */
+ int num_lines; /* a flag if < 0, line count if >= 0 */
+# define REOPEN_AND_COUNT (-1)
+# define REOPEN_STDIN (-2)
+# define NOT_REGULAR_FILE (-3)
#endif
#if ENABLE_FEATURE_LESS_MARKS
unsigned num_marks;
/* flines[] are lines read from stdin, each in malloc'ed buffer.
* Line numbers are stored as uint32_t prepended to each line.
* Pointer is adjusted so that flines[i] points directly past
- * line number. Accesor: */
+ * line number. Accessor: */
#define MEMPTR(p) ((char*)(p) - 4)
#define LINENO(p) (*(uint32_t*)((p) - 4))
static void less_exit(int code)
{
set_tty_cooked();
+ if (!(G.kbd_fd_orig_flags & O_NONBLOCK))
+ ndelay_off(kbd_fd);
clear_line();
if (code < 0)
kill_myself_with_sig(- code); /* does not return */
#define fill_match_lines(pos) ((void)0)
#endif
+static int at_end(void)
+{
+ return (option_mask32 & FLAG_S)
+ ? !(cur_fline <= max_fline &&
+ max_lineno > LINENO(flines[cur_fline]) + max_displayed_line)
+ : !(max_fline > cur_fline + max_displayed_line);
+}
+
/* Devilishly complex routine.
*
* Has to deal with EOF and EPIPE on input,
*/
static void read_lines(void)
{
-#define readbuf bb_common_bufsiz1
char *current_line, *p;
int w = width;
char last_terminated = terminated;
unsigned old_max_fline = max_fline;
#endif
+#define readbuf bb_common_bufsiz1
+ setup_common_bufsiz();
+
/* (careful: max_fline can be -1) */
if (max_fline + 1 > MAXLINES)
return;
if (option_mask32 & FLAG_N)
w -= 8;
- p = current_line = ((char*)xmalloc(w + 4)) + 4;
+ p = current_line = ((char*)xmalloc(w + 5)) + 4;
if (!last_terminated) {
const char *cp = flines[max_fline];
p = stpcpy(p, cp);
time_t t;
errno = 0;
- eof_error = safe_read(STDIN_FILENO, readbuf, sizeof(readbuf));
+ eof_error = safe_read(STDIN_FILENO, readbuf, COMMON_BUFSIZE);
if (errno != EAGAIN)
break;
t = time(NULL);
new_last_line_pos += 7;
new_last_line_pos &= (~7);
}
- if ((int)new_last_line_pos >= w)
+ if ((int)new_last_line_pos > w)
break;
last_line_pos = new_last_line_pos;
}
eof_error = 0; /* Pretend we saw EOF */
break;
}
- if (!(option_mask32 & FLAG_S)
- ? (max_fline > cur_fline + max_displayed_line)
- : (max_fline >= cur_fline
- && max_lineno > LINENO(flines[cur_fline]) + max_displayed_line)
- ) {
+ if (!at_end()) {
#if !ENABLE_FEATURE_LESS_REGEXP
break;
#else
break;
}
max_fline++;
- current_line = ((char*)xmalloc(w + 4)) + 4;
+ current_line = ((char*)xmalloc(w + 5)) + 4;
p = current_line;
last_line_pos = 0;
} /* end of "read lines until we reach cur_fline" loop */
static void update_num_lines(void)
{
int count, fd;
+ struct stat stbuf;
ssize_t len, i;
char buf[4096];
- struct stat stbuf;
- if (num_lines == READING_FILE) {
+ /* only do this for regular files */
+ if (num_lines == REOPEN_AND_COUNT || num_lines == REOPEN_STDIN) {
count = 0;
- fd = open(filename, O_RDONLY);
- if (fd < 0)
- goto skip;
- if (fstat(fd, &stbuf) != 0 || !S_ISREG(stbuf.st_mode))
+ fd = open("/proc/self/fd/0", O_RDONLY);
+ if (fd < 0 && num_lines == REOPEN_AND_COUNT) {
+ /* "filename" is valid only if REOPEN_AND_COUNT */
+ fd = open(filename, O_RDONLY);
+ }
+ if (fd < 0) {
+ /* somebody stole my file! */
+ num_lines = NOT_REGULAR_FILE;
+ return;
+ }
+ if (fstat(fd, &stbuf) != 0 || !S_ISREG(stbuf.st_mode)) {
+ num_lines = NOT_REGULAR_FILE;
goto do_close;
+ }
while ((len = safe_read(fd, buf, sizeof(buf))) > 0) {
for (i = 0; i < len; ++i) {
if (buf[i] == '\n' && ++count == MAXLINES)
num_lines = count;
do_close:
close(fd);
- skip: ;
}
}
if (num_lines >= 0)
printf("/%i", num_lines);
- if (cur_fline >= (int)(max_fline - max_displayed_line)) {
+ if (at_end()) {
printf(" (END)");
if (num_files > 1 && current_file != num_files)
printf(" - next: %s", files[current_file]);
#endif
clear_line();
- if (cur_fline && cur_fline < (int)(max_fline - max_displayed_line)) {
+ if (cur_fline && !at_end()) {
bb_putchar(':');
return;
}
print_hilite(p);
}
-static void cap_cur_fline(int nlines)
-{
- int diff;
- if (cur_fline < 0)
- cur_fline = 0;
- if (cur_fline + max_displayed_line > max_fline + TILDES) {
- cur_fline -= nlines;
- if (cur_fline < 0)
- cur_fline = 0;
- diff = max_fline - (cur_fline + max_displayed_line) + TILDES;
- /* As the number of lines requested was too large, we just move
- * to the end of the file */
- if (diff > 0)
- cur_fline += diff;
- }
-}
-
static const char controls[] ALIGN1 =
/* NUL: never encountered; TAB: not converted */
/**/"\x01\x02\x03\x04\x05\x06\x07\x08" "\x0a\x0b\x0c\x0d\x0e\x0f"
char *growline;
regmatch_t match_structs;
- char buf[width];
+ char buf[width+1];
const char *str = line;
char *p = buf;
size_t n;
static void print_ascii(const char *str)
{
- char buf[width];
+ char buf[width+1];
char *p;
size_t n;
buffer_print();
}
+/* move cur_fline to a given line number, reading lines if necessary */
+static void goto_lineno(int target)
+{
+ if (target <= 0 ) {
+ cur_fline = 0;
+ }
+ else if (target > LINENO(flines[cur_fline])) {
+ retry:
+ while (LINENO(flines[cur_fline]) != target && cur_fline < max_fline)
+ ++cur_fline;
+ /* target not reached but more input is available */
+ if (LINENO(flines[cur_fline]) != target && eof_error > 0) {
+ read_lines();
+ goto retry;
+ }
+ }
+ else {
+ /* search backwards through already-read lines */
+ while (LINENO(flines[cur_fline]) != target && cur_fline > 0)
+ --cur_fline;
+ }
+}
+
+static void cap_cur_fline(void)
+{
+ if ((option_mask32 & FLAG_S)) {
+ if (cur_fline > max_fline)
+ cur_fline = max_fline;
+ if (LINENO(flines[cur_fline]) + max_displayed_line > max_lineno + TILDES) {
+ goto_lineno(max_lineno - max_displayed_line + TILDES);
+ read_lines();
+ }
+ }
+ else {
+ if (cur_fline + max_displayed_line > max_fline + TILDES)
+ cur_fline = max_fline - max_displayed_line + TILDES;
+ if (cur_fline < 0)
+ cur_fline = 0;
+ }
+}
+
/* Move the buffer up and down in the file in order to scroll */
static void buffer_down(int nlines)
{
- cur_fline += nlines;
+ if ((option_mask32 & FLAG_S))
+ goto_lineno(LINENO(flines[cur_fline]) + nlines);
+ else
+ cur_fline += nlines;
read_lines();
- cap_cur_fline(nlines);
+ cap_cur_fline();
buffer_fill_and_print();
}
static void buffer_up(int nlines)
{
- cur_fline -= nlines;
- if (cur_fline < 0) cur_fline = 0;
+ if ((option_mask32 & FLAG_S)) {
+ goto_lineno(LINENO(flines[cur_fline]) - nlines);
+ }
+ else {
+ cur_fline -= nlines;
+ if (cur_fline < 0)
+ cur_fline = 0;
+ }
read_lines();
buffer_fill_and_print();
}
-static void buffer_line(int linenum)
+/* display a given line where the argument can be either an index into
+ * the flines array or a line number */
+static void buffer_to_line(int linenum, int is_lineno)
{
- if (linenum < 0)
- linenum = 0;
- cur_fline = linenum;
+ if (linenum <= 0)
+ cur_fline = 0;
+ else if (is_lineno)
+ goto_lineno(linenum);
+ else
+ cur_fline = linenum;
read_lines();
- if (linenum + max_displayed_line > max_fline)
- linenum = max_fline - max_displayed_line + TILDES;
- if (linenum < 0)
- linenum = 0;
- cur_fline = linenum;
+ cap_cur_fline();
buffer_fill_and_print();
}
+static void buffer_line(int linenum)
+{
+ buffer_to_line(linenum, FALSE);
+}
+
+static void buffer_lineno(int lineno)
+{
+ buffer_to_line(lineno, TRUE);
+}
+
static void open_file_and_read_lines(void)
{
if (filename) {
xmove_fd(xopen(filename, O_RDONLY), STDIN_FILENO);
+#if ENABLE_FEATURE_LESS_FLAGS
+ num_lines = REOPEN_AND_COUNT;
+#endif
} else {
/* "less" with no arguments in argv[] */
/* For status line only */
filename = xstrdup(bb_msg_standard_input);
+#if ENABLE_FEATURE_LESS_FLAGS
+ num_lines = REOPEN_STDIN;
+#endif
}
readpos = 0;
readeof = 0;
max_fline = -1;
cur_fline = 0;
max_lineno = 0;
-#if ENABLE_FEATURE_LESS_FLAGS
- num_lines = filename ? READING_FILE : READING_STDIN;
-#endif
open_file_and_read_lines();
#if ENABLE_FEATURE_LESS_ASK_TERMINAL
if (G.winsize_err)
*/
rd = 1;
/* Are we interested in stdin? */
-//TODO: reuse code for determining this
- if (!(option_mask32 & FLAG_S)
- ? !(max_fline > cur_fline + max_displayed_line)
- : !(max_fline >= cur_fline
- && max_lineno > LINENO(flines[cur_fline]) + max_displayed_line)
- ) {
+ if (at_end()) {
if (eof_error > 0) /* did NOT reach eof yet */
rd = 0; /* yes, we are interested in stdin */
}
buffer_up(num);
break;
case 'g': case '<': case 'G': case '>':
- cur_fline = num + max_displayed_line;
- read_lines();
- buffer_line(num - 1);
+ buffer_lineno(num - 1);
break;
case 'p': case '%':
- num = num * (max_fline / 100); /* + max_fline / 2; */
- cur_fline = num + max_displayed_line;
- read_lines();
- buffer_line(num);
+#if ENABLE_FEATURE_LESS_FLAGS
+ update_num_lines();
+ num = num * (num_lines > 0 ? num_lines : max_lineno) / 100;
+#else
+ num = num * max_lineno / 100;
+#endif
+ buffer_lineno(num);
break;
#if ENABLE_FEATURE_LESS_REGEXP
case 'n':
static void match_right_bracket(char bracket)
{
- unsigned i;
+ unsigned i = cur_fline;
- if (strchr(flines[cur_fline], bracket) == NULL) {
+ if (i >= max_fline
+ || strchr(flines[i], bracket) == NULL
+ ) {
print_statusline("No bracket in top line");
return;
}
+
bracket = opp_bracket(bracket);
- for (i = cur_fline + 1; i < max_fline; i++) {
+ for (; i < max_fline; i++) {
if (strchr(flines[i], bracket) != NULL) {
- buffer_line(i);
+ /*
+ * Line with matched right bracket becomes
+ * last visible line
+ */
+ buffer_line(i - max_displayed_line);
return;
}
}
static void match_left_bracket(char bracket)
{
- int i;
+ int i = cur_fline + max_displayed_line;
- if (strchr(flines[cur_fline + max_displayed_line], bracket) == NULL) {
+ if (i >= max_fline
+ || strchr(flines[i], bracket) == NULL
+ ) {
print_statusline("No bracket in bottom line");
return;
}
bracket = opp_bracket(bracket);
- for (i = cur_fline + max_displayed_line; i >= 0; i--) {
+ for (; i >= 0; i--) {
if (strchr(flines[i], bracket) != NULL) {
+ /*
+ * Line with matched left bracket becomes
+ * first visible line
+ */
buffer_line(i);
return;
}
/* Some versions of less can survive w/o controlling tty,
* try to do the same. This also allows to specify an alternative
* tty via "less 1<>TTY".
- * We don't try to use STDOUT_FILENO directly,
+ *
+ * We prefer not to use STDOUT_FILENO directly,
* since we want to set this fd to non-blocking mode,
- * and not bother with restoring it on exit.
+ * and not interfere with other processes which share stdout with us.
*/
tty_name = xmalloc_ttyname(STDOUT_FILENO);
if (tty_name) {
/* Try controlling tty */
try_ctty:
tty_fd = open(CURRENT_TTY, O_RDONLY);
- if (tty_fd < 0)
- return bb_cat(argv);
+ if (tty_fd < 0) {
+ /* If all else fails, less 481 uses stdout. Mimic that */
+ tty_fd = STDOUT_FILENO;
+ }
}
- ndelay_on(tty_fd);
+ G.kbd_fd_orig_flags = ndelay_on(tty_fd);
kbd_fd = tty_fd; /* save in a global */
tcgetattr(kbd_fd, &term_orig);