From: Jim Warner Date: Sun, 25 Nov 2012 05:00:05 +0000 (-0500) Subject: top: add a flexible 'Inspect' capability X-Git-Tag: v3.3.6~48 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=081fe506f3ab5df958041ff997e4a33615cc85ec;p=procps-ng top: add a flexible 'Inspect' capability This commit introduces an extremely powerful, flexible brand new capability. Now, users can pause the normal iterative display and inspect the contents of any file or output from any script, command, or even pipelines. It's invoked via the 'Y' interactive command which, in turn, is supported with simple user supplied additions as new entries in the top personal configuration file. A separate new 'Inspect' window supports scrolling and searching, similar to the main top display. Except it extends existing 'L'/'&' (locate/locate-next) commands so that an out-of-view match automatically adjusts the horizontal position bringing such data into view. And it provides for multiple successive same line matches. Also, the basic 'more/less' navigation keys are active in this new 'Inspect' window, to ease user transition. There are no program changes required when entries are added to or deleted from the rcfile. And there are no known limits to the complexity of a script, command or pipeline, other than the unidirectional nature imposed by the 'popen' function call which top cannot violate. Since it's impossible to predict exactly what contents will be generated, top treats all output as raw binary data. Any control characters display in '^C' notation while all other unprintable characters show as ''. The biggest problem encountered was with the find/next capability since that strstr guy was really diminished given the possibility that numerous 'strings' could be encountered *within* many of top's raw, binary 'rows'. Oh, and another problem was in maintaining the perfect left & right text justification of this commit message along with all of the commit summaries. Some of those summaries (like this very one) are of course, slightly shorter, to make room for the 'man document' addition. Enjoy! Signed-off-by: Jim Warner --- diff --git a/top/top.c b/top/top.c index 15b9f0af..0e7e0803 100644 --- a/top/top.c +++ b/top/top.c @@ -597,7 +597,7 @@ static void capsmk (WIN_t *q) { if (!capsdone) { STRLCPY(Cap_clr_eol, tIF(clr_eol)) STRLCPY(Cap_clr_scr, tIF(clear_screen)) - // due to leading newline, only 1 function may use this (and carefully) + // due to the leading newline, the following must be used with care snprintf(Cap_nl_clreos, sizeof(Cap_nl_clreos), "\n%s", tIF(clr_eos)); STRLCPY(Cap_curs_huge, tIF(cursor_visible)) STRLCPY(Cap_curs_norm, tIF(cursor_normal)) @@ -739,18 +739,24 @@ static void show_special (int interact, const char *glob) { | capclr_rowhigh, = \007, | | capclr_rownorm }; = \010 [octal!] | +------------------------------------------------------+ */ - /* ( pssst, after adding the termcap transitions, row may ) - ( exceed 300+ bytes, even in an 80x24 terminal window! ) */ - char tmp[SMLBUFSIZ], lin[MEDBUFSIZ], row[LRGBUFSIZ]; + /* ( Pssst, after adding the termcap transitions, row may ) + ( exceed 300+ bytes, even in an 80x24 terminal window! ) + ( And if we're no longer guaranteed lines created only ) + ( by top, we'll need larger buffs plus some protection ) + ( against overrunning them with this 'lin_end - glob'. ) */ + char tmp[LRGBUFSIZ], lin[LRGBUFSIZ], row[ROWMAXSIZ]; char *rp, *lin_end, *sub_beg, *sub_end; int room; // handle multiple lines passed in a bunch while ((lin_end = strchr(glob, '\n'))) { + #define myMIN(a,b) (((a) < (b)) ? (a) : (b)) + size_t lessor = myMIN((size_t)(lin_end - glob), sizeof(lin) -1); + // create a local copy we can extend and otherwise abuse - memcpy(lin, glob, (unsigned)(lin_end - glob)); + memcpy(lin, glob, lessor); // zero terminate this part and prepare to parse substrings - lin[lin_end - glob] = '\0'; + lin[lessor] = '\0'; room = Screen_cols; sub_beg = sub_end = lin; *(rp = row) = '\0'; @@ -765,7 +771,8 @@ static void show_special (int interact, const char *glob) { case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8: *sub_end = '\0'; - snprintf(tmp, sizeof(tmp), "%s%.*s%s", Curwin->captab[ch], room, sub_beg, Caps_off); + snprintf(tmp, sizeof(tmp), "%s%.*s%s" + , Curwin->captab[ch], room, sub_beg, Caps_off); rp = scat(rp, tmp); room -= (sub_end - sub_beg); sub_beg = (sub_end += 2); @@ -780,6 +787,7 @@ static void show_special (int interact, const char *glob) { else PUFF("%s%s\n", row, Caps_endline); glob = ++lin_end; // point to next line (maybe) + #undef myMIN } // end: while 'lines' /* If there's anything left in the glob (by virtue of no trailing '\n'), @@ -817,7 +825,7 @@ static void updt_scroll_msg (void) { , "%%s%s %.*s%s", Caps_off, Screen_cols - 3, tmp2, Cap_clr_eol); } // end: updt_scroll_msg -/*###### Low Level Memory/Keyboard support #############################*/ +/*###### Low Level Memory/Keyboard/File I/O support ####################*/ /* * Handle our own memory stuff without the risk of leaving the @@ -1048,6 +1056,37 @@ static char *linein (const char *prompt) { #undef bufMAX } // end: linein #endif + + + /* + * This routine provides the i/o in support of files whose size + * cannot be determined in advance. Given a stream pointer, he'll + * try to slurp in the whole thing and return a dynamically acquired + * buffer supporting that single string glob. + * + * He always creates a buffer at least READMINSZ big, possibly + * all zeros (an empty string), even if the file wasn't read. */ +static int readfile (FILE *fp, char **baddr, unsigned *bsize, unsigned *bread) { + char chunk[4096*16]; + size_t num; + + *bread = 0; + *bsize = READMINSZ; + *baddr = alloc_c(READMINSZ); + if (fp) { + while (0 < (num = fread(chunk, 1, sizeof(chunk) -1, fp))) { + if (feof(fp) && chunk[num -1]) chunk[num++] = '\0'; + *baddr = alloc_r(*baddr, num + *bsize); + memcpy(*baddr + *bread, chunk, num); + *bread += num; + *bsize += num; + }; + // adjust for the null terminator, which was counted above + if (*bread) --(*bread); + return ferror(fp); + } + return ENOENT; +} // end: readfile /*###### Small Utility routines ########################################*/ @@ -2265,6 +2304,445 @@ static void sysinfo_refresh (int forced) { #endif } // end: sysinfo_refresh +/*###### Inspect Other Output ##########################################*/ + + /* + * HOWTO Extend the top 'inspect' functionality: + * + * To exploit the 'Y' interactive command, one must add entries to + * the top personal configuration file. Such entries simply reflect + * a file to be read or command/pipeline to be executed whose results + * will then be displayed in a separate scrollable window. + * + * Entries beginning with a '#' character are ignored, regardless of + * content. Otherwise they consist of the following 3 elements, each + * of which must be separated by a tab character (thus 2 '\t' total): + * type: literal 'file' or 'pipe' + * name: selection shown on the Inspect screen + * fmts: string representing a path or command + * + * The two types of Inspect entries are not interchangeable. + * Those designated 'file' will be accessed using fopen/fread and must + * reference a single file in the 'fmts' element. Entries specifying + * 'pipe' will employ popen/fread, their 'fmts' element could contain + * many pipelined commands and, none can be interactive. + * + * Here are some examples of both types of inspection entries. + * The first entry will be ignored due to the initial '#' character. + * For clarity, the pseudo tab depictions (^I) are surrounded by an + * extra space but the actual tabs would not be. + * + * # pipe ^I Sockets ^I lsof -n -P -i 2>&1 + * pipe ^I Open Files ^I lsof -P -p %d 2>&1 + * file ^I NUMA Info ^I /proc/%d/numa_maps + * pipe ^I Log ^I tail -n100 /var/log/syslog | sort -Mr + * + * Caution: If the output contains unprintable characters they will + * be displayed in either the ^I notation or hexidecimal form. + * This applies to tab characters as well. So if one wants a more + * accurate display, any tabs should be expanded within the 'fmts'. + * + * The following example takes what could have been a 'file' entry + * but employs a 'pipe' instead so as to expand the tabs. + * + * # next would have contained '\t' ... + * # file ^I ^I /proc/%d/status + * # but this will eliminate embedded '\t' ... + * pipe ^I ^I cat /proc/%d/status | expand - + */ + +static char **Insp_p; // pointers to each line start +static int Insp_nl; // total lines, total Insp_p entries +static char *Insp_buf; // the results from insp_do_file/pipe +static unsigned Insp_bufsz; // allocated size of Insp_buf +static unsigned Insp_bufrd; // bytes actually in Insp_buf +static char *Insp_selname; // the selected label, if anybody cares +static char *Insp_selfmts; // the selected path/command, ditto + + + // Our 'make status line' macro +#define INSP_MKSL(big,txt) { int _sz = big ? Screen_cols : 80; \ + putp(tg2(0, (Msg_row = 3))); \ + PUTT("%s%.*s", Curwin->capclr_hdr, Screen_cols \ + , fmtmk("%-*.*s%s", _sz, _sz, txt, Cap_clr_eol)); \ + putp(Caps_off); } + + // Our 'row length' macro, equivalent to a strlen() call +#define INSP_RLEN(idx) (size_t)(Insp_p[idx +1] - Insp_p[idx] -1) + + // Our 'busy' (wait please) macro +#define INSP_BUSY { INSP_MKSL(0, N_txt(YINSP_workin_txt)); \ + fflush(stdout); } + + + /* + * Establish the number of lines present in the Insp_buf glob plus + * build the all important row start array. It is that array that + * others will rely on since we dare not try to use strlen() on what + * is potentially raw binary data. Who knows what some user might + * name as a file or include in a pipeline (scary, ain't it?). */ +static void insp_cnt_nl (void) { + char *beg = Insp_buf; + char *cur = Insp_buf; + char *end = Insp_buf + Insp_bufrd + 1; + +#ifdef INSP_SAVEBUF +{ + static int n = 1; + char fn[SMLBUFSIZ]; + FILE *fd; + snprintf(fn, sizeof(fn), "%s.Insp_buf.%02d.txt", Myname, n++); + fd = fopen(fn, "w"); + if (fd) { + fwrite(Insp_buf, 1, Insp_bufrd, fd); + fclose(fd); + } +} +#endif + Insp_p = alloc_c(sizeof(char*) * 2); + + for (Insp_nl = 0; beg < end; beg++) { + if (*beg == '\n') { + Insp_p[Insp_nl++] = cur; + // keep our array ahead of next potential need (plus the 2 above) + Insp_p = alloc_r(Insp_p, (sizeof(char*) * (Insp_nl +3))); + cur = beg +1; + } + } + Insp_p[0] = Insp_buf; + Insp_p[Insp_nl++] = cur; + Insp_p[Insp_nl] = end; + if ((end - cur) == 1) // if there's a eof null delimiter, + --Insp_nl; // don't count it as a new line +} // end: insp_cnt_nl + + +#ifndef INSP_OFFDEMO + /* + * The pseudo output DEMO utility. */ +static void insp_do_demo (char *fmts, int pid) { + (void)fmts; (void)pid; + Insp_bufsz = READMINSZ; + Insp_buf = alloc_c(Insp_bufsz); + Insp_bufrd = snprintf(Insp_buf, Insp_bufsz, "%s", N_txt(YINSP_demo04_txt)); + insp_cnt_nl(); +} // end: insp_do_demo +#endif + + + /* + * The generalized FILE utility. */ +static void insp_do_file (char *fmts, int pid) { + char buf[LRGBUFSIZ]; + FILE *fp; + int rc; + + snprintf(buf, sizeof(buf), fmts, pid); + fp = fopen(buf, "r"); + rc = readfile(fp, &Insp_buf, &Insp_bufsz, &Insp_bufrd); + if (fp) fclose(fp); + if (rc) Insp_bufrd = snprintf(Insp_buf, Insp_bufsz, "%s" + , fmtmk(N_fmt(YINSP_failed_fmt), strerror(errno))); + insp_cnt_nl(); +} // end: insp_do_file + + + /* + * The generalized PIPE utility. */ +static void insp_do_pipe (char *fmts, int pid) { + char buf[LRGBUFSIZ]; + FILE *fp; + int rc; + + snprintf(buf, sizeof(buf), fmts, pid); + fp = popen(buf, "r"); + rc = readfile(fp, &Insp_buf, &Insp_bufsz, &Insp_bufrd); + if (fp) pclose(fp); + if (rc) Insp_bufrd = snprintf(Insp_buf, Insp_bufsz, "%s" + , fmtmk(N_fmt(YINSP_failed_fmt), strerror(errno))); + insp_cnt_nl(); +} // end: insp_do_pipe + + + /* + * This guy supports the inspect 'L' and '&' search provisions + * and returns the row and *optimal* col for viewing any match + * ( we'll always opt for left column justification since any ) + * ( preceeding control chars would consume an unknown amount ) */ +static void insp_find (int ch, int *col, int *row) { + #define reDUX (found) ? N_txt(WORD_another_txt) : "" + #define begFS (int)(fnd - Insp_p[i]) + static char str[SCREENMAX]; + static int found; + char *fnd, *p; + int i, x, ccur = *col; + + if ((ch == '&' || ch == 'n') && !str[0]) { + show_msg(N_txt(FIND_no_next_txt)); + return; + } + if (ch == 'L' || ch == '/') { + strcpy(str, linein(N_txt(GET_find_str_txt))); + found = 0; + } + if (str[0]) { + INSP_BUSY; + for (i = *row; i < Insp_nl; ) { + fnd = NULL; // because our glob might + for (x = ccur +1; x < INSP_RLEN(i); x++) { // be raw binary data, we + if (!*(p = Insp_p[i] + x)) // could encounter a '\0' + continue; // in what we view as the + if ((fnd = STRSTR(p, str))) // 'row' -- so we'll have + break; // to search it in chunks + x += strlen(str); // ... + } // and, account for maybe + if (fnd && fnd < Insp_p[i +1]) { // overrunning that 'row' + found = 1; + *row = i; + *col = begFS; + return; + } + ++i; + ccur = 0; + } + show_msg(fmtmk(N_fmt(FIND_no_find_fmt), reDUX, str)); + } + #undef reDUX + #undef begFS +} // end: insp_find + + + /* + * This guy is an insp_view_this() *Helper* function responsible + * for positioning us in both the x/y axes within the former glob + * and displaying a page worth of damages. Along the way, he makes + * sure that any control characters and/or unprintable characters + * use a less-like approach which distinguishes between two forms + * of representation: ^C and . + * + * He also creates a customized status line based on the maximum + * number of digits for the current selection's position so it will + * hopefully serve to inform, not distract by being jumpy. */ +static inline void insp_show_pg (int col, int row, int max) { + #define capNO { if (hicap) { putp(Caps_off); hicap = 0; } } + #define mkCTL { if ((to += 2) <= Screen_cols) \ + PUTT("%s^%c", (!hicap) ? Curwin->capclr_msg : "", uch + '@'); hicap = 1; } + #define mkUNP { if ((to += 4) <= Screen_cols) \ + PUTT("%s<%02X>", (!hicap) ? Curwin->capclr_msg : "", uch); hicap = 1; } + #define mkSTD { capNO; putchar(uch); to++; } + char buf[SMLBUFSIZ]; + int r = snprintf(buf, sizeof(buf), "%d", Insp_nl); + int c = snprintf(buf, sizeof(buf), "%d", col +Screen_cols); + int l = row +1, ls = Insp_nl;; + int hicap = 0; + + if (!Insp_bufrd) l = ls = 0; // for a more honest representation + snprintf(buf, sizeof(buf), N_fmt(YINSP_status_fmt) + , Insp_selname + , r, l, r, ls + , c, col + 1, c, col + Screen_cols + , Insp_bufrd); + INSP_MKSL(0, buf); + + for ( ; max && row < Insp_nl; row++) { + char tline[SCREENMAX]; + size_t fr, to, len; + + capNO; + putp("\n"); + memset(tline, ' ', sizeof(tline)); + len = INSP_RLEN(row); + if (col < len) + memcpy(tline, Insp_p[row] + col, sizeof(tline)); + for (fr = 0, to = 0; fr < len && to < Screen_cols; fr++) { + unsigned char uch = tline[fr]; + if (uch == '\n') break; // a no show (he,he) + if (uch > 126) mkUNP // show as '' + else if (uch < 32) mkCTL // show as '^C' + else mkSTD // a show off (he,he) + } + capNO; + putp(Cap_clr_eol); + --max; + } + if (max) putp(Cap_nl_clreos); + #undef capNO + #undef mkCTL + #undef mkUNP + #undef mkSTD +} // end: insp_show_pg + + + /* + * This guy is responsible for displaying the Insp_buf contents and + * managing all scrolling/locate requests until the user gives up. */ +static int insp_view_this (char *hdr) { +#ifdef INSP_SLIDE_1 + #define hzAMT 1 +#else + #define hzAMT 8 +#endif + #define maxLN (Screen_rows - (Msg_row +1)) + char buf[SMLBUFSIZ]; + int key, curlin, curcol; + + for (curlin = curcol = 0;;) { + if (curcol < 0) curcol = 0; + if (curlin >= Insp_nl) curlin = Insp_nl -1; + if (curlin < 0) curlin = 0; + + putp(Cap_home); + putp(Cap_curs_hide); + show_special(1, fmtmk(N_unq(INSP_hdrview_fmt), hdr)); + insp_show_pg(curcol, curlin, maxLN); + + switch (key = keyin(0)) { + case kbd_ENTER: // must force new keyin() + key = -1; // fall through ! + case kbd_ESC: case 'q': case 0: + putp(Cap_clr_scr); + return key; + case kbd_LEFT: + curcol -= hzAMT; + break; + case kbd_RIGHT: + curcol += hzAMT; + break; + case kbd_UP: + --curlin; + break; + case kbd_DOWN: + ++curlin; + break; + case kbd_PGUP: case 'b': + curlin -= maxLN -1; // keep 1 line for reference + break; + case kbd_PGDN: case kbd_SPACE: + curlin += maxLN -1; // ditto + break; + case kbd_HOME: case 'g': + curcol = curlin = 0; + break; + case kbd_END: case 'G': + curcol = 0; + curlin = Insp_nl - maxLN; + break; + case 'L': case '&': case '/': case 'n': + putp(Cap_curs_norm); + insp_find(key, &curcol, &curlin); + break; + case '=': + snprintf(buf, sizeof(buf), "%s", Insp_selfmts); + INSP_MKSL(1, buf); // show an extended SL + key = keyin(0); + if (!key) return key; // oops, we got signaled + break; + default: // keep gcc happy + break; + } + } + #undef maxLN +} // end: insp_view_this + + + /* + * Our driving table support, the basis for generalized inspection, + * built at startup (if at all) from rcfile or demo entries. */ +struct I_entry { + void (*func)(char *, int); // a pointer to file/pipe/demo function + char *type; // the type of entry ('file' or 'pipe') + char *name; // the selection label for display + char *fmts; // format string to build path or command + int farg; // 1 = '%d' in fmts, 0 = not (future use) + const char *caps; // not really caps, show_special() delim's +}; +struct I_struc { + int demo; // do NOT save table entries in rcfile + int total; // total I_entry table entries + char *raw; // all entries for 'W', incl '#' & blank + struct I_entry *tab; +}; +static struct I_struc Inspect; + + + /* + * This is the main Inspect routine, responsible for: + * 1) validating the passed pid (required, but not always used) + * 2) presenting/establishing the target selection + * 3) arranging to fill Insp_buf (via the Inspect.tab[?].func) + * 4) invoking insp_view_this() for viewing/scrolling/searching + * 5) cleaning up the dynamically acquired memory afterwards */ +static void inspection_utility (int pid) { + #define mkSEL(dst) { for (i = 0; i < Inspect.total; i++) Inspect.tab[i].caps = "~1"; \ + Inspect.tab[sel].caps = "~4"; dst[0] = '\0'; \ + for (i = 0; i < Inspect.total; i++) { char _s[SMLBUFSIZ]; \ + snprintf(_s, sizeof(_s), " %s %s", Inspect.tab[i].name, Inspect.tab[i].caps); \ + strcat(dst, _s); } } + char head[MEDBUFSIZ], sels[MEDBUFSIZ]; + static int sel; + int i, key; + proc_t *p; + + for (i = 0, p = NULL; i < Frame_maxtask; i++) + if (pid == Curwin->ppt[i]->tid) { + p = Curwin->ppt[i]; + break; + } + if (!p) { + show_msg(fmtmk(N_fmt(YINSP_pidbad_fmt), pid)); + return; + } + putp(Cap_clr_scr); + key = -1; + + do { + mkSEL(sels); + putp(Cap_home); + putp(Cap_curs_hide); + snprintf(head, sizeof(head), "%s", fmtmk(N_unq(INSP_hdrbase_fmt) + , pid, p->cmd, p->euser)); + show_special(1, fmtmk(N_unq(INSP_hdrsels_fmt), head, sels)); + INSP_MKSL(0, " "); + + if (-1 == key) key = keyin(0); + switch (key) { + case 0: + case 'q': + case kbd_ESC: + break; + case kbd_END: + sel = 0; // fall through ! + case kbd_LEFT: + if (--sel < 0) sel = Inspect.total -1; + key = -1; + break; + case kbd_HOME: + sel = Inspect.total; // fall through ! + case kbd_RIGHT: + if (++sel >= Inspect.total) sel = 0; + key = -1; + break; + case kbd_ENTER: + INSP_BUSY; + Insp_selname = Inspect.tab[sel].name; + Insp_selfmts = Inspect.tab[sel].fmts; + Inspect.tab[sel].func(Inspect.tab[sel].fmts, pid); + key = insp_view_this(head); + free(Insp_buf); + free(Insp_p); + break; + default: // keep gcc happy + key = -1; + break; + } + } while (key && 'q' != key && kbd_ESC != key); + + #undef mkSEL +} // end: inspection_utility +#undef INSP_MKSL +#undef INSP_RLEN +#undef INSP_BUSY + /*###### Startup routines ##############################################*/ /* @@ -2333,6 +2811,8 @@ static void before (char *me) { default: sa.sa_handler = sig_abexit; break; + case SIGCHLD: // we can't catch this + continue; // when opening a pipe } sigaction(i, &sa, NULL); } @@ -2440,7 +2920,7 @@ static void configs_read (void) { char fbuf[LRGBUFSIZ]; const char *p; FILE *fp; - int i, x; + int i; p = getenv("HOME"); snprintf(Rc_name, sizeof(Rc_name), "%s/.%src", (p && *p) ? p : ".", Myname); @@ -2464,12 +2944,13 @@ static void configs_read (void) { , "Id:%c, Mode_altscr=%d, Mode_irixps=%d, Delay_time=%f, Curwin=%d\n" , &Rc.id, &Rc.mode_altscr, &Rc.mode_irixps, &tmp_delay, &i)) { p = fmtmk(N_fmt(RC_bad_files_fmt), Rc_name); - goto default_or_error; + goto try_inspect_entries; // maybe a faulty 'inspect' echo } // you saw that, right? (fscanf stickin' it to 'i') Curwin = &Winstk[i]; for (i = 0 ; i < GROUPSMAX; i++) { + int x; WIN_t *w = &Winstk[i]; p = fmtmk(N_fmt(RC_bad_entry_fmt), i+1, Rc_name); @@ -2509,6 +2990,64 @@ static void configs_read (void) { // any new addition(s) last, for older rcfiles compatibility... fscanf(fp, "Fixed_widest=%d\n", &Rc.fixed_widest); +try_inspect_entries: + + // we'll start off Inspect stuff with 1 'potential' blank line + // ( only realized if we end up with Inspect.total > 0 ) + for (i = 0, Inspect.raw = strdup("\n");;) { + #define iT(element) Inspect.tab[i].element + size_t lraw = strlen(Inspect.raw) +1; + char *s; + + if (!fgets(fbuf, sizeof(fbuf), fp)) break; + lraw += strlen(fbuf) +1; + Inspect.raw = alloc_r(Inspect.raw, lraw); + strcat(Inspect.raw, fbuf); + + if (fbuf[0] == '#' || fbuf[0] == '\n') continue; + Inspect.tab = alloc_r(Inspect.tab, sizeof(struct I_entry) * (i + 1)); + p = fmtmk(N_fmt(YINSP_rcfile_fmt), i +1); + + if (!(s = strtok(fbuf, "\t\n"))) goto default_or_error; + iT(type) = strdup(s); + if (!(s = strtok(NULL, "\t\n"))) goto default_or_error; + iT(name) = strdup(s); + if (!(s = strtok(NULL, "\t\n"))) goto default_or_error; + iT(fmts) = strdup(s); + + switch (toupper(fbuf[0])) { + case 'F': + iT(func) = insp_do_file; + break; + case 'P': + iT(func) = insp_do_pipe; + break; + default: + goto default_or_error; + } + + iT(farg) = (strstr(iT(fmts), "%d")) ? 1 : 0; + ++i; + #undef iT + } // end: for ('inspect' entries) + + Inspect.total = i; +#ifndef INSP_OFFDEMO + if (!Inspect.total) { + Inspect.tab = alloc_c(sizeof(struct I_entry) * 3); + Inspect.tab[0].name = strdup(N_txt(YINSP_demo01_txt)); + Inspect.tab[0].func = insp_do_demo; + Inspect.tab[0].fmts = strdup(N_txt(YINSP_demo05_txt)); + Inspect.tab[1].name = strdup(N_txt(YINSP_demo02_txt)); + Inspect.tab[1].func = insp_do_demo; + Inspect.tab[1].fmts = strdup(N_txt(YINSP_demo05_txt)); + Inspect.tab[2].name = strdup(N_txt(YINSP_demo03_txt)); + Inspect.tab[2].func = insp_do_demo; + Inspect.tab[2].fmts = strdup(N_txt(YINSP_demo05_txt)); + Inspect.total = 3; + Inspect.demo = 1; + } +#endif fclose(fp); } // end: if (fp) @@ -2585,7 +3124,8 @@ static void parse_args (char **args) { Thread_mode = 1; break; case 'h': - case 'v': case 'V': + case 'v': + case 'V': fprintf(stdout, N_fmt(HELP_cmdline_fmt) , procps_version, Myname, N_txt(USAGE_abbrev_txt)); bye_bye(NULL); @@ -3012,6 +3552,9 @@ static void file_writerc (void) { // any new addition(s) last, for older rcfiles compatibility... fprintf(fp, "Fixed_widest=%d\n", Rc.fixed_widest); + if (!Inspect.demo && Inspect.total) + fputs(Inspect.raw, fp); + fclose(fp); show_msg(fmtmk(N_fmt(WRITE_rcfile_fmt), Rc_name)); } // end: file_writerc @@ -3169,6 +3712,17 @@ static void keys_global (int ch) { } } break; + case 'Y': + if (!Inspect.total) + linein(N_txt(YINSP_noents_txt)); + else { + int pid, def = w->ppt[w->begtask]->tid; + if (GET_INT_BAD < (pid = get_int(fmtmk(N_fmt(YINSP_pidsee_fmt), def)))) { + if (0 > pid) pid = def; + if (pid) inspection_utility(pid); + } + } + break; case 'Z': wins_colors(); break; @@ -3634,7 +4188,7 @@ static void do_key (int ch) { char keys[SMLBUFSIZ]; } key_tab[] = { { keys_global, - { '?', 'B', 'd', 'F', 'f', 'g', 'H', 'h', 'I', 'k', 'r', 's', 'X', 'Z' + { '?', 'B', 'd', 'F', 'f', 'g', 'H', 'h', 'I', 'k', 'r', 's', 'X', 'Y', 'Z' , kbd_ENTER, kbd_SPACE, '\0' } }, { keys_summary, { '1', 'C', 'l', 'm', 't', '\0' } }, diff --git a/top/top.h b/top/top.h index 3169ac87..03fd1f36 100644 --- a/top/top.h +++ b/top/top.h @@ -32,6 +32,9 @@ //#define CASEUP_SUFIX /* show time/mem/cnts suffix in upper case */ //#define CPU_ZEROTICS /* tolerate few tics when cpu off vs. idle */ //#define EQUCOLHDRYES /* yes, do equalize column header lengths */ +//#define INSP_OFFDEMO /* disable demo screens, issue msg instead */ +//#define INSP_SAVEBUF /* preserve 'Insp_buf' contents in a file */ +//#define INSP_SLIDE_1 /* when scrolling left/right don't move 8 */ //#define OFF_HST_HASH /* use BOTH qsort+bsrch vs. hashing scheme */ //#define OFF_STDIOLBF /* disable our own stdout _IOFBF override */ //#define PERCENTBOOST /* enable extended precision for % fields */ @@ -120,6 +123,8 @@ char *strcasestr(const char *haystack, const char *needle); many termcap/color transitions - these definitions ensure we have room */ #define ROWMINSIZ ( SCREENMAX + 4 * (CAPBUFSIZ + CLRBUFSIZ) ) #define ROWMAXSIZ ( SCREENMAX + 16 * (CAPBUFSIZ + CLRBUFSIZ) ) + // minimum size guarantee for dynamically acquired 'readfile' buffer +#define READMINSZ 2048 // space between task fields/columns #define COLPADSTR " " @@ -598,12 +603,13 @@ typedef struct WIN_t { //atic inline void show_scroll (void); //atic void show_special (int interact, const char *glob); //atic void updt_scroll_msg (void); -/*------ Low Level Memory/Keyboard support -----------------------------*/ +/*------ Low Level Memory/Keyboard/File I/O support --------------------*/ //atic void *alloc_c (size_t num); //atic void *alloc_r (void *ptr, size_t num); //atic int chin (int ech, char *buf, unsigned cnt); //atic int keyin (int init); //atic char *linein (const char *prompt); +//atic int readfile (FILE *fp, char **baddr, unsigned *bsize, unsigned *bread); /*------ Small Utility routines ----------------------------------------*/ //atic float get_float (const char *prompt); //atic int get_int (const char *prompt); @@ -638,6 +644,17 @@ typedef struct WIN_t { //atic void procs_hlp (proc_t *p); //atic void procs_refresh (void); //atic void sysinfo_refresh (int forced); +/*------ Inspect Other Output ------------------------------------------*/ +//atic void insp_cnt_nl (void); +#ifndef INSP_OFFDEMO +//atic void insp_do_demo (char *fmts, int pid); +#endif +//atic void insp_do_file (char *fmts, int pid); +//atic void insp_do_pipe (char *fmts, int pid); +//atic void insp_find (int ch, int *col, int *row); +//atic inline void insp_show_pg (int col, int row, int max); +//atic int insp_view_this (char *hdr); +//atic void inspection_utility (int pid); /*------ Startup routines ----------------------------------------------*/ //atic void before (char *me); //atic int config_cvt (WIN_t *q); diff --git a/top/top_nls.c b/top/top_nls.c index 577d8d85..6239378b 100644 --- a/top/top_nls.c +++ b/top/top_nls.c @@ -371,6 +371,55 @@ static void build_norm_nlstab (void) { #ifndef WARN_CFG_OFF Norm_nlstab[XTRA_warncfg_txt] = _("Overwrite existing old style rcfile?"); #endif +#ifndef INSP_OFFDEMO + Norm_nlstab[YINSP_demo01_txt] = _("Open Files"); + Norm_nlstab[YINSP_demo02_txt] = _("NUMA Info"); + Norm_nlstab[YINSP_demo03_txt] = _("Log"); + Norm_nlstab[YINSP_demo04_txt] = _("" + "This is simulated output representing the contents of some file or the output\n" + "from some command. Exactly which commands and/or files are solely up to you.\n" + "\n" + "Although this text is for information purposes only, it can still be scrolled\n" + "and searched like real output will be. You are encouraged to experiment with\n" + "those features as explained in the prologue above.\n" + "\n" + "To enable real Inspect functionality, entries must be added to the end of the\n" + "top personal personal configuration file. You could use your favorite editor\n" + "to accomplish this, taking care not to disturb existing entries.\n" + "\n" + "Another way to add entries is illustrated below, but it risks overwriting the\n" + "rcfile. Redirected echoes must not replace (>) but append (>>) to that file.\n" + "\n" + " /bin/echo -e \"pipe\\tOpen Files\\tlsof -P -p %d 2>&1\" >> ~/.toprc\n" + " /bin/echo -e \"file\\tNUMA Info\\t/proc/%d/numa_maps\" >> ~/.toprc\n" + " /bin/echo -e \"pipe\\tLog\\ttail -n200 /var/log/syslog | sort -Mr\" >> ~/.toprc\n" + "\n" + "If you don't know the location or name of the top rcfile, use the 'W' command\n" + "and note those details. After backing up the current rcfile, try issuing the\n" + "above echoes exactly as shown, replacing '.toprc' as appropriate. The safest\n" + "approach would be to use copy then paste to avoid any typing mistakes.\n" + "\n" + "Finally, restart top to reveal what actual Inspect entries combined with this\n" + "new command can offer. The possibilities are endless, especially considering\n" + "that 'pipe' type entries can include shell scripts too!\n" + "\n" + "For additional important information, please consult the top documentation.\n" + "Then enhance top with your very own customized 'file' and 'pipe' entries.\n" + "\n" + "Enjoy!\n"); + Norm_nlstab[YINSP_demo05_txt] = _("the '=' key will eventually show the actual file read or command(s) excuted ..."); +#endif + Norm_nlstab[YINSP_failed_fmt] = _("Selection failed with: %s\n"); +#ifndef INSP_OFFDEMO + Norm_nlstab[YINSP_noents_txt] = _("to enable 'Y' press then type 'W' and restart top"); +#else + Norm_nlstab[YINSP_noents_txt] = _("to enable 'Y' please consult the top man page (press Enter)"); +#endif + Norm_nlstab[YINSP_pidbad_fmt] = _("unable to inspect, pid %d not found"); + Norm_nlstab[YINSP_pidsee_fmt] = _("inspect at PID [defailt pid = %d]"); + Norm_nlstab[YINSP_rcfile_fmt] = _("could not parse rcfile inspect entry %d"); + Norm_nlstab[YINSP_status_fmt] = _("%s: %*d-%-*d lines, %*d-%*d columns, %u bytes read"); + Norm_nlstab[YINSP_workin_txt] = _("patience please, working..."); } @@ -400,7 +449,7 @@ static void build_uniq_nlstab (void) { . adhere to that goal lest the translated text be truncated. . . If you would like additional information regarding these strings, - . please see the prolog to the show_special function in the top.c + . please see the prologue to the show_special function in the top.c . source file. . */ @@ -413,17 +462,17 @@ static void build_uniq_nlstab (void) { " 1,I Toggle SMP view: '~11~2' single/separate states; '~1I~2' Irix/Solaris mode\n" " f,F,X Fields: '~1f~2'/'~1F~2' add/remove/order/sort; '~1X~2' increase fixed-width\n" "\n" - " L,&,<,> . Locate: '~1L~2'/'~1&~2' find/again; Move sort column: '~1<~2'/'~1>~2' left/right\n" \ + " L,&,<,> . Locate: '~1L~2'/'~1&~2' find/again; Move sort column: '~1<~2'/'~1>~2' left/right\n" " R,H,V,J . Toggle: '~1R~2' Sort; '~1H~2' Threads; '~1V~2' Forest view; '~1J~2' Num justify\n" " c,i,S,j . Toggle: '~1c~2' Cmd name/line; '~1i~2' Idle; '~1S~2' Time; '~1j~2' Str justify\n" " x~5,~1y~5 . Toggle highlights: '~1x~2' sort field; '~1y~2' running tasks\n" " z~5,~1b~5 . Toggle: '~1z~2' color/mono; '~1b~2' bold/reverse (only if 'x' or 'y')\n" - " u,U . Show: '~1u~2' effective user; '~1U~2' real, saved, file or effective user\n" + " u,U . Filter by: '~1u~2' effective user; '~1U~2' real, saved, file or effective user\n" " n or # . Set maximum tasks displayed\n" " C,... . Toggle scroll coordinates msg for: ~1up~2,~1down~2,~1left~2,right~2,~1home~2,~1end~2\n" "\n" "%s" - " W Write configuration file\n" + " W,Y Write configuration file '~1W~2'; Inspect other output '~1Y~2'\n" " q Quit\n" " ( commands shown with '.' require a ~1visible~2 task display ~1window~2 ) \n" "Press '~1h~2' or '~1?~2' for help with ~1Windows~2,\n" @@ -544,6 +593,19 @@ static void build_uniq_nlstab (void) { Uniq_nlstab[MEMORY_lines_fmt] = _("" "%s Mem: ~3 %8lu ~2total,~3 %8lu ~2used,~3 %8lu ~2free,~3 %8lu ~2buffers~3\n" "%s Swap:~3 %8lu ~2total,~3 %8lu ~2used,~3 %8lu ~2free,~3 %8lu ~2cached~3\n"); + + Uniq_nlstab[INSP_hdrbase_fmt] = _("" + "Inspection~2 Pause at: pid ~1%d~6 running command ~1%s~6 as user ~1%s~6"); + + Uniq_nlstab[INSP_hdrsels_fmt] = _("" + "%s\n" + "Use~2: left/right then to ~1select~5 an option; 'q' or to ~1end~5 !\n" + "Options~2: ~1%s\n"); + + Uniq_nlstab[INSP_hdrview_fmt] = _("" + "%s\n" + "Use~2: left/right/up/down/etc to ~1navigate~5 the output; 'L'/'&' to ~1locate~5/~1next~5.\n" + "Or~2: to ~1select another~5; 'q' or to ~1end~5 !\n"); } @@ -602,6 +664,16 @@ void initialize_nls (void) { fprintf(stderr, nls_err, "Uniq", i); exit(1); } + #ifndef INSP_OFFDEMO + if (READMINSZ < strlen(N_txt(YINSP_demo04_txt)) +1) { + fprintf(stderr + , "\nAssertion Failed in %s (%s):\n" + "\t'READMINSZ < strlen(N_txt(YINSP_demo04_txt)) + 1'\n" + "READMINSZ must be at least %u !\n\n" + , __FILE__, __func__, (unsigned)strlen(N_txt(YINSP_demo04_txt)) + 1); + exit(1); + } + #endif #else setlocale (LC_ALL, ""); bindtextdomain(PACKAGE, LOCALEDIR); diff --git a/top/top_nls.h b/top/top_nls.h index 8c2454d9..b1678ed2 100644 --- a/top/top_nls.h +++ b/top/top_nls.h @@ -84,13 +84,20 @@ enum norm_nls { #ifndef WARN_CFG_OFF XTRA_warncfg_txt, #endif +#ifndef INSP_OFFDEMO + YINSP_demo01_txt, YINSP_demo02_txt, YINSP_demo03_txt, YINSP_demo04_txt, + YINSP_demo05_txt, +#endif + YINSP_failed_fmt, YINSP_noents_txt, YINSP_pidbad_fmt, YINSP_pidsee_fmt, + YINSP_rcfile_fmt, YINSP_status_fmt, YINSP_workin_txt, norm_MAX }; enum uniq_nls { KEYS_helpbas_fmt, KEYS_helpext_fmt, WINDOWS_help_fmt, COLOR_custom_fmt, FIELD_header_fmt, MEMORY_lines_fmt, STATE_line_1_fmt, STATE_lin2x4_fmt, - STATE_lin2x5_fmt, STATE_lin2x6_fmt, STATE_lin2x7_fmt, + STATE_lin2x5_fmt, STATE_lin2x6_fmt, STATE_lin2x7_fmt, INSP_hdrbase_fmt, + INSP_hdrsels_fmt, INSP_hdrview_fmt, uniq_MAX };