1 /*- 2 * SPDX-License-Identifier: BSD-3-Clause 3 * 4 * Copyright (c) 1990, 1993, 1994 5 * The Regents of the University of California. All rights reserved. 6 * Copyright (c) 2025 The FreeBSD Foundation 7 * 8 * Portions of this software were developed by Olivier Certner 9 * <olce@FreeBSD.org> at Kumacom SARL under sponsorship from the FreeBSD 10 * Foundation. 11 * 12 * Redistribution and use in source and binary forms, with or without 13 * modification, are permitted provided that the following conditions 14 * are met: 15 * 1. Redistributions of source code must retain the above copyright 16 * notice, this list of conditions and the following disclaimer. 17 * 2. Redistributions in binary form must reproduce the above copyright 18 * notice, this list of conditions and the following disclaimer in the 19 * documentation and/or other materials provided with the distribution. 20 * 3. Neither the name of the University nor the names of its contributors 21 * may be used to endorse or promote products derived from this software 22 * without specific prior written permission. 23 * 24 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 34 * SUCH DAMAGE. 35 */ 36 37 #include <sys/param.h> 38 #include <sys/time.h> 39 #include <sys/resource.h> 40 #include <sys/proc.h> 41 #include <sys/sysctl.h> 42 #include <sys/user.h> 43 44 #include <assert.h> 45 #include <stdbool.h> 46 #include <stddef.h> 47 #include <stdio.h> 48 #include <stdlib.h> 49 #include <string.h> 50 51 #include <libxo/xo.h> 52 53 #include "ps.h" 54 55 static int vcmp(const void *, const void *); 56 57 /* Compute offset in common structures. */ 58 #define KOFF(x) offsetof(struct kinfo_proc, x) 59 #define ROFF(x) offsetof(struct rusage, x) 60 61 #define LWPFMT "d" 62 #define NLWPFMT "d" 63 #define UIDFMT "u" 64 #define PIDFMT "d" 65 66 /* PLEASE KEEP THE TABLE BELOW SORTED ALPHABETICALLY!!! */ 67 static VAR keywords[] = { 68 {"%cpu", {NULL}, "%CPU", "percent-cpu", 0, pcpu, 0, UNSPEC, NULL}, 69 {"%mem", {NULL}, "%MEM", "percent-memory", 0, pmem, 0, UNSPEC, NULL}, 70 {"acflag", {NULL}, "ACFLG", "accounting-flag", 0, kvar, KOFF(ki_acflag), 71 USHORT, "x"}, 72 {"acflg", {"acflag"}, NULL, NULL, 0, NULL, 0, UNSPEC, NULL}, 73 {"args", {NULL}, "COMMAND", "arguments", COMM|LJUST|USER, arguments, 0, 74 UNSPEC, NULL}, 75 {"blocked", {"sigmask"}, NULL, NULL, 0, NULL, 0, UNSPEC, NULL}, 76 {"caught", {"sigcatch"}, NULL, NULL, 0, NULL, 0, UNSPEC, NULL}, 77 {"class", {NULL}, "CLASS", "login-class", LJUST, loginclass, 0, 78 UNSPEC, NULL}, 79 {"comm", {NULL}, "COMMAND", "command", LJUST, ucomm, 0, UNSPEC, NULL}, 80 {"command", {NULL}, "COMMAND", "command", COMM|LJUST|USER, command, 0, 81 UNSPEC, NULL}, 82 {"cow", {NULL}, "COW", "copy-on-write-faults", 0, kvar, KOFF(ki_cow), 83 UINT, "u"}, 84 {"cpu", {NULL}, "C", "on-cpu", 0, cpunum, 0, UNSPEC, NULL}, 85 {"cputime", {"time"}, NULL, NULL, 0, NULL, 0, UNSPEC, NULL}, 86 {"dsiz", {NULL}, "DSIZ", "data-size", 0, kvar, KOFF(ki_dsize), 87 PGTOK, "ld"}, 88 {"egid", {"gid"}, NULL, NULL, 0, NULL, 0, UNSPEC, NULL}, 89 {"egroup", {"group"}, NULL, NULL, 0, NULL, 0, UNSPEC, NULL}, 90 {"emul", {NULL}, "EMUL", "emulation-envirnment", LJUST, emulname, 0, 91 UNSPEC, NULL}, 92 {"etime", {NULL}, "ELAPSED", "elapsed-time", USER, elapsed, 0, 93 UNSPEC, NULL}, 94 {"etimes", {NULL}, "ELAPSED", "elapsed-times", USER, elapseds, 0, 95 UNSPEC, NULL}, 96 {"euid", {"uid"}, NULL, NULL, 0, NULL, 0, UNSPEC, NULL}, 97 {"f", {NULL}, "F", "flags", 0, kvar, KOFF(ki_flag), LONG, "lx"}, 98 {"f2", {NULL}, "F2", "flags2", 0, kvar, KOFF(ki_flag2), INT, "08x"}, 99 {"fib", {NULL}, "FIB", "fib", 0, kvar, KOFF(ki_fibnum), INT, "d"}, 100 {"flags", {"f"}, NULL, NULL, 0, NULL, 0, UNSPEC, NULL}, 101 {"flags2", {"f2"}, NULL, NULL, 0, NULL, 0, UNSPEC, NULL}, 102 {"gid", {NULL}, "GID", "gid", 0, kvar, KOFF(ki_groups), UINT, UIDFMT}, 103 {"group", {NULL}, "GROUP", "group", LJUST, egroupname, 0, UNSPEC, NULL}, 104 {"ignored", {"sigignore"}, NULL, NULL, 0, NULL, 0, UNSPEC, NULL}, 105 {"inblk", {NULL}, "INBLK", "read-blocks", USER, rvar, ROFF(ru_inblock), 106 LONG, "ld"}, 107 {"inblock", {"inblk"}, NULL, NULL, 0, NULL, 0, UNSPEC, NULL}, 108 {"jail", {NULL}, "JAIL", "jail-name", LJUST, jailname, 0, UNSPEC, NULL}, 109 {"jid", {NULL}, "JID", "jail-id", 0, kvar, KOFF(ki_jid), INT, "d"}, 110 {"jobc", {NULL}, "JOBC", "job-control-count", 0, kvar, KOFF(ki_jobc), 111 SHORT, "d"}, 112 {"ktrace", {NULL}, "KTRACE", "ktrace", 0, kvar, KOFF(ki_traceflag), 113 INT, "x"}, 114 {"label", {NULL}, "LABEL", "label", LJUST, label, 0, UNSPEC, NULL}, 115 {"lim", {NULL}, "LIM", "memory-limit", 0, maxrss, 0, UNSPEC, NULL}, 116 {"lockname", {NULL}, "LOCK", "lock-name", LJUST, lockname, 0, 117 UNSPEC, NULL}, 118 {"login", {NULL}, "LOGIN", "login-name", LJUST, logname, 0, 119 UNSPEC, NULL}, 120 {"logname", {"login"}, NULL, NULL, 0, NULL, 0, UNSPEC, NULL}, 121 {"lstart", {NULL}, "STARTED", "start-time", LJUST|USER, lstarted, 0, 122 UNSPEC, NULL}, 123 {"lwp", {NULL}, "LWP", "thread-id", 0, kvar, KOFF(ki_tid), 124 UINT, LWPFMT}, 125 {"majflt", {NULL}, "MAJFLT", "major-faults", USER, rvar, ROFF(ru_majflt), 126 LONG, "ld"}, 127 {"minflt", {NULL}, "MINFLT", "minor-faults", USER, rvar, ROFF(ru_minflt), 128 LONG, "ld"}, 129 {"msgrcv", {NULL}, "MSGRCV", "received-messages", USER, rvar, 130 ROFF(ru_msgrcv), LONG, "ld"}, 131 {"msgsnd", {NULL}, "MSGSND", "sent-messages", USER, rvar, 132 ROFF(ru_msgsnd), LONG, "ld"}, 133 {"mwchan", {NULL}, "MWCHAN", "wait-channel", LJUST, mwchan, 0, 134 UNSPEC, NULL}, 135 {"ni", {"nice"}, NULL, NULL, 0, NULL, 0, UNSPEC, NULL}, 136 {"nice", {NULL}, "NI", "nice", 0, kvar, KOFF(ki_nice), CHAR, "d"}, 137 {"nivcsw", {NULL}, "NIVCSW", "involuntary-context-switches", USER, rvar, 138 ROFF(ru_nivcsw), LONG, "ld"}, 139 {"nlwp", {NULL}, "NLWP", "threads", 0, kvar, KOFF(ki_numthreads), 140 UINT, NLWPFMT}, 141 {"nsignals", {"nsigs"}, NULL, NULL, 0, NULL, 0, UNSPEC, NULL}, 142 {"nsigs", {NULL}, "NSIGS", "signals-taken", USER, rvar, 143 ROFF(ru_nsignals), LONG, "ld"}, 144 {"nswap", {NULL}, "NSWAP", "swaps", USER, rvar, ROFF(ru_nswap), 145 LONG, "ld"}, 146 {"nvcsw", {NULL}, "NVCSW", "voluntary-context-switches", USER, rvar, 147 ROFF(ru_nvcsw), LONG, "ld"}, 148 {"nwchan", {NULL}, "NWCHAN", "wait-channel-address", LJUST, nwchan, 0, 149 UNSPEC, NULL}, 150 {"oublk", {NULL}, "OUBLK", "written-blocks", USER, rvar, 151 ROFF(ru_oublock), LONG, "ld"}, 152 {"oublock", {"oublk"}, NULL, NULL, 0, NULL, 0, UNSPEC, NULL}, 153 {"paddr", {NULL}, "PADDR", "process-address", 0, kvar, KOFF(ki_paddr), 154 KPTR, "lx"}, 155 {"pagein", {NULL}, "PAGEIN", "pageins", USER, pagein, 0, UNSPEC, NULL}, 156 {"pcpu", {"%cpu"}, NULL, NULL, 0, NULL, 0, UNSPEC, NULL}, 157 {"pending", {"sig"}, NULL, NULL, 0, NULL, 0, UNSPEC, NULL}, 158 {"pgid", {NULL}, "PGID", "process-group", 0, kvar, KOFF(ki_pgid), 159 UINT, PIDFMT}, 160 {"pid", {NULL}, "PID", "pid", 0, kvar, KOFF(ki_pid), UINT, PIDFMT}, 161 {"pmem", {"%mem"}, NULL, NULL, 0, NULL, 0, UNSPEC, NULL}, 162 {"ppid", {NULL}, "PPID", "ppid", 0, kvar, KOFF(ki_ppid), UINT, PIDFMT}, 163 {"pri", {NULL}, "PRI", "priority", 0, pri, 0, UNSPEC, NULL}, 164 {"re", {NULL}, "RE", "residency-time", INF127, kvar, KOFF(ki_swtime), 165 UINT, "d"}, 166 {"rgid", {NULL}, "RGID", "real-gid", 0, kvar, KOFF(ki_rgid), 167 UINT, UIDFMT}, 168 {"rgroup", {NULL}, "RGROUP", "real-group", LJUST, rgroupname, 0, 169 UNSPEC, NULL}, 170 {"rss", {NULL}, "RSS", "rss", 0, kvar, KOFF(ki_rssize), PGTOK, "ld"}, 171 {"rtprio", {NULL}, "RTPRIO", "realtime-priority", 0, priorityr, 172 KOFF(ki_pri), UNSPEC, NULL}, 173 {"ruid", {NULL}, "RUID", "real-uid", 0, kvar, KOFF(ki_ruid), 174 UINT, UIDFMT}, 175 {"ruser", {NULL}, "RUSER", "real-user", LJUST, runame, 0, UNSPEC, NULL}, 176 {"sid", {NULL}, "SID", "sid", 0, kvar, KOFF(ki_sid), UINT, PIDFMT}, 177 {"sig", {NULL}, "PENDING", "signals-pending", 0, kvar, KOFF(ki_siglist), 178 INT, "x"}, 179 {"sigcatch", {NULL}, "CAUGHT", "signals-caught", 0, kvar, 180 KOFF(ki_sigcatch), UINT, "x"}, 181 {"sigignore", {NULL}, "IGNORED", "signals-ignored", 0, kvar, 182 KOFF(ki_sigignore), UINT, "x"}, 183 {"sigmask", {NULL}, "BLOCKED", "signal-mask", 0, kvar, KOFF(ki_sigmask), 184 UINT, "x"}, 185 {"sl", {NULL}, "SL", "sleep-time", INF127, kvar, KOFF(ki_slptime), 186 UINT, "d"}, 187 {"ssiz", {NULL}, "SSIZ", "stack-size", 0, kvar, KOFF(ki_ssize), 188 PGTOK, "ld"}, 189 {"start", {NULL}, "STARTED", "start-time", LJUST|USER, started, 0, 190 UNSPEC, NULL}, 191 {"stat", {"state"}, NULL, NULL, 0, NULL, 0, UNSPEC, NULL}, 192 {"state", {NULL}, "STAT", "state", LJUST, state, 0, UNSPEC, NULL}, 193 {"svgid", {NULL}, "SVGID", "saved-gid", 0, kvar, KOFF(ki_svgid), 194 UINT, UIDFMT}, 195 {"svuid", {NULL}, "SVUID", "saved-uid", 0, kvar, KOFF(ki_svuid), 196 UINT, UIDFMT}, 197 {"systime", {NULL}, "SYSTIME", "system-time", USER, systime, 0, 198 UNSPEC, NULL}, 199 {"tdaddr", {NULL}, "TDADDR", "thread-address", 0, kvar, KOFF(ki_tdaddr), 200 KPTR, "lx"}, 201 {"tdev", {NULL}, "TDEV", "terminal-device", 0, tdev, 0, UNSPEC, NULL}, 202 {"tdnam", {"tdname"}, NULL, NULL, 0, NULL, 0, UNSPEC, NULL}, 203 {"tdname", {NULL}, "TDNAME", "thread-name", LJUST, tdnam, 0, 204 UNSPEC, NULL}, 205 {"tid", {"lwp"}, NULL, NULL, 0, NULL, 0, UNSPEC, NULL}, 206 {"time", {NULL}, "TIME", "cpu-time", USER, cputime, 0, UNSPEC, NULL}, 207 {"tpgid", {NULL}, "TPGID", "terminal-process-gid", 0, kvar, 208 KOFF(ki_tpgid), UINT, PIDFMT}, 209 {"tracer", {NULL}, "TRACER", "tracer", 0, kvar, KOFF(ki_tracer), 210 UINT, PIDFMT}, 211 {"tsid", {NULL}, "TSID", "terminal-sid", 0, kvar, KOFF(ki_tsid), 212 UINT, PIDFMT}, 213 {"tsiz", {NULL}, "TSIZ", "text-size", 0, kvar, KOFF(ki_tsize), 214 PGTOK, "ld"}, 215 {"tt", {NULL}, "TT ", "terminal-name", 0, tname, 0, UNSPEC, NULL}, 216 {"tty", {NULL}, "TTY", "tty", LJUST, longtname, 0, UNSPEC, NULL}, 217 {"ucomm", {NULL}, "UCOMM", "accounting-name", LJUST, ucomm, 0, 218 UNSPEC, NULL}, 219 {"uid", {NULL}, "UID", "uid", 0, kvar, KOFF(ki_uid), UINT, UIDFMT}, 220 {"upr", {NULL}, "UPR", "user-priority", 0, upr, 0, UNSPEC, NULL}, 221 {"uprocp", {NULL}, "UPROCP", "process-address", 0, kvar, KOFF(ki_paddr), 222 KPTR, "lx"}, 223 {"user", {NULL}, "USER", "user", LJUST, username, 0, UNSPEC, NULL}, 224 {"usertime", {NULL}, "USERTIME", "user-time", USER, usertime, 0, 225 UNSPEC, NULL}, 226 {"usrpri", {"upr"}, NULL, NULL, 0, NULL, 0, UNSPEC, NULL}, 227 {"vmaddr", {NULL}, "VMADDR", "vmspace-address", 0, kvar, 228 KOFF(ki_vmspace), KPTR, "lx"}, 229 {"vsize", {"vsz"}, NULL, NULL, 0, NULL, 0, UNSPEC, NULL}, 230 {"vsz", {NULL}, "VSZ", "virtual-size", 0, vsize, 0, UNSPEC, NULL}, 231 {"wchan", {NULL}, "WCHAN", "wait-channel", LJUST, wchan, 0, 232 UNSPEC, NULL}, 233 {"xstat", {NULL}, "XSTAT", "exit-status", 0, kvar, KOFF(ki_xstat), 234 USHORT, "x"}, 235 }; 236 237 const size_t known_keywords_nb = nitems(keywords); 238 239 size_t 240 aliased_keyword_index(const VAR *const v) 241 { 242 const VAR *const fv = (v->flag & RESOLVED_ALIAS) == 0 ? 243 v : v->final_kw; 244 const size_t idx = fv - keywords; 245 246 assert(idx < known_keywords_nb); 247 return (idx); 248 } 249 250 /* 251 * Sanity checks on declared keywords. 252 * 253 * Checks specific to aliases are done in resolve_alias() instead. 254 * 255 * Currently, only checks that keywords are alphabetically ordered by their 256 * names. More checks could be added, such as the absence of type (UNSPEC), 257 * 'fmt' (NULL) when the output routine is not kval()/rval(). 258 * 259 * Called from main() on PS_CHECK_KEYWORDS, else available when debugging. 260 */ 261 void 262 check_keywords(void) 263 { 264 const VAR *k, *next_k; 265 bool order_violated = false; 266 267 k = &keywords[0]; 268 for (size_t i = 1; i < known_keywords_nb; ++i) { 269 next_k = &keywords[i]; 270 if (vcmp(k, next_k) >= 0) { 271 xo_warnx("keywords bad order: '%s' followed by '%s'", 272 k->name, next_k->name); 273 order_violated = true; 274 } 275 k = next_k; 276 } 277 if (order_violated) 278 /* Must be the case as we rely on bsearch() + vcmp(). */ 279 xo_errx(2, "keywords are not in ascending order " 280 "(internal error)"); 281 } 282 283 static void 284 alias_errx(const char *const name, const char *const what) 285 { 286 xo_errx(2, "alias keyword '%s' specifies %s (internal error)", 287 name, what); 288 } 289 290 static void 291 merge_alias(VAR *const k, VAR *const tgt) 292 { 293 if ((tgt->flag & RESOLVED_ALIAS) != 0) 294 k->final_kw = tgt->final_kw; 295 else { 296 k->final_kw = tgt; 297 assert(tgt->aliased == NULL); 298 } 299 300 #define MERGE_IF_SENTINEL(field, sentinel) do { \ 301 if (k->field == sentinel) \ 302 k->field = tgt->field; \ 303 } while (0) 304 305 MERGE_IF_SENTINEL(header, NULL); 306 MERGE_IF_SENTINEL(field, NULL); 307 /* If NOINHERIT is present, no merge occurs. */ 308 MERGE_IF_SENTINEL(flag, 0); 309 310 #undef MERGE_IF_SENTINEL 311 312 /* We also check that aliases don't specify things they should not. */ 313 #define MERGE_CHECK_SENTINEL(field, sentinel, field_descr) do { \ 314 if (k->field != sentinel) \ 315 alias_errx(k->name, field_descr); \ 316 k->field = tgt->field; \ 317 } while (0); 318 319 MERGE_CHECK_SENTINEL(oproc, NULL, "an output routine"); 320 MERGE_CHECK_SENTINEL(off, 0, "a structure offset"); 321 MERGE_CHECK_SENTINEL(type, UNSPEC, "a different type than UNSPEC"); 322 MERGE_CHECK_SENTINEL(fmt, NULL, "a printf format"); 323 324 #undef MERGE_CHECK_SENTINEL 325 } 326 327 static void 328 resolve_alias(VAR *const k) 329 { 330 VAR *t, key; 331 332 if ((k->flag & RESOLVED_ALIAS) != 0 || k->aliased == NULL) 333 return; 334 335 if ((k->flag & RESOLVING_ALIAS) != 0) 336 xo_errx(2, "cycle when resolving alias keyword '%s'", k->name); 337 k->flag |= RESOLVING_ALIAS; 338 339 key.name = k->aliased; 340 t = bsearch(&key, keywords, known_keywords_nb, sizeof(VAR), vcmp); 341 if (t == NULL) 342 xo_errx(2, "unknown target '%s' for keyword alias '%s'", 343 k->aliased, k->name); 344 345 resolve_alias(t); 346 merge_alias(k, t); 347 348 k->flag &= ~RESOLVING_ALIAS; 349 k->flag |= RESOLVED_ALIAS; 350 } 351 352 /* 353 * Resolve all aliases immediately. 354 * 355 * Called from main() on PS_CHECK_KEYWORDS, else available when debugging. 356 */ 357 void 358 resolve_aliases(void) 359 { 360 for (size_t i = 0; i < known_keywords_nb; ++i) 361 resolve_alias(&keywords[i]); 362 } 363 364 void 365 showkey(void) 366 { 367 const VAR *v; 368 const VAR *const end = keywords + known_keywords_nb; 369 const char *sep; 370 int i; 371 372 i = 0; 373 sep = ""; 374 xo_open_list("key"); 375 for (v = keywords; v < end; ++v) { 376 const char *const p = v->name; 377 const int len = strlen(p); 378 379 if (termwidth && (i += len + 1) > termwidth) { 380 i = len; 381 sep = "\n"; 382 } 383 xo_emit("{P:/%hs}{l:key/%hs}", sep, p); 384 sep = " "; 385 } 386 xo_emit("\n"); 387 xo_close_list("key"); 388 if (xo_finish() < 0) 389 xo_err(1, "stdout"); 390 } 391 392 void 393 parsefmt(const char *p, struct velisthead *const var_list, 394 const int user) 395 { 396 char *copy, *cp; 397 char *hdr_p, sep; 398 size_t sep_idx; 399 VAR *v, key; 400 struct varent *vent; 401 402 cp = copy = strdup(p); 403 if (copy == NULL) 404 xo_err(1, "strdup"); 405 406 sep = cp[0]; /* We only care if it's 0 or not here. */ 407 sep_idx = -1; 408 while (sep != '\0') { 409 cp += sep_idx + 1; 410 411 /* 412 * If an item contains an equals sign, it specifies a column 413 * header, may contain embedded separator characters and 414 * is always the last item. 415 */ 416 sep_idx = strcspn(cp, "= \t,\n"); 417 sep = cp[sep_idx]; 418 cp[sep_idx] = 0; 419 if (sep == '=') { 420 hdr_p = cp + sep_idx + 1; 421 sep = '\0'; /* No more keywords. */ 422 } else 423 hdr_p = NULL; 424 425 /* At this point, '*cp' is '\0' iff 'sep_idx' is 0. */ 426 if (*cp == '\0') { 427 /* 428 * Empty keyword. Skip it, and silently unless some 429 * header has been specified. 430 */ 431 if (hdr_p != NULL) 432 xo_warnx("empty keyword with header '%s'", 433 hdr_p); 434 continue; 435 } 436 437 /* Find the keyword. */ 438 key.name = cp; 439 v = bsearch(&key, keywords, 440 known_keywords_nb, sizeof(VAR), vcmp); 441 if (v == NULL) { 442 xo_warnx("%s: keyword not found", cp); 443 eval = 1; 444 continue; 445 } 446 447 #ifndef PS_CHECK_KEYWORDS 448 /* 449 * On PS_CHECK_KEYWORDS, this is not necessary as all aliases 450 * are resolved at startup in main() by calling 451 * resolve_aliases(). 452 */ 453 resolve_alias(v); 454 #endif 455 456 if ((vent = malloc(sizeof(struct varent))) == NULL) 457 xo_errx(1, "malloc failed"); 458 vent->header = v->header; 459 if (hdr_p) { 460 hdr_p = strdup(hdr_p); 461 if (hdr_p) 462 vent->header = hdr_p; 463 } 464 vent->width = strlen(vent->header); 465 vent->var = v; 466 vent->flags = user ? VE_KEEP : 0; 467 STAILQ_INSERT_TAIL(var_list, vent, next_ve); 468 } 469 470 free(copy); 471 472 if (STAILQ_EMPTY(var_list)) { 473 xo_warnx("no valid keywords; valid keywords:"); 474 showkey(); 475 exit(1); 476 } 477 } 478 479 static int 480 vcmp(const void *a, const void *b) 481 { 482 return (strcmp(((const VAR *)a)->name, ((const VAR *)b)->name)); 483 } 484