source: MondoRescue/branches/stable/mindi-busybox/miscutils/less.c@ 1770

Last change on this file since 1770 was 1770, checked in by Bruno Cornec, 16 years ago
  • Better output for mindi-busybox revision
  • Remove dummy file created on NFS - report from Arnaud Tiger <arnaud.tiger_at_hp.com>
  • strace useful for debug
  • fix new versions for pb (2.0.0 for mindi and 1.7.2 for mindi-busybox)
  • fix build process for mindi-busybox + options used in that version (dd for label-partitions-as-necessary)
  • fix typo in label-partitions-as-necessary which doesn't seem to work
  • Update to busybox 1.7.2
  • perl is now required at restore time to support uuid swap partitions (and will be used for many other thigs

in the future for sure)

  • next mindi version will be 2.0.0 due to all the changes made in it (udev may break working distros)
  • small optimization in mindi on keyboard handling (one single find instead of multiple)
  • better interaction for USB device when launching mindi manually
  • attempt to automatically guess block disk size for ramdisk
  • fix typos in bkphw
  • Fix the remaining problem with UUID support for swap partitions
  • Updates mondoarchive man page for USB support
  • Adds preliminary Hardware support to mindi (Proliant SSSTK)
  • Tries to add udev support also for rhel4
  • Fix UUID support which was still broken.
  • Be conservative in test for the start-nfs script
  • Update config file for mindi-busybox for 1.7.2 migration
  • Try to run around a busybox bug (1.2.2 pb on inexistant links)
  • Add build content for mindi-busybox in pb
  • Remove distributions content for mindi-busybox
  • Fix a warning on inexistant raidtab
  • Solve problem on tmpfs in restore init (Problem of inexistant symlink and busybox)
  • Create MONDO_CACHE and use it everywhere + creation at start
  • Really never try to eject a USB device
  • Fix a issue with &> usage (replaced with 1> and 2>)
  • Adds magic file to depllist in order to have file working + ldd which helps for debugging issues
  • tty modes correct to avoid sh error messages
  • Use ext3 normally and not ext2 instead
  • USB device should be corrected after reading (take 1st part)
  • Adds a mount_USB_here function derived from mount_CDROM_here
  • usb detection place before /dev detection in device name at restore time
  • Fix when restoring from USB: media is asked in interactive mode
  • Adds USB support for mondorestore
  • mount_cdrom => mount_media
  • elilo.efi is now searched throughout /boot/efi and not in a fixed place as there is no standard
  • untar-and-softlink => untar (+ interface change)
  • suppress useless softlinks creation/removal in boot process
  • avoids udevd messages on groups
  • Increase # of disks to 99 as in mindi at restore time (should be a conf file parameter)
  • skip existing big file creation
  • seems to work correctly for USB mindi boot
  • Adds group and tty link to udev conf
  • Always load usb-torage (even 2.6) to initiate USB bus discovery
  • Better printing of messages
  • Attempt to fix a bug in supporting OpenSusE 10.3 kernel for initramfs (mindi may now use multiple regex for kernel initrd detection)
  • Links were not correctly done as non relative for modules in mindi
  • exclusion of modules denied now works
  • Also create modules in their ordinary place, so that classical modprobe works + copy modules.dep
  • Fix bugs for DENY_MODS handling
  • Add device /dev/console for udev
  • ide-generic should now really be excluded
  • Fix a bug in major number for tty
  • If udev then adds modprobe/insmod to rootfs
  • tty0 is also cretaed with udev
  • ide-generic put rather in DENY_MODS
  • udevd remove from deplist s handled in mindi directly
  • better default for mindi when using --usb
  • Handles dynamically linked busybox (in case we want to use it soon ;-)
  • Adds fixed devices to create for udev
  • ide-generic should not be part of the initrd when using libata v2
  • support a dynamically linked udev (case on Ubuntu 7.10 and Mandriva 2008.0 so should be quite generic) This will give incitation to move to dyn. linked binaries in the initrd which will help for other tasks (ia6 4)
  • Improvement in udev support (do not use cl options not available in busybox)
  • Udev in mindi
    • auto creation of the right links at boot time with udev-links.conf(from Mandriva 2008.0)
    • rework startup of udev as current makes kernel crash (from Mandriva 2008.0)
    • add support for 64 bits udev
  • Try to render MyInsmod silent at boot time
  • Adds udev support (mandatory for newest distributions to avoid remapping of devices in a different way as on the original system)
  • We also need vaft format support for USB boot
  • Adds libusual support (Ubuntu 7.10 needs it for USB)
  • Improve Ubuntu/Debian keyboard detection and support
  • pbinit adapted to new pb (0.8.10). Filtering of docs done in it
  • Suppress some mondo warnings and errors on USB again
  • Tries to fix lack of files in deb mindi package
  • Verify should now work for USB devices
  • More log/mesages improvement for USB support
  • - Supress g_erase_tmpdir_and_scratchdir
  • Improve some log messages for USB support
  • Try to improve install in mindi to avoid issues with isolinux.cfg not installed vene if in the pkg :-(
  • Improve mindi-busybox build
  • In conformity with pb 0.8.9
  • Add support for Ubuntu 7.10 in build process
  • Add USB Key button to Menu UI (CD streamer removed)
  • Attempt to fix error messages on tmp/scratch files at the end by removing those dir at the latest possible.
  • Fix a bug linked to the size of the -E param which could be used (Arnaud Tiger/René Ribaud).
  • Integrate ~/.pbrc content into mondorescue.pb (required project-builder >= 0.8.7)
  • Put mondorescue in conformity with new pb filtering rules
  • Add USB support at restore time (no test done yet). New start-usb script PB varibale added where useful
  • Unmounting USB device before removal of temporary scratchdir
  • Stil refining USB copy back to mondo (one command was not executed)
  • No need to have the image subdor in the csratchdir when USB.
  • umount the USB partition before attempting to use it
  • Remove useless copy from mindi to mondo at end of USB handling

(risky merge, we are raising the limits of 2 diverging branches. The status of stable is not completely sure as such. Will need lots of tests, but it's not yet done :-()
(merge -r1692:1769 $SVN_M/branches/2.2.5)

File size: 30.9 KB
Line 
1/* vi: set sw=4 ts=4: */
2/*
3 * Mini less implementation for busybox
4 *
5 * Copyright (C) 2005 by Rob Sullivan <cogito.ergo.cogito@gmail.com>
6 *
7 * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
8 */
9
10/*
11 * TODO:
12 * - Add more regular expression support - search modifiers, certain matches, etc.
13 * - Add more complex bracket searching - currently, nested brackets are
14 * not considered.
15 * - Add support for "F" as an input. This causes less to act in
16 * a similar way to tail -f.
17 * - Allow horizontal scrolling.
18 *
19 * Notes:
20 * - the inp file pointer is used so that keyboard input works after
21 * redirected input has been read from stdin
22 */
23
24#include <sched.h> /* sched_yield() */
25
26#include "libbb.h"
27#if ENABLE_FEATURE_LESS_REGEXP
28#include "xregex.h"
29#endif
30
31/* FIXME: currently doesn't work right */
32#undef ENABLE_FEATURE_LESS_FLAGCS
33#define ENABLE_FEATURE_LESS_FLAGCS 0
34
35/* The escape codes for highlighted and normal text */
36#define HIGHLIGHT "\033[7m"
37#define NORMAL "\033[0m"
38/* The escape code to clear the screen */
39#define CLEAR "\033[H\033[J"
40/* The escape code to clear to end of line */
41#define CLEAR_2_EOL "\033[K"
42
43/* These are the escape sequences corresponding to special keys */
44enum {
45 REAL_KEY_UP = 'A',
46 REAL_KEY_DOWN = 'B',
47 REAL_KEY_RIGHT = 'C',
48 REAL_KEY_LEFT = 'D',
49 REAL_PAGE_UP = '5',
50 REAL_PAGE_DOWN = '6',
51 REAL_KEY_HOME = '7', // vt100? linux vt? or what?
52 REAL_KEY_END = '8',
53 REAL_KEY_HOME_ALT = '1', // ESC [1~ (vt100? linux vt? or what?)
54 REAL_KEY_END_ALT = '4', // ESC [4~
55 REAL_KEY_HOME_XTERM = 'H',
56 REAL_KEY_END_XTERM = 'F',
57
58/* These are the special codes assigned by this program to the special keys */
59 KEY_UP = 20,
60 KEY_DOWN = 21,
61 KEY_RIGHT = 22,
62 KEY_LEFT = 23,
63 PAGE_UP = 24,
64 PAGE_DOWN = 25,
65 KEY_HOME = 26,
66 KEY_END = 27,
67
68/* Absolute max of lines eaten */
69 MAXLINES = CONFIG_FEATURE_LESS_MAXLINES,
70
71/* This many "after the end" lines we will show (at max) */
72 TILDES = 1,
73};
74
75/* Command line options */
76enum {
77 FLAG_E = 1,
78 FLAG_M = 1 << 1,
79 FLAG_m = 1 << 2,
80 FLAG_N = 1 << 3,
81 FLAG_TILDE = 1 << 4,
82/* hijack command line options variable for internal state vars */
83 LESS_STATE_MATCH_BACKWARDS = 1 << 15,
84};
85
86#if !ENABLE_FEATURE_LESS_REGEXP
87enum { pattern_valid = 0 };
88#endif
89
90struct globals {
91 int cur_fline; /* signed */
92 int kbd_fd; /* fd to get input from */
93/* last position in last line, taking into account tabs */
94 size_t linepos;
95 unsigned max_displayed_line;
96 unsigned max_fline;
97 unsigned max_lineno; /* this one tracks linewrap */
98 unsigned width;
99 ssize_t eof_error; /* eof if 0, error if < 0 */
100 size_t readpos;
101 size_t readeof;
102 const char **buffer;
103 const char **flines;
104 const char *empty_line_marker;
105 unsigned num_files;
106 unsigned current_file;
107 char *filename;
108 char **files;
109#if ENABLE_FEATURE_LESS_MARKS
110 unsigned num_marks;
111 unsigned mark_lines[15][2];
112#endif
113#if ENABLE_FEATURE_LESS_REGEXP
114 unsigned *match_lines;
115 int match_pos; /* signed! */
116 unsigned num_matches;
117 regex_t pattern;
118 smallint pattern_valid;
119#endif
120 smallint terminated;
121 struct termios term_orig, term_less;
122};
123#define G (*ptr_to_globals)
124#define cur_fline (G.cur_fline )
125#define kbd_fd (G.kbd_fd )
126#define linepos (G.linepos )
127#define max_displayed_line (G.max_displayed_line)
128#define max_fline (G.max_fline )
129#define max_lineno (G.max_lineno )
130#define width (G.width )
131#define eof_error (G.eof_error )
132#define readpos (G.readpos )
133#define readeof (G.readeof )
134#define buffer (G.buffer )
135#define flines (G.flines )
136#define empty_line_marker (G.empty_line_marker )
137#define num_files (G.num_files )
138#define current_file (G.current_file )
139#define filename (G.filename )
140#define files (G.files )
141#define num_marks (G.num_marks )
142#define mark_lines (G.mark_lines )
143#if ENABLE_FEATURE_LESS_REGEXP
144#define match_lines (G.match_lines )
145#define match_pos (G.match_pos )
146#define num_matches (G.num_matches )
147#define pattern (G.pattern )
148#define pattern_valid (G.pattern_valid )
149#endif
150#define terminated (G.terminated )
151#define term_orig (G.term_orig )
152#define term_less (G.term_less )
153#define INIT_G() do { \
154 PTR_TO_GLOBALS = xzalloc(sizeof(G)); \
155 empty_line_marker = "~"; \
156 num_files = 1; \
157 current_file = 1; \
158 eof_error = 1; \
159 terminated = 1; \
160 } while (0)
161
162/* Reset terminal input to normal */
163static void set_tty_cooked(void)
164{
165 fflush(stdout);
166 tcsetattr(kbd_fd, TCSANOW, &term_orig);
167}
168
169/* Exit the program gracefully */
170static void less_exit(int code)
171{
172 /* TODO: We really should save the terminal state when we start,
173 * and restore it when we exit. Less does this with the
174 * "ti" and "te" termcap commands; can this be done with
175 * only termios.h? */
176 putchar('\n');
177 fflush_stdout_and_exit(code);
178}
179
180/* Move the cursor to a position (x,y), where (0,0) is the
181 top-left corner of the console */
182static void move_cursor(int line, int row)
183{
184 printf("\033[%u;%uH", line, row);
185}
186
187static void clear_line(void)
188{
189 printf("\033[%u;0H" CLEAR_2_EOL, max_displayed_line + 2);
190}
191
192static void print_hilite(const char *str)
193{
194 printf(HIGHLIGHT"%s"NORMAL, str);
195}
196
197static void print_statusline(const char *str)
198{
199 clear_line();
200 printf(HIGHLIGHT"%.*s"NORMAL, width - 1, str);
201}
202
203#if ENABLE_FEATURE_LESS_REGEXP
204static void fill_match_lines(unsigned pos);
205#else
206#define fill_match_lines(pos) ((void)0)
207#endif
208
209/* Devilishly complex routine.
210 *
211 * Has to deal with EOF and EPIPE on input,
212 * with line wrapping, with last line not ending in '\n'
213 * (possibly not ending YET!), with backspace and tabs.
214 * It reads input again if last time we got an EOF (thus supporting
215 * growing files) or EPIPE (watching output of slow process like make).
216 *
217 * Variables used:
218 * flines[] - array of lines already read. Linewrap may cause
219 * one source file line to occupy several flines[n].
220 * flines[max_fline] - last line, possibly incomplete.
221 * terminated - 1 if flines[max_fline] is 'terminated'
222 * (if there was '\n' [which isn't stored itself, we just remember
223 * that it was seen])
224 * max_lineno - last line's number, this one doesn't increment
225 * on line wrap, only on "real" new lines.
226 * readbuf[0..readeof-1] - small preliminary buffer.
227 * readbuf[readpos] - next character to add to current line.
228 * linepos - screen line position of next char to be read
229 * (takes into account tabs and backspaces)
230 * eof_error - < 0 error, == 0 EOF, > 0 not EOF/error
231 */
232static void read_lines(void)
233{
234#define readbuf bb_common_bufsiz1
235 char *current_line, *p;
236 USE_FEATURE_LESS_REGEXP(unsigned old_max_fline = max_fline;)
237 int w = width;
238 char last_terminated = terminated;
239
240 if (option_mask32 & FLAG_N)
241 w -= 8;
242
243 current_line = xmalloc(w);
244 p = current_line;
245 max_fline += last_terminated;
246 if (!last_terminated) {
247 const char *cp = flines[max_fline];
248 if (option_mask32 & FLAG_N)
249 cp += 8;
250 strcpy(current_line, cp);
251 p += strlen(current_line);
252 /* linepos is still valid from previous read_lines() */
253 } else {
254 linepos = 0;
255 }
256
257 while (1) {
258 again:
259 *p = '\0';
260 terminated = 0;
261 while (1) {
262 char c;
263 /* if no unprocessed chars left, eat more */
264 if (readpos >= readeof) {
265 smallint yielded = 0;
266
267 ndelay_on(0);
268 read_again:
269 eof_error = safe_read(0, readbuf, sizeof(readbuf));
270 readpos = 0;
271 readeof = eof_error;
272 if (eof_error < 0) {
273 if (errno == EAGAIN && !yielded) {
274 /* We can hit EAGAIN while searching for regexp match.
275 * Yield is not 100% reliable solution in general,
276 * but for less it should be good enough -
277 * we give stdin supplier some CPU time to produce
278 * more input. We do it just once.
279 * Currently, we do not stop when we found the Nth
280 * occurrence we were looking for. We read till end
281 * (or double EAGAIN). TODO? */
282 sched_yield();
283 yielded = 1;
284 goto read_again;
285 }
286 readeof = 0;
287 if (errno != EAGAIN)
288 print_statusline("read error");
289 }
290 ndelay_off(0);
291
292 if (eof_error <= 0) {
293 goto reached_eof;
294 }
295 }
296 c = readbuf[readpos];
297 /* backspace? [needed for manpages] */
298 /* <tab><bs> is (a) insane and */
299 /* (b) harder to do correctly, so we refuse to do it */
300 if (c == '\x8' && linepos && p[-1] != '\t') {
301 readpos++; /* eat it */
302 linepos--;
303 /* was buggy (p could end up <= current_line)... */
304 *--p = '\0';
305 continue;
306 }
307 {
308 size_t new_linepos = linepos + 1;
309 if (c == '\t') {
310 new_linepos += 7;
311 new_linepos &= (~7);
312 }
313 if (new_linepos >= w)
314 break;
315 linepos = new_linepos;
316 }
317 /* ok, we will eat this char */
318 readpos++;
319 if (c == '\n') {
320 terminated = 1;
321 linepos = 0;
322 break;
323 }
324 /* NUL is substituted by '\n'! */
325 if (c == '\0') c = '\n';
326 *p++ = c;
327 *p = '\0';
328 }
329 /* Corner case: linewrap with only "" wrapping to next line */
330 /* Looks ugly on screen, so we do not store this empty line */
331 if (!last_terminated && !current_line[0]) {
332 last_terminated = 1;
333 max_lineno++;
334 goto again;
335 }
336 reached_eof:
337 last_terminated = terminated;
338 flines = xrealloc(flines, (max_fline+1) * sizeof(char *));
339 if (option_mask32 & FLAG_N) {
340 /* Width of 7 preserves tab spacing in the text */
341 flines[max_fline] = xasprintf(
342 (max_lineno <= 9999999) ? "%7u %s" : "%07u %s",
343 max_lineno % 10000000, current_line);
344 free(current_line);
345 if (terminated)
346 max_lineno++;
347 } else {
348 flines[max_fline] = xrealloc(current_line, strlen(current_line)+1);
349 }
350 if (max_fline >= MAXLINES) {
351 eof_error = 0; /* Pretend we saw EOF */
352 break;
353 }
354 if (max_fline > cur_fline + max_displayed_line)
355 break;
356 if (eof_error <= 0) {
357 if (eof_error < 0 && errno == EAGAIN) {
358 /* not yet eof or error, reset flag (or else
359 * we will hog CPU - select() will return
360 * immediately */
361 eof_error = 1;
362 }
363 break;
364 }
365 max_fline++;
366 current_line = xmalloc(w);
367 p = current_line;
368 linepos = 0;
369 }
370 fill_match_lines(old_max_fline);
371#undef readbuf
372}
373
374#if ENABLE_FEATURE_LESS_FLAGS
375/* Interestingly, writing calc_percent as a function saves around 32 bytes
376 * on my build. */
377static int calc_percent(void)
378{
379 unsigned p = (100 * (cur_fline+max_displayed_line+1) + max_fline/2) / (max_fline+1);
380 return p <= 100 ? p : 100;
381}
382
383/* Print a status line if -M was specified */
384static void m_status_print(void)
385{
386 int percentage;
387
388 clear_line();
389 printf(HIGHLIGHT"%s", filename);
390 if (num_files > 1)
391 printf(" (file %i of %i)", current_file, num_files);
392 printf(" lines %i-%i/%i ",
393 cur_fline + 1, cur_fline + max_displayed_line + 1,
394 max_fline + 1);
395 if (cur_fline >= max_fline - max_displayed_line) {
396 printf("(END)"NORMAL);
397 if (num_files > 1 && current_file != num_files)
398 printf(HIGHLIGHT" - next: %s"NORMAL, files[current_file]);
399 return;
400 }
401 percentage = calc_percent();
402 printf("%i%%"NORMAL, percentage);
403}
404#endif
405
406/* Print the status line */
407static void status_print(void)
408{
409 const char *p;
410
411 /* Change the status if flags have been set */
412#if ENABLE_FEATURE_LESS_FLAGS
413 if (option_mask32 & (FLAG_M|FLAG_m)) {
414 m_status_print();
415 return;
416 }
417 /* No flags set */
418#endif
419
420 clear_line();
421 if (cur_fline && cur_fline < max_fline - max_displayed_line) {
422 putchar(':');
423 return;
424 }
425 p = "(END)";
426 if (!cur_fline)
427 p = filename;
428 if (num_files > 1) {
429 printf(HIGHLIGHT"%s (file %i of %i)"NORMAL,
430 p, current_file, num_files);
431 return;
432 }
433 print_hilite(p);
434}
435
436static void cap_cur_fline(int nlines)
437{
438 int diff;
439 if (cur_fline < 0)
440 cur_fline = 0;
441 if (cur_fline + max_displayed_line > max_fline + TILDES) {
442 cur_fline -= nlines;
443 if (cur_fline < 0)
444 cur_fline = 0;
445 diff = max_fline - (cur_fline + max_displayed_line) + TILDES;
446 /* As the number of lines requested was too large, we just move
447 to the end of the file */
448 if (diff > 0)
449 cur_fline += diff;
450 }
451}
452
453static const char controls[] ALIGN1 =
454 /* NUL: never encountered; TAB: not converted */
455 /**/"\x01\x02\x03\x04\x05\x06\x07\x08" "\x0a\x0b\x0c\x0d\x0e\x0f"
456 "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"
457 "\x7f\x9b"; /* DEL and infamous Meta-ESC :( */
458static const char ctrlconv[] ALIGN1 =
459 /* '\n': it's a former NUL - subst with '@', not 'J' */
460 "\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x40\x4b\x4c\x4d\x4e\x4f"
461 "\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f";
462
463#if ENABLE_FEATURE_LESS_REGEXP
464static void print_found(const char *line)
465{
466 int match_status;
467 int eflags;
468 char *growline;
469 regmatch_t match_structs;
470
471 char buf[width];
472 const char *str = line;
473 char *p = buf;
474 size_t n;
475
476 while (*str) {
477 n = strcspn(str, controls);
478 if (n) {
479 if (!str[n]) break;
480 memcpy(p, str, n);
481 p += n;
482 str += n;
483 }
484 n = strspn(str, controls);
485 memset(p, '.', n);
486 p += n;
487 str += n;
488 }
489 strcpy(p, str);
490
491 /* buf[] holds quarantined version of str */
492
493 /* Each part of the line that matches has the HIGHLIGHT
494 and NORMAL escape sequences placed around it.
495 NB: we regex against line, but insert text
496 from quarantined copy (buf[]) */
497 str = buf;
498 growline = NULL;
499 eflags = 0;
500 goto start;
501
502 while (match_status == 0) {
503 char *new = xasprintf("%s%.*s"HIGHLIGHT"%.*s"NORMAL,
504 growline ? : "",
505 match_structs.rm_so, str,
506 match_structs.rm_eo - match_structs.rm_so,
507 str + match_structs.rm_so);
508 free(growline); growline = new;
509 str += match_structs.rm_eo;
510 line += match_structs.rm_eo;
511 eflags = REG_NOTBOL;
512 start:
513 /* Most of the time doesn't find the regex, optimize for that */
514 match_status = regexec(&pattern, line, 1, &match_structs, eflags);
515 }
516
517 if (!growline) {
518 printf(CLEAR_2_EOL"%s\n", str);
519 return;
520 }
521 printf(CLEAR_2_EOL"%s%s\n", growline, str);
522 free(growline);
523}
524#else
525void print_found(const char *line);
526#endif
527
528static void print_ascii(const char *str)
529{
530 char buf[width];
531 char *p;
532 size_t n;
533
534 printf(CLEAR_2_EOL);
535 while (*str) {
536 n = strcspn(str, controls);
537 if (n) {
538 if (!str[n]) break;
539 printf("%.*s", (int) n, str);
540 str += n;
541 }
542 n = strspn(str, controls);
543 p = buf;
544 do {
545 if (*str == 0x7f)
546 *p++ = '?';
547 else if (*str == (char)0x9b)
548 /* VT100's CSI, aka Meta-ESC. Who's inventor? */
549 /* I want to know who committed this sin */
550 *p++ = '{';
551 else
552 *p++ = ctrlconv[(unsigned char)*str];
553 str++;
554 } while (--n);
555 *p = '\0';
556 print_hilite(buf);
557 }
558 puts(str);
559}
560
561/* Print the buffer */
562static void buffer_print(void)
563{
564 int i;
565
566 move_cursor(0, 0);
567 for (i = 0; i <= max_displayed_line; i++)
568 if (pattern_valid)
569 print_found(buffer[i]);
570 else
571 print_ascii(buffer[i]);
572 status_print();
573}
574
575static void buffer_fill_and_print(void)
576{
577 int i;
578 for (i = 0; i <= max_displayed_line && cur_fline + i <= max_fline; i++) {
579 buffer[i] = flines[cur_fline + i];
580 }
581 for (; i <= max_displayed_line; i++) {
582 buffer[i] = empty_line_marker;
583 }
584 buffer_print();
585}
586
587/* Move the buffer up and down in the file in order to scroll */
588static void buffer_down(int nlines)
589{
590 cur_fline += nlines;
591 read_lines();
592 cap_cur_fline(nlines);
593 buffer_fill_and_print();
594}
595
596static void buffer_up(int nlines)
597{
598 cur_fline -= nlines;
599 if (cur_fline < 0) cur_fline = 0;
600 read_lines();
601 buffer_fill_and_print();
602}
603
604static void buffer_line(int linenum)
605{
606 if (linenum < 0)
607 linenum = 0;
608 cur_fline = linenum;
609 read_lines();
610 if (linenum + max_displayed_line > max_fline)
611 linenum = max_fline - max_displayed_line + TILDES;
612 if (linenum < 0)
613 linenum = 0;
614 cur_fline = linenum;
615 buffer_fill_and_print();
616}
617
618static void open_file_and_read_lines(void)
619{
620 if (filename) {
621 int fd = xopen(filename, O_RDONLY);
622 dup2(fd, 0);
623 if (fd) close(fd);
624 } else {
625 /* "less" with no arguments in argv[] */
626 /* For status line only */
627 filename = xstrdup(bb_msg_standard_input);
628 }
629 readpos = 0;
630 readeof = 0;
631 linepos = 0;
632 terminated = 1;
633 read_lines();
634}
635
636/* Reinitialize everything for a new file - free the memory and start over */
637static void reinitialize(void)
638{
639 int i;
640
641 if (flines) {
642 for (i = 0; i <= max_fline; i++)
643 free((void*)(flines[i]));
644 free(flines);
645 flines = NULL;
646 }
647
648 max_fline = -1;
649 cur_fline = 0;
650 max_lineno = 0;
651 open_file_and_read_lines();
652 buffer_fill_and_print();
653}
654
655static void getch_nowait(char* input, int sz)
656{
657 ssize_t rd;
658 fd_set readfds;
659 again:
660 fflush(stdout);
661
662 /* NB: select returns whenever read will not block. Therefore:
663 * (a) with O_NONBLOCK'ed fds select will return immediately
664 * (b) if eof is reached, select will also return
665 * because read will immediately return 0 bytes.
666 * Even if select says that input is available, read CAN block
667 * (switch fd into O_NONBLOCK'ed mode to avoid it)
668 */
669 FD_ZERO(&readfds);
670 if (max_fline <= cur_fline + max_displayed_line
671 && eof_error > 0 /* did NOT reach eof yet */
672 ) {
673 /* We are interested in stdin */
674 FD_SET(0, &readfds);
675 }
676 FD_SET(kbd_fd, &readfds);
677 tcsetattr(kbd_fd, TCSANOW, &term_less);
678 select(kbd_fd + 1, &readfds, NULL, NULL, NULL);
679
680 input[0] = '\0';
681 ndelay_on(kbd_fd);
682 rd = read(kbd_fd, input, sz);
683 ndelay_off(kbd_fd);
684 if (rd < 0) {
685 /* No keyboard input, but we have input on stdin! */
686 if (errno != EAGAIN) /* Huh?? */
687 return;
688 read_lines();
689 buffer_fill_and_print();
690 goto again;
691 }
692}
693
694/* Grab a character from input without requiring the return key. If the
695 * character is ASCII \033, get more characters and assign certain sequences
696 * special return codes. Note that this function works best with raw input. */
697static int less_getch(void)
698{
699 char input[16];
700 unsigned i;
701 again:
702 memset(input, 0, sizeof(input));
703 getch_nowait(input, sizeof(input));
704
705 /* Detect escape sequences (i.e. arrow keys) and handle
706 * them accordingly */
707 if (input[0] == '\033' && input[1] == '[') {
708 set_tty_cooked();
709 i = input[2] - REAL_KEY_UP;
710 if (i < 4)
711 return 20 + i;
712 i = input[2] - REAL_PAGE_UP;
713 if (i < 4)
714 return 24 + i;
715 if (input[2] == REAL_KEY_HOME_XTERM)
716 return KEY_HOME;
717 if (input[2] == REAL_KEY_HOME_ALT)
718 return KEY_HOME;
719 if (input[2] == REAL_KEY_END_XTERM)
720 return KEY_END;
721 if (input[2] == REAL_KEY_END_ALT)
722 return KEY_END;
723 return 0;
724 }
725 /* Reject almost all control chars */
726 i = input[0];
727 if (i < ' ' && i != 0x0d && i != 8) goto again;
728 set_tty_cooked();
729 return i;
730}
731
732static char* less_gets(int sz)
733{
734 char c;
735 int i = 0;
736 char *result = xzalloc(1);
737 while (1) {
738 fflush(stdout);
739
740 /* I be damned if I know why is it needed *repeatedly*,
741 * but it is needed. Is it because of stdio? */
742 tcsetattr(kbd_fd, TCSANOW, &term_less);
743
744 c = '\0';
745 read(kbd_fd, &c, 1);
746 if (c == 0x0d)
747 return result;
748 if (c == 0x7f)
749 c = 8;
750 if (c == 8 && i) {
751 printf("\x8 \x8");
752 i--;
753 }
754 if (c < ' ')
755 continue;
756 if (i >= width - sz - 1)
757 continue; /* len limit */
758 putchar(c);
759 result[i++] = c;
760 result = xrealloc(result, i+1);
761 result[i] = '\0';
762 }
763}
764
765static void examine_file(void)
766{
767 print_statusline("Examine: ");
768 free(filename);
769 filename = less_gets(sizeof("Examine: ")-1);
770 /* files start by = argv. why we assume that argv is infinitely long??
771 files[num_files] = filename;
772 current_file = num_files + 1;
773 num_files++; */
774 files[0] = filename;
775 num_files = current_file = 1;
776 reinitialize();
777}
778
779/* This function changes the file currently being paged. direction can be one of the following:
780 * -1: go back one file
781 * 0: go to the first file
782 * 1: go forward one file */
783static void change_file(int direction)
784{
785 if (current_file != ((direction > 0) ? num_files : 1)) {
786 current_file = direction ? current_file + direction : 1;
787 free(filename);
788 filename = xstrdup(files[current_file - 1]);
789 reinitialize();
790 } else {
791 print_statusline(direction > 0 ? "No next file" : "No previous file");
792 }
793}
794
795static void remove_current_file(void)
796{
797 int i;
798
799 if (num_files < 2)
800 return;
801
802 if (current_file != 1) {
803 change_file(-1);
804 for (i = 3; i <= num_files; i++)
805 files[i - 2] = files[i - 1];
806 num_files--;
807 } else {
808 change_file(1);
809 for (i = 2; i <= num_files; i++)
810 files[i - 2] = files[i - 1];
811 num_files--;
812 current_file--;
813 }
814}
815
816static void colon_process(void)
817{
818 int keypress;
819
820 /* Clear the current line and print a prompt */
821 print_statusline(" :");
822
823 keypress = less_getch();
824 switch (keypress) {
825 case 'd':
826 remove_current_file();
827 break;
828 case 'e':
829 examine_file();
830 break;
831#if ENABLE_FEATURE_LESS_FLAGS
832 case 'f':
833 m_status_print();
834 break;
835#endif
836 case 'n':
837 change_file(1);
838 break;
839 case 'p':
840 change_file(-1);
841 break;
842 case 'q':
843 less_exit(0);
844 break;
845 case 'x':
846 change_file(0);
847 break;
848 }
849}
850
851#if ENABLE_FEATURE_LESS_REGEXP
852static void normalize_match_pos(int match)
853{
854 if (match >= num_matches)
855 match = num_matches - 1;
856 if (match < 0)
857 match = 0;
858 match_pos = match;
859}
860
861static void goto_match(int match)
862{
863 int sv;
864
865 if (!pattern_valid)
866 return;
867 if (match < 0)
868 match = 0;
869 sv = cur_fline;
870 /* Try to find next match if eof isn't reached yet */
871 if (match >= num_matches && eof_error > 0) {
872 cur_fline = MAXLINES; /* look as far as needed */
873 read_lines();
874 }
875 if (num_matches) {
876 cap_cur_fline(cur_fline);
877 normalize_match_pos(match);
878 buffer_line(match_lines[match_pos]);
879 } else {
880 cur_fline = sv;
881 print_statusline("No matches found");
882 }
883}
884
885static void fill_match_lines(unsigned pos)
886{
887 if (!pattern_valid)
888 return;
889 /* Run the regex on each line of the current file */
890 while (pos <= max_fline) {
891 /* If this line matches */
892 if (regexec(&pattern, flines[pos], 0, NULL, 0) == 0
893 /* and we didn't match it last time */
894 && !(num_matches && match_lines[num_matches-1] == pos)
895 ) {
896 match_lines = xrealloc(match_lines, (num_matches+1) * sizeof(int));
897 match_lines[num_matches++] = pos;
898 }
899 pos++;
900 }
901}
902
903static void regex_process(void)
904{
905 char *uncomp_regex, *err;
906
907 /* Reset variables */
908 free(match_lines);
909 match_lines = NULL;
910 match_pos = 0;
911 num_matches = 0;
912 if (pattern_valid) {
913 regfree(&pattern);
914 pattern_valid = 0;
915 }
916
917 /* Get the uncompiled regular expression from the user */
918 clear_line();
919 putchar((option_mask32 & LESS_STATE_MATCH_BACKWARDS) ? '?' : '/');
920 uncomp_regex = less_gets(1);
921 if (!uncomp_regex[0]) {
922 free(uncomp_regex);
923 buffer_print();
924 return;
925 }
926
927 /* Compile the regex and check for errors */
928 err = regcomp_or_errmsg(&pattern, uncomp_regex, 0);
929 free(uncomp_regex);
930 if (err) {
931 print_statusline(err);
932 free(err);
933 return;
934 }
935
936 pattern_valid = 1;
937 match_pos = 0;
938 fill_match_lines(0);
939 while (match_pos < num_matches) {
940 if (match_lines[match_pos] > cur_fline)
941 break;
942 match_pos++;
943 }
944 if (option_mask32 & LESS_STATE_MATCH_BACKWARDS)
945 match_pos--;
946
947 /* It's possible that no matches are found yet.
948 * goto_match() will read input looking for match,
949 * if needed */
950 goto_match(match_pos);
951}
952#endif
953
954static void number_process(int first_digit)
955{
956 int i = 1;
957 int num;
958 char num_input[sizeof(int)*4]; /* more than enough */
959 char keypress;
960
961 num_input[0] = first_digit;
962
963 /* Clear the current line, print a prompt, and then print the digit */
964 clear_line();
965 printf(":%c", first_digit);
966
967 /* Receive input until a letter is given */
968 while (i < sizeof(num_input)-1) {
969 num_input[i] = less_getch();
970 if (!num_input[i] || !isdigit(num_input[i]))
971 break;
972 putchar(num_input[i]);
973 i++;
974 }
975
976 /* Take the final letter out of the digits string */
977 keypress = num_input[i];
978 num_input[i] = '\0';
979 num = bb_strtou(num_input, NULL, 10);
980 /* on format error, num == -1 */
981 if (num < 1 || num > MAXLINES) {
982 buffer_print();
983 return;
984 }
985
986 /* We now know the number and the letter entered, so we process them */
987 switch (keypress) {
988 case KEY_DOWN: case 'z': case 'd': case 'e': case ' ': case '\015':
989 buffer_down(num);
990 break;
991 case KEY_UP: case 'b': case 'w': case 'y': case 'u':
992 buffer_up(num);
993 break;
994 case 'g': case '<': case 'G': case '>':
995 cur_fline = num + max_displayed_line;
996 read_lines();
997 buffer_line(num - 1);
998 break;
999 case 'p': case '%':
1000 num = num * (max_fline / 100); /* + max_fline / 2; */
1001 cur_fline = num + max_displayed_line;
1002 read_lines();
1003 buffer_line(num);
1004 break;
1005#if ENABLE_FEATURE_LESS_REGEXP
1006 case 'n':
1007 goto_match(match_pos + num);
1008 break;
1009 case '/':
1010 option_mask32 &= ~LESS_STATE_MATCH_BACKWARDS;
1011 regex_process();
1012 break;
1013 case '?':
1014 option_mask32 |= LESS_STATE_MATCH_BACKWARDS;
1015 regex_process();
1016 break;
1017#endif
1018 }
1019}
1020
1021#if ENABLE_FEATURE_LESS_FLAGCS
1022static void flag_change(void)
1023{
1024 int keypress;
1025
1026 clear_line();
1027 putchar('-');
1028 keypress = less_getch();
1029
1030 switch (keypress) {
1031 case 'M':
1032 option_mask32 ^= FLAG_M;
1033 break;
1034 case 'm':
1035 option_mask32 ^= FLAG_m;
1036 break;
1037 case 'E':
1038 option_mask32 ^= FLAG_E;
1039 break;
1040 case '~':
1041 option_mask32 ^= FLAG_TILDE;
1042 break;
1043 }
1044}
1045
1046static void show_flag_status(void)
1047{
1048 int keypress;
1049 int flag_val;
1050
1051 clear_line();
1052 putchar('_');
1053 keypress = less_getch();
1054
1055 switch (keypress) {
1056 case 'M':
1057 flag_val = option_mask32 & FLAG_M;
1058 break;
1059 case 'm':
1060 flag_val = option_mask32 & FLAG_m;
1061 break;
1062 case '~':
1063 flag_val = option_mask32 & FLAG_TILDE;
1064 break;
1065 case 'N':
1066 flag_val = option_mask32 & FLAG_N;
1067 break;
1068 case 'E':
1069 flag_val = option_mask32 & FLAG_E;
1070 break;
1071 default:
1072 flag_val = 0;
1073 break;
1074 }
1075
1076 clear_line();
1077 printf(HIGHLIGHT"The status of the flag is: %u"NORMAL, flag_val != 0);
1078}
1079#endif
1080
1081static void save_input_to_file(void)
1082{
1083 const char *msg = "";
1084 char *current_line;
1085 int i;
1086 FILE *fp;
1087
1088 print_statusline("Log file: ");
1089 current_line = less_gets(sizeof("Log file: ")-1);
1090 if (strlen(current_line) > 0) {
1091 fp = fopen(current_line, "w");
1092 if (!fp) {
1093 msg = "Error opening log file";
1094 goto ret;
1095 }
1096 for (i = 0; i <= max_fline; i++)
1097 fprintf(fp, "%s\n", flines[i]);
1098 fclose(fp);
1099 msg = "Done";
1100 }
1101 ret:
1102 print_statusline(msg);
1103 free(current_line);
1104}
1105
1106#if ENABLE_FEATURE_LESS_MARKS
1107static void add_mark(void)
1108{
1109 int letter;
1110
1111 print_statusline("Mark: ");
1112 letter = less_getch();
1113
1114 if (isalpha(letter)) {
1115 /* If we exceed 15 marks, start overwriting previous ones */
1116 if (num_marks == 14)
1117 num_marks = 0;
1118
1119 mark_lines[num_marks][0] = letter;
1120 mark_lines[num_marks][1] = cur_fline;
1121 num_marks++;
1122 } else {
1123 print_statusline("Invalid mark letter");
1124 }
1125}
1126
1127static void goto_mark(void)
1128{
1129 int letter;
1130 int i;
1131
1132 print_statusline("Go to mark: ");
1133 letter = less_getch();
1134 clear_line();
1135
1136 if (isalpha(letter)) {
1137 for (i = 0; i <= num_marks; i++)
1138 if (letter == mark_lines[i][0]) {
1139 buffer_line(mark_lines[i][1]);
1140 break;
1141 }
1142 if (num_marks == 14 && letter != mark_lines[14][0])
1143 print_statusline("Mark not set");
1144 } else
1145 print_statusline("Invalid mark letter");
1146}
1147#endif
1148
1149#if ENABLE_FEATURE_LESS_BRACKETS
1150static char opp_bracket(char bracket)
1151{
1152 switch (bracket) {
1153 case '{': case '[':
1154 return bracket + 2;
1155 case '(':
1156 return ')';
1157 case '}': case ']':
1158 return bracket - 2;
1159 case ')':
1160 return '(';
1161 }
1162 return 0;
1163}
1164
1165static void match_right_bracket(char bracket)
1166{
1167 int bracket_line = -1;
1168 int i;
1169
1170 if (strchr(flines[cur_fline], bracket) == NULL) {
1171 print_statusline("No bracket in top line");
1172 return;
1173 }
1174 for (i = cur_fline + 1; i < max_fline; i++) {
1175 if (strchr(flines[i], opp_bracket(bracket)) != NULL) {
1176 bracket_line = i;
1177 break;
1178 }
1179 }
1180 if (bracket_line == -1)
1181 print_statusline("No matching bracket found");
1182 buffer_line(bracket_line - max_displayed_line);
1183}
1184
1185static void match_left_bracket(char bracket)
1186{
1187 int bracket_line = -1;
1188 int i;
1189
1190 if (strchr(flines[cur_fline + max_displayed_line], bracket) == NULL) {
1191 print_statusline("No bracket in bottom line");
1192 return;
1193 }
1194
1195 for (i = cur_fline + max_displayed_line; i >= 0; i--) {
1196 if (strchr(flines[i], opp_bracket(bracket)) != NULL) {
1197 bracket_line = i;
1198 break;
1199 }
1200 }
1201 if (bracket_line == -1)
1202 print_statusline("No matching bracket found");
1203 buffer_line(bracket_line);
1204}
1205#endif /* FEATURE_LESS_BRACKETS */
1206
1207static void keypress_process(int keypress)
1208{
1209 switch (keypress) {
1210 case KEY_DOWN: case 'e': case 'j': case 0x0d:
1211 buffer_down(1);
1212 break;
1213 case KEY_UP: case 'y': case 'k':
1214 buffer_up(1);
1215 break;
1216 case PAGE_DOWN: case ' ': case 'z':
1217 buffer_down(max_displayed_line + 1);
1218 break;
1219 case PAGE_UP: case 'w': case 'b':
1220 buffer_up(max_displayed_line + 1);
1221 break;
1222 case 'd':
1223 buffer_down((max_displayed_line + 1) / 2);
1224 break;
1225 case 'u':
1226 buffer_up((max_displayed_line + 1) / 2);
1227 break;
1228 case KEY_HOME: case 'g': case 'p': case '<': case '%':
1229 buffer_line(0);
1230 break;
1231 case KEY_END: case 'G': case '>':
1232 cur_fline = MAXLINES;
1233 read_lines();
1234 buffer_line(cur_fline);
1235 break;
1236 case 'q': case 'Q':
1237 less_exit(0);
1238 break;
1239#if ENABLE_FEATURE_LESS_MARKS
1240 case 'm':
1241 add_mark();
1242 buffer_print();
1243 break;
1244 case '\'':
1245 goto_mark();
1246 buffer_print();
1247 break;
1248#endif
1249 case 'r': case 'R':
1250 buffer_print();
1251 break;
1252 /*case 'R':
1253 full_repaint();
1254 break;*/
1255 case 's':
1256 save_input_to_file();
1257 break;
1258 case 'E':
1259 examine_file();
1260 break;
1261#if ENABLE_FEATURE_LESS_FLAGS
1262 case '=':
1263 m_status_print();
1264 break;
1265#endif
1266#if ENABLE_FEATURE_LESS_REGEXP
1267 case '/':
1268 option_mask32 &= ~LESS_STATE_MATCH_BACKWARDS;
1269 regex_process();
1270 break;
1271 case 'n':
1272 goto_match(match_pos + 1);
1273 break;
1274 case 'N':
1275 goto_match(match_pos - 1);
1276 break;
1277 case '?':
1278 option_mask32 |= LESS_STATE_MATCH_BACKWARDS;
1279 regex_process();
1280 break;
1281#endif
1282#if ENABLE_FEATURE_LESS_FLAGCS
1283 case '-':
1284 flag_change();
1285 buffer_print();
1286 break;
1287 case '_':
1288 show_flag_status();
1289 break;
1290#endif
1291#if ENABLE_FEATURE_LESS_BRACKETS
1292 case '{': case '(': case '[':
1293 match_right_bracket(keypress);
1294 break;
1295 case '}': case ')': case ']':
1296 match_left_bracket(keypress);
1297 break;
1298#endif
1299 case ':':
1300 colon_process();
1301 break;
1302 }
1303
1304 if (isdigit(keypress))
1305 number_process(keypress);
1306}
1307
1308static void sig_catcher(int sig ATTRIBUTE_UNUSED)
1309{
1310 set_tty_cooked();
1311 exit(1);
1312}
1313
1314int less_main(int argc, char **argv);
1315int less_main(int argc, char **argv)
1316{
1317 int keypress;
1318
1319 INIT_G();
1320
1321 /* TODO: -x: do not interpret backspace, -xx: tab also */
1322 /* -xxx: newline also */
1323 /* -w N: assume width N (-xxx -w 32: hex viewer of sorts) */
1324 getopt32(argv, "EMmN~");
1325 argc -= optind;
1326 argv += optind;
1327 num_files = argc;
1328 files = argv;
1329
1330 /* Another popular pager, most, detects when stdout
1331 * is not a tty and turns into cat. This makes sense. */
1332 if (!isatty(STDOUT_FILENO))
1333 return bb_cat(argv);
1334 kbd_fd = open(CURRENT_TTY, O_RDONLY);
1335 if (kbd_fd < 0)
1336 return bb_cat(argv);
1337
1338 if (!num_files) {
1339 if (isatty(STDIN_FILENO)) {
1340 /* Just "less"? No args and no redirection? */
1341 bb_error_msg("missing filename");
1342 bb_show_usage();
1343 }
1344 } else
1345 filename = xstrdup(files[0]);
1346
1347 get_terminal_width_height(kbd_fd, &width, &max_displayed_line);
1348 /* 20: two tabstops + 4 */
1349 if (width < 20 || max_displayed_line < 3)
1350 bb_error_msg_and_die("too narrow here");
1351 max_displayed_line -= 2;
1352
1353 buffer = xmalloc((max_displayed_line+1) * sizeof(char *));
1354 if (option_mask32 & FLAG_TILDE)
1355 empty_line_marker = "";
1356
1357 tcgetattr(kbd_fd, &term_orig);
1358 signal(SIGTERM, sig_catcher);
1359 signal(SIGINT, sig_catcher);
1360 term_less = term_orig;
1361 term_less.c_lflag &= ~(ICANON | ECHO);
1362 term_less.c_iflag &= ~(IXON | ICRNL);
1363 /*term_less.c_oflag &= ~ONLCR;*/
1364 term_less.c_cc[VMIN] = 1;
1365 term_less.c_cc[VTIME] = 0;
1366
1367 /* Want to do it just once, but it doesn't work, */
1368 /* so we are redoing it (see code above). Mystery... */
1369 /*tcsetattr(kbd_fd, TCSANOW, &term_less);*/
1370
1371 reinitialize();
1372 while (1) {
1373 keypress = less_getch();
1374 keypress_process(keypress);
1375 }
1376}
Note: See TracBrowser for help on using the repository browser.