| 1 | /*	$NetBSD: keyword.c,v 1.56 2018/04/11 18:52:05 christos Exp $	*/ | 
| 2 |  | 
| 3 | /*- | 
| 4 |  * Copyright (c) 1990, 1993, 1994 | 
| 5 |  *	The Regents of the University of California.  All rights reserved. | 
| 6 |  * | 
| 7 |  * Redistribution and use in source and binary forms, with or without | 
| 8 |  * modification, are permitted provided that the following conditions | 
| 9 |  * are met: | 
| 10 |  * 1. Redistributions of source code must retain the above copyright | 
| 11 |  *    notice, this list of conditions and the following disclaimer. | 
| 12 |  * 2. Redistributions in binary form must reproduce the above copyright | 
| 13 |  *    notice, this list of conditions and the following disclaimer in the | 
| 14 |  *    documentation and/or other materials provided with the distribution. | 
| 15 |  * 3. Neither the name of the University nor the names of its contributors | 
| 16 |  *    may be used to endorse or promote products derived from this software | 
| 17 |  *    without specific prior written permission. | 
| 18 |  * | 
| 19 |  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND | 
| 20 |  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | 
| 21 |  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | 
| 22 |  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE | 
| 23 |  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | 
| 24 |  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | 
| 25 |  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | 
| 26 |  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | 
| 27 |  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | 
| 28 |  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | 
| 29 |  * SUCH DAMAGE. | 
| 30 |  */ | 
| 31 |  | 
| 32 | #include <sys/cdefs.h> | 
| 33 | #ifndef lint | 
| 34 | #if 0 | 
| 35 | static char sccsid[] = "@(#)keyword.c	8.5 (Berkeley) 4/2/94" ; | 
| 36 | #else | 
| 37 | __RCSID("$NetBSD: keyword.c,v 1.56 2018/04/11 18:52:05 christos Exp $" ); | 
| 38 | #endif | 
| 39 | #endif /* not lint */ | 
| 40 |  | 
| 41 | #include <sys/param.h> | 
| 42 | #include <sys/time.h> | 
| 43 | #include <sys/lwp.h> | 
| 44 | #include <sys/proc.h> | 
| 45 | #include <sys/resource.h> | 
| 46 | #include <sys/sysctl.h> | 
| 47 | #include <sys/ucred.h> | 
| 48 |  | 
| 49 | #include <err.h> | 
| 50 | #include <errno.h> | 
| 51 | #include <kvm.h> | 
| 52 | #include <stddef.h> | 
| 53 | #include <stdio.h> | 
| 54 | #include <stdlib.h> | 
| 55 | #include <string.h> | 
| 56 | #include <signal.h> | 
| 57 |  | 
| 58 | #include "ps.h" | 
| 59 |  | 
| 60 | static VAR *findvar(const char *); | 
| 61 | static int  vcmp(const void *, const void *); | 
| 62 |  | 
| 63 | #if 0 	/* kernel doesn't calculate these */ | 
| 64 | 	PUVAR("idrss" , "IDRSS" , 0, p_uru_idrss, UINT64, PRIu64), | 
| 65 | 	PUVAR("isrss" , "ISRSS" , 0, p_uru_isrss, UINT64, PRId64), | 
| 66 | 	PUVAR("ixrss" , "IXRSS" , 0, p_uru_ixrss, UINT64, PRId64), | 
| 67 | 	PUVAR("maxrss" , "MAXRSS" , 0, p_uru_maxrss, UINT64, PRIu64), | 
| 68 | #endif | 
| 69 |  | 
| 70 | /* Compute offset in common structures. */ | 
| 71 | #define	POFF(x)	offsetof(struct kinfo_proc2, x) | 
| 72 | #define	LOFF(x)	offsetof(struct kinfo_lwp, x) | 
| 73 |  | 
| 74 | #define	UIDFMT	"u" | 
| 75 | #define	UID(n1, n2, of) \ | 
| 76 | 	{ .name = n1, .header = n2, .flag = 0, .oproc = pvar, \ | 
| 77 | 	  .off = POFF(of), .type = UINT32, .fmt = UIDFMT } | 
| 78 | #define	GID(n1, n2, off)	UID(n1, n2, off) | 
| 79 |  | 
| 80 | #define	PIDFMT	"d" | 
| 81 | #define	PID(n1, n2, of) \ | 
| 82 | 	{ .name = n1, .header = n2, .flag = 0, .oproc = pvar, \ | 
| 83 | 	  .off = POFF(of), .type = INT32, .fmt = PIDFMT } | 
| 84 |  | 
| 85 | #define	LVAR(n1, n2, fl, of, ty, fm) \ | 
| 86 | 	{ .name = n1, .header = n2, .flag = (fl) | LWP, .oproc = pvar, \ | 
| 87 | 	  .off = LOFF(of), .type = ty, .fmt = fm } | 
| 88 | #define	PVAR(n1, n2, fl, of, ty, fm) \ | 
| 89 | 	{ .name = n1, .header = n2, .flag = (fl) | 0, .oproc = pvar, \ | 
| 90 | 	  .off = POFF(of), .type = ty, .fmt = fm } | 
| 91 | #define	PUVAR(n1, n2, fl, of, ty, fm) \ | 
| 92 | 	{ .name = n1, .header = n2, .flag = (fl) | UAREA, .oproc = pvar, \ | 
| 93 | 	  .off = POFF(of), .type = ty, .fmt = fm } | 
| 94 | #define VAR3(n1, n2, fl) \ | 
| 95 | 	{ .name = n1, .header = n2, .flag = fl } | 
| 96 | #define VAR4(n1, n2, fl, op) \ | 
| 97 | 	{ .name = n1, .header = n2, .flag = fl, .oproc = op, } | 
| 98 | #define VAR6(n1, n2, fl, op, of, ty) \ | 
| 99 | 	{ .name = n1, .header = n2, .flag = fl, .oproc = op, \ | 
| 100 | 	  .off = of, .type = ty } | 
| 101 |  | 
| 102 | /* NB: table must be sorted, in vi use: | 
| 103 |  *	:/^VAR/,/end_sort/! sort -t\" +1 | 
| 104 |  * breaking long lines just makes the sort harder | 
| 105 |  * | 
| 106 |  * We support all the fields required by P1003.1-2004 (SUSv3), with | 
| 107 |  * the correct default headers, except for the "tty" field, where the | 
| 108 |  * standard says the header should be "TT", but we have "TTY". | 
| 109 |  */ | 
| 110 | VAR var[] = { | 
| 111 | 	VAR6("%cpu" , "%CPU" , 0, pcpu, 0, PCPU), | 
| 112 | 	VAR6("%mem" , "%MEM" , 0, pmem, POFF(p_vm_rssize), INT32), | 
| 113 | 	PVAR("acflag" , "ACFLG" , 0, p_acflag, USHORT, "x" ), | 
| 114 | 	VAR3("acflg" , "acflag" , ALIAS), | 
| 115 | 	VAR3("args" , "command" , ALIAS), | 
| 116 | 	VAR3("blocked" , "sigmask" , ALIAS), | 
| 117 | 	VAR3("caught" , "sigcatch" , ALIAS), | 
| 118 | 	VAR4("comm" , "COMMAND" , COMM|ARGV0|LJUST, command), | 
| 119 | 	VAR4("command" , "COMMAND" , COMM|LJUST, command), | 
| 120 | 	PVAR("cpu" , "CPU" , 0, p_estcpu, UINT, "u" ), | 
| 121 | 	VAR4("cpuid" , "CPUID" , LWP, cpuid), | 
| 122 | 	VAR3("cputime" , "time" , ALIAS), | 
| 123 | 	VAR6("ctime" , "CTIME" , 0, putimeval, POFF(p_uctime_sec), TIMEVAL), | 
| 124 | 	GID("egid" , "EGID" , p_gid), | 
| 125 | 	VAR4("egroup" , "EGROUP" , LJUST, gname), | 
| 126 | 	VAR4("emul" , "EMUL" , LJUST, emul), | 
| 127 | 	VAR6("etime" , "ELAPSED" , 0, elapsed, POFF(p_ustart_sec), TIMEVAL), | 
| 128 | 	UID("euid" , "EUID" , p_uid), | 
| 129 | 	VAR4("euser" , "EUSER" , LJUST, usrname), | 
| 130 | 	PVAR("f" , "F" , 0, p_flag, INT, "x" ), | 
| 131 | 	VAR3("flags" , "f" , ALIAS), | 
| 132 | 	GID("gid" , "GID" , p_gid), | 
| 133 | 	VAR4("group" , "GROUP" , LJUST, gname), | 
| 134 | 	VAR4("groupnames" , "GROUPNAMES" , LJUST, groupnames), | 
| 135 | 	VAR4("groups" , "GROUPS" , LJUST, groups), | 
| 136 | 	/* holdcnt: unused, left for compat. */ | 
| 137 | 	LVAR("holdcnt" , "HOLDCNT" , 0, l_holdcnt, INT, "d" ), | 
| 138 | 	VAR3("ignored" , "sigignore" , ALIAS), | 
| 139 | 	PUVAR("inblk" , "INBLK" , 0, p_uru_inblock, UINT64, PRIu64), | 
| 140 | 	VAR3("inblock" , "inblk" , ALIAS), | 
| 141 | 	PVAR("jobc" , "JOBC" , 0, p_jobc, SHORT, "d" ), | 
| 142 | 	PVAR("ktrace" , "KTRACE" , 0, p_traceflag, INT, "x" ), | 
| 143 | /*XXX*/	PVAR("ktracep" , "KTRACEP" , 0, p_tracep, KPTR, PRIx64), | 
| 144 | 	LVAR("laddr" , "LADDR" , 0, l_laddr, KPTR, PRIx64), | 
| 145 | 	LVAR("lid" , "LID" , 0, l_lid, INT32, "d" ), | 
| 146 | 	VAR4("lim" , "LIM" , 0, maxrss), | 
| 147 | 	VAR4("lname" , "LNAME" , LJUST|LWP, lname), | 
| 148 | 	VAR4("login" , "LOGIN" , LJUST, logname), | 
| 149 | 	VAR3("logname" , "login" , ALIAS), | 
| 150 | 	VAR6("lstart" , "STARTED" , LJUST, lstarted, POFF(p_ustart_sec), UINT32), | 
| 151 | 	VAR4("lstate" , "STAT" , LJUST|LWP, lstate), | 
| 152 | 	VAR6("ltime" , "LTIME" , LWP, lcputime, 0, CPUTIME), | 
| 153 | 	PUVAR("majflt" , "MAJFLT" , 0, p_uru_majflt, UINT64, PRIu64), | 
| 154 | 	PUVAR("minflt" , "MINFLT" , 0, p_uru_minflt, UINT64, PRIu64), | 
| 155 | 	PUVAR("msgrcv" , "MSGRCV" , 0, p_uru_msgrcv, UINT64, PRIu64), | 
| 156 | 	PUVAR("msgsnd" , "MSGSND" , 0, p_uru_msgsnd, UINT64, PRIu64), | 
| 157 | 	VAR3("ni" , "nice" , ALIAS), | 
| 158 | 	VAR6("nice" , "NI" , 0, pnice, POFF(p_nice), UCHAR), | 
| 159 | 	PUVAR("nivcsw" , "NIVCSW" , 0, p_uru_nivcsw, UINT64, PRIu64), | 
| 160 | 	PVAR("nlwp" , "NLWP" , 0, p_nlwps, UINT64, PRId64), | 
| 161 | 	VAR3("nsignals" , "nsigs" , ALIAS), | 
| 162 | 	PUVAR("nsigs" , "NSIGS" , 0, p_uru_nsignals, UINT64, PRIu64), | 
| 163 | 	/* nswap: unused, left for compat. */ | 
| 164 | 	PUVAR("nswap" , "NSWAP" , 0, p_uru_nswap, UINT64, PRIu64), | 
| 165 | 	PUVAR("nvcsw" , "NVCSW" , 0, p_uru_nvcsw, UINT64, PRIu64), | 
| 166 | /*XXX*/	LVAR("nwchan" , "WCHAN" , 0, l_wchan, KPTR, PRIx64), | 
| 167 | 	PUVAR("oublk" , "OUBLK" , 0, p_uru_oublock, UINT64, PRIu64), | 
| 168 | 	VAR3("oublock" , "oublk" , ALIAS), | 
| 169 | /*XXX*/	PVAR("p_ru" , "P_RU" , 0, p_ru, KPTR, PRIx64), | 
| 170 | /*XXX*/	PVAR("paddr" , "PADDR" , 0, p_paddr, KPTR, PRIx64), | 
| 171 | 	PUVAR("pagein" , "PAGEIN" , 0, p_uru_majflt, UINT64, PRIu64), | 
| 172 | 	VAR3("pcpu" , "%cpu" , ALIAS), | 
| 173 | 	VAR3("pending" , "sig" , ALIAS), | 
| 174 | 	PID("pgid" , "PGID" , p__pgid), | 
| 175 | 	PID("pid" , "PID" , p_pid), | 
| 176 | 	VAR3("pmem" , "%mem" , ALIAS), | 
| 177 | 	PID("ppid" , "PPID" , p_ppid), | 
| 178 | 	VAR4("pri" , "PRI" , LWP, pri), | 
| 179 | 	LVAR("re" , "RE" , INF127, l_swtime, UINT, "u" ), | 
| 180 | 	GID("rgid" , "RGID" , p_rgid), | 
| 181 | 	VAR4("rgroup" , "RGROUP" , LJUST, rgname), | 
| 182 | /*XXX*/	LVAR("rlink" , "RLINK" , 0, l_back, KPTR, PRIx64), | 
| 183 | 	PVAR("rlwp" , "RLWP" , 0, p_nrlwps, UINT64, PRId64), | 
| 184 | 	VAR6("rss" , "RSS" , 0, p_rssize, POFF(p_vm_rssize), INT32), | 
| 185 | 	VAR3("rssize" , "rsz" , ALIAS), | 
| 186 | 	VAR6("rsz" , "RSZ" , 0, rssize, POFF(p_vm_rssize), INT32), | 
| 187 | 	UID("ruid" , "RUID" , p_ruid), | 
| 188 | 	VAR4("ruser" , "RUSER" , LJUST, runame), | 
| 189 | 	PVAR("sess" , "SESS" , 0, p_sess, KPTR24, PRIx64), | 
| 190 | 	PID("sid" , "SID" , p_sid), | 
| 191 | 	PVAR("sig" , "PENDING" , 0, p_siglist, SIGLIST, "s" ), | 
| 192 | 	PVAR("sigcatch" , "CAUGHT" , 0, p_sigcatch, SIGLIST, "s" ), | 
| 193 | 	PVAR("sigignore" , "IGNORED" , 0, p_sigignore, SIGLIST, "s" ), | 
| 194 | 	PVAR("sigmask" , "BLOCKED" , 0, p_sigmask, SIGLIST, "s" ), | 
| 195 | 	LVAR("sl" , "SL" , INF127, l_slptime, UINT, "u" ), | 
| 196 | 	VAR6("start" , "STARTED" , 0, started, POFF(p_ustart_sec), UINT32), | 
| 197 | 	VAR3("stat" , "state" , ALIAS), | 
| 198 | 	VAR4("state" , "STAT" , LJUST, state), | 
| 199 | 	VAR6("stime" , "STIME" , 0, putimeval, POFF(p_ustime_sec), TIMEVAL), | 
| 200 | 	GID("svgid" , "SVGID" , p_svgid), | 
| 201 | 	VAR4("svgroup" , "SVGROUP" , LJUST, svgname), | 
| 202 | 	UID("svuid" , "SVUID" , p_svuid), | 
| 203 | 	VAR4("svuser" , "SVUSER" , LJUST, svuname), | 
| 204 | 	/* "tdev" is UINT32, but we do this for sorting purposes */ | 
| 205 | 	VAR6("tdev" , "TDEV" , 0, tdev, POFF(p_tdev), INT32), | 
| 206 | 	VAR6("time" , "TIME" , 0, cputime, 0, CPUTIME), | 
| 207 | 	PID("tpgid" , "TPGID" , p_tpgid), | 
| 208 | 	PVAR("tsess" , "TSESS" , 0, p_tsess, KPTR, PRIx64), | 
| 209 | 	VAR6("tsiz" , "TSIZ" , 0, tsize, POFF(p_vm_tsize), INT32), | 
| 210 | 	VAR6("tt" , "TTY" , LJUST, tname, POFF(p_tdev), INT32), | 
| 211 | 	VAR6("tty" , "TTY" , LJUST, longtname, POFF(p_tdev), INT32), | 
| 212 | 	LVAR("uaddr" , "UADDR" , 0, l_addr, KPTR, PRIx64), | 
| 213 | 	VAR4("ucomm" , "UCOMM" , LJUST, ucomm), | 
| 214 | 	UID("uid" , "UID" , p_uid), | 
| 215 | 	LVAR("upr" , "UPR" , 0, l_usrpri, UCHAR, "u" ), | 
| 216 | 	VAR4("user" , "USER" , LJUST, usrname), | 
| 217 | 	VAR3("usrpri" , "upr" , ALIAS), | 
| 218 | 	VAR6("utime" , "UTIME" , 0, putimeval, POFF(p_uutime_sec), TIMEVAL), | 
| 219 | 	VAR3("vsize" , "vsz" , ALIAS), | 
| 220 | 	VAR6("vsz" , "VSZ" , 0, vsize, 0, VSIZE), | 
| 221 | 	VAR4("wchan" , "WCHAN" , LJUST|LWP, wchan), | 
| 222 | 	PVAR("xstat" , "XSTAT" , 0, p_xstat, USHORT, "x" ), | 
| 223 | /* "zzzz" end_sort */ | 
| 224 | 	{ .name = ""  }, | 
| 225 | }; | 
| 226 |  | 
| 227 | void | 
| 228 | showkey(void) | 
| 229 | { | 
| 230 | 	VAR *v; | 
| 231 | 	int i; | 
| 232 | 	const char *p; | 
| 233 | 	const char *sep; | 
| 234 |  | 
| 235 | 	i = 0; | 
| 236 | 	sep = "" ; | 
| 237 | 	for (v = var; *(p = v->name); ++v) { | 
| 238 | 		int len = strlen(p); | 
| 239 | 		if (termwidth && (i += len + 1) > termwidth) { | 
| 240 | 			i = len; | 
| 241 | 			sep = "\n" ; | 
| 242 | 		} | 
| 243 | 		(void)printf("%s%s" , sep, p); | 
| 244 | 		sep = " " ; | 
| 245 | 	} | 
| 246 | 	(void)printf("\n" ); | 
| 247 | } | 
| 248 |  | 
| 249 | /* | 
| 250 |  * Parse the string pp, and insert or append entries to the list | 
| 251 |  * referenced by listptr.  If pos in non-null and *pos is non-null, then | 
| 252 |  * *pos specifies where to insert (instead of appending).  If pos is | 
| 253 |  * non-null, then a new value is returned through *pos referring to the | 
| 254 |  * last item inserted. | 
| 255 |  */ | 
| 256 | static void | 
| 257 | parsevarlist(const char *pp, struct varlist *listptr, struct varent **pos) | 
| 258 | { | 
| 259 | 	char *p, *sp, *equalsp; | 
| 260 |  | 
| 261 | 	/* dup to avoid zapping arguments.  We will free sp later. */ | 
| 262 | 	p = sp = strdup(pp); | 
| 263 |  | 
| 264 | 	/* | 
| 265 | 	 * Everything after the first '=' is part of a custom header. | 
| 266 | 	 * Temporarily replace it with '\0' to simplify other code. | 
| 267 | 	 */ | 
| 268 | 	equalsp = strchr(p, '='); | 
| 269 | 	if (equalsp) | 
| 270 | 	    *equalsp = '\0'; | 
| 271 |  | 
| 272 | #define	FMTSEP	" \t,\n" | 
| 273 | 	while (p && *p) { | 
| 274 | 		char *cp; | 
| 275 | 		VAR *v; | 
| 276 | 		struct varent *vent; | 
| 277 |  | 
| 278 | 		/* | 
| 279 | 		 * skip separators before the first keyword, and | 
| 280 | 		 * look for the separator after the keyword. | 
| 281 | 		 */ | 
| 282 | 		for (cp = p; *cp != '\0'; cp++) { | 
| 283 | 		    p = strpbrk(cp, FMTSEP); | 
| 284 | 		    if (p != cp) | 
| 285 | 			break; | 
| 286 | 		} | 
| 287 | 		if (*cp == '\0') | 
| 288 | 		    break; | 
| 289 | 		/* | 
| 290 | 		 * Now cp points to the start of a keyword, | 
| 291 | 		 * and p is NULL or points past the end of the keyword. | 
| 292 | 		 * | 
| 293 | 		 * Terminate the keyword with '\0', or reinstate the | 
| 294 | 		 * '=' that was removed earlier, if appropriate. | 
| 295 | 		 */ | 
| 296 | 		if (p) { | 
| 297 | 			*p = '\0'; | 
| 298 | 			p++; | 
| 299 | 		} else if (equalsp) { | 
| 300 | 			*equalsp = '='; | 
| 301 | 		} | 
| 302 |  | 
| 303 | 		/* | 
| 304 | 		 * If findvar() likes the keyword or keyword=header, | 
| 305 | 		 * add it to our list.  If findvar() doesn't like it, | 
| 306 | 		 * it will print a warning, so we ignore it. | 
| 307 | 		 */ | 
| 308 | 		if ((v = findvar(cp)) == NULL) | 
| 309 | 			continue; | 
| 310 | 		if ((vent = malloc(sizeof(struct varent))) == NULL) | 
| 311 | 			err(EXIT_FAILURE, NULL); | 
| 312 | 		vent->var = v; | 
| 313 | 		if (pos && *pos) | 
| 314 | 		    SIMPLEQ_INSERT_AFTER(listptr, *pos, vent, next); | 
| 315 | 		else { | 
| 316 | 		    SIMPLEQ_INSERT_TAIL(listptr, vent, next); | 
| 317 | 		} | 
| 318 | 		if (pos) | 
| 319 | 		    *pos = vent; | 
| 320 | 	} | 
| 321 |  	free(sp); | 
| 322 | 	if (SIMPLEQ_EMPTY(listptr)) | 
| 323 | 		errx(EXIT_FAILURE, "no valid keywords" ); | 
| 324 | } | 
| 325 |  | 
| 326 | void | 
| 327 | parsefmt(const char *p) | 
| 328 | { | 
| 329 |  | 
| 330 | 	parsevarlist(p, &displaylist, NULL); | 
| 331 | } | 
| 332 |  | 
| 333 | void | 
| 334 | parsefmt_insert(const char *p, struct varent **pos) | 
| 335 | { | 
| 336 |  | 
| 337 | 	parsevarlist(p, &displaylist, pos); | 
| 338 | } | 
| 339 |  | 
| 340 | void | 
| 341 | parsesort(const char *p) | 
| 342 | { | 
| 343 |  | 
| 344 | 	parsevarlist(p, &sortlist, NULL); | 
| 345 | } | 
| 346 |  | 
| 347 | /* Search through a list for an entry with a specified name. */ | 
| 348 | struct varent * | 
| 349 | varlist_find(struct varlist *list, const char *name) | 
| 350 | { | 
| 351 | 	struct varent *vent; | 
| 352 |  | 
| 353 | 	SIMPLEQ_FOREACH(vent, list, next) { | 
| 354 | 		if (strcmp(vent->var->name, name) == 0) | 
| 355 | 			break; | 
| 356 | 	} | 
| 357 | 	return vent; | 
| 358 | } | 
| 359 |  | 
| 360 | static VAR * | 
| 361 | findvar(const char *p) | 
| 362 | { | 
| 363 | 	VAR *v; | 
| 364 | 	char *hp; | 
| 365 |  | 
| 366 | 	hp = strchr(p, '='); | 
| 367 | 	if (hp) | 
| 368 | 		*hp++ = '\0'; | 
| 369 |  | 
| 370 | 	v = bsearch(p, var, sizeof(var)/sizeof(VAR) - 1, sizeof(VAR), vcmp); | 
| 371 | 	if (v && v->flag & ALIAS) | 
| 372 | 		v = findvar(v->header); | 
| 373 | 	if (!v) { | 
| 374 | 		warnx("%s: keyword not found" , p); | 
| 375 | 		eval = 1; | 
| 376 | 		return NULL; | 
| 377 | 	} | 
| 378 |  | 
| 379 | 	if (v && hp) { | 
| 380 | 		/* | 
| 381 | 		 * Override the header. | 
| 382 | 		 * | 
| 383 | 		 * We need to copy the entry first, and override the | 
| 384 | 		 * header in the copy, because the same field might be | 
| 385 | 		 * used multiple times with different headers.  We also | 
| 386 | 		 * need to strdup the header. | 
| 387 | 		 */ | 
| 388 | 		struct var *newvar; | 
| 389 | 		char *; | 
| 390 |  | 
| 391 | 		if ((newvar = malloc(sizeof(struct var))) == NULL) | 
| 392 | 			err(EXIT_FAILURE, NULL); | 
| 393 | 		if ((newheader = strdup(hp)) == NULL) | 
| 394 | 			err(EXIT_FAILURE, NULL); | 
| 395 | 		memcpy(newvar, v, sizeof(struct var)); | 
| 396 | 		newvar->header = newheader; | 
| 397 |  | 
| 398 | 		/* | 
| 399 | 		 * According to P1003.1-2004, if the header text is null, | 
| 400 | 		 * such as -o user=, the field width will be at least as | 
| 401 | 		 * wide as the default header text. | 
| 402 | 		 */ | 
| 403 | 		if (*hp == '\0') | 
| 404 | 			newvar->width = strlen(v->header); | 
| 405 |  | 
| 406 | 		v = newvar; | 
| 407 | 	} | 
| 408 | 	return v; | 
| 409 | } | 
| 410 |  | 
| 411 | static int | 
| 412 | vcmp(const void *a, const void *b) | 
| 413 | { | 
| 414 |         return strcmp(a, ((const VAR *)b)->name); | 
| 415 | } | 
| 416 |  |