diff --git a/bin/ps/extern.h b/bin/ps/extern.h --- a/bin/ps/extern.h +++ b/bin/ps/extern.h @@ -40,6 +40,7 @@ extern time_t now; extern int showthreads, sumrusage, termwidth; extern struct velisthead varlist; +extern const size_t known_keywords_nb; __BEGIN_DECLS char *arguments(KINFO *, VARENT *); @@ -55,6 +56,7 @@ const char *fmt_argv(char **, char *, char *, size_t); double getpcpu(const KINFO *); char *jailname(KINFO *, VARENT *); +size_t aliased_keyword_index(const VAR *); char *kvar(KINFO *, VARENT *); char *label(KINFO *, VARENT *); char *loginclass(KINFO *, VARENT *); diff --git a/bin/ps/keyword.c b/bin/ps/keyword.c --- a/bin/ps/keyword.c +++ b/bin/ps/keyword.c @@ -42,6 +42,7 @@ #include #include #include + #include #include "ps.h" @@ -228,7 +229,18 @@ USHORT, "x"}, }; -static const size_t known_keywords_nb = nitems(keywords); +const size_t known_keywords_nb = nitems(keywords); + +size_t +aliased_keyword_index(const VAR *const v) +{ + const VAR *const fv = (v->flag & RESOLVED_ALIAS) == 0 ? + v : v->final_kw; + const size_t idx = fv - keywords; + + assert(idx < known_keywords_nb); + return (idx); +} /* * Sanity checks on declared keywords. @@ -426,16 +438,6 @@ eval = 1; continue; } - if (!user) { - /* - * If the user is NOT adding this field manually, - * get on with our lives if this VAR is already - * represented in the list. - */ - vent = find_varentry(v->name); - if (vent != NULL) - continue; - } #ifndef PS_CHECK_KEYWORDS /* @@ -456,6 +458,7 @@ } vent->width = strlen(vent->header); vent->var = v; + vent->flags = user ? VE_KEEP : 0; STAILQ_INSERT_TAIL(var_list, vent, next_ve); } diff --git a/bin/ps/ps.h b/bin/ps/ps.h --- a/bin/ps/ps.h +++ b/bin/ps/ps.h @@ -60,6 +60,8 @@ const char *header; const struct var *var; u_int width; +#define VE_KEEP (1 << 0) + uint16_t flags; } VARENT; STAILQ_HEAD(velisthead, varent); diff --git a/bin/ps/ps.1 b/bin/ps/ps.1 --- a/bin/ps/ps.1 +++ b/bin/ps/ps.1 @@ -140,11 +140,16 @@ designate specific predefined groups of columns, also called canned displays. Appearance of any of these options inhibits the default display, replacing it all with the requested columns, and in the order options are passed. -The individual columns requested via a canned display option which have the same -keyword as that of some column added by earlier options are not added again. -This kind of automatic removal of duplicate keywords in canned displays is -useful for slightly tweaking these displays without having to rebuild variants -from scratch, e.g., using +The individual columns requested via a canned display option that have the same +keyword or an alias to that of some column added by an earlier canned display +option, or by an explicit +.Fl O +or +.Fl o +option anywhere on the command line, are suppressed. +This automatic removal of duplicate data in canned displays is useful for +slightly tweaking these displays and/or combining multiple ones without having +to rebuild variants from scratch, e.g., using only .Fl o options. .Pp @@ -1027,14 +1032,6 @@ Finally, columns requested through multiple occurences are not grouped together, as one may naturally expect. .Pp -Automatic removal of duplicate columns from canned displays only works backwards -at time of insertion, i.e., adding a new canned display will lead to checking -columns before it but not those after it. -Besides the inconsistency, this prevents relocating columns of canned displays -further right, which can be useful, e.g., to relocate a column with the -.Cm command -keyword at end of display. -.Pp The .Fl a option has no effect if other options affecting the selection of processes are diff --git a/bin/ps/ps.c b/bin/ps/ps.c --- a/bin/ps/ps.c +++ b/bin/ps/ps.c @@ -82,6 +82,19 @@ int termwidth; /* Width of the screen (0 == infinity). */ int showthreads; /* will threads be shown? */ +struct keyword_info { + /* + * Whether there is (at least) one column referencing this keyword that + * must be kept. + */ +#define KWI_HAS_MUST_KEEP_COLUMN (1 << 0) + /* + * Whether a column with such a keyword has been seen. + */ +#define KWI_SEEN (1 << 1) + u_int flags; +}; + struct velisthead varlist = STAILQ_HEAD_INITIALIZER(varlist); static kvm_t *kd; @@ -129,7 +142,8 @@ static char *kludge_oldps_options(const char *, char *, const char *); static int pscomp(const void *, const void *); static void saveuser(KINFO *); -static void scanvars(void); +static void scan_vars(struct keyword_info *); +static void remove_redundant_columns(struct keyword_info *); static void pidmax_init(void); static void usage(void); @@ -166,6 +180,7 @@ char fmtbuf[_POSIX2_LINE_MAX]; enum { NONE = 0, UP = 1, DOWN = 2, BOTH = 1 | 2 } directions = NONE; struct { int traversed; int initial; } pid_count; + struct keyword_info *keywords_info; (void) setlocale(LC_ALL, ""); time(&now); /* Used by routines in print.c. */ @@ -462,6 +477,22 @@ if (!_fmt) parsefmt(dfmt, &varlist, 0); + keywords_info = calloc(known_keywords_nb, sizeof(struct keyword_info)); + if (keywords_info == NULL) + xo_errx(1, "malloc failed"); + /* + * Scan requested variables, noting which structures are needed and + * which keywords are specified. + */ + scan_vars(keywords_info); + /* + * Remove redundant columns from "canned" displays (see the callee's + * herald comment for more details). + */ + remove_redundant_columns(keywords_info); + free(keywords_info); + keywords_info = NULL; + if (!all && nselectors == 0) { uidlist.l.ptr = malloc(sizeof(uid_t)); if (uidlist.l.ptr == NULL) @@ -471,12 +502,6 @@ *uidlist.l.uids = getuid(); } - /* - * scan requested variables, noting what structures are needed, - * and adjusting header widths as appropriate. - */ - scanvars(); - /* * Get process list. If the user requested just one selector- * option, then kvm_getprocs can be asked to return just those @@ -1196,7 +1221,7 @@ } static void -scanvars(void) +scan_vars(struct keyword_info *const keywords_info) { struct varent *vent; const VAR *v; @@ -1207,6 +1232,47 @@ needuser = 1; if (v->flag & COMM) needcomm = 1; + if ((vent->flags & VE_KEEP) != 0) + keywords_info[aliased_keyword_index(v)].flags |= + KWI_HAS_MUST_KEEP_COLUMN; + } +} + +/* + * For each explicitly requested keyword, remove all the same keywords + * from "canned" displays. If the same keyword appears multiple times + * only in "canned displays", then keep the first (leftmost) occurence + * only (with the reasoning that columns requested first are the most + * important as their positions catch the eye more). + */ +static void +remove_redundant_columns(struct keyword_info *const keywords_info) +{ + struct varent *prev_vent, *vent, *next_vent; + + prev_vent = NULL; + STAILQ_FOREACH_SAFE(vent, &varlist, next_ve, next_vent) { + const VAR *const v = vent->var; + struct keyword_info *const kwi = + &keywords_info[aliased_keyword_index(v)]; + + /* + * If the current column is not marked as to absolutely keep, + * and we have either already output one with the same keyword + * or know we will output one later, remove it. + */ + if ((vent->flags & VE_KEEP) == 0 && + (kwi->flags & (KWI_HAS_MUST_KEEP_COLUMN | KWI_SEEN)) != 0) { + if (prev_vent == NULL) + STAILQ_REMOVE_HEAD(&varlist, next_ve); + else + STAILQ_REMOVE_AFTER(&varlist, prev_vent, + next_ve); + } else + prev_vent = vent; + + + kwi->flags |= KWI_SEEN; } }