pkill.c revision 127425
1127412Sgad/*	$NetBSD: pkill.c,v 1.7 2004/02/15 17:03:30 soren Exp $	*/
2127412Sgad
3127412Sgad/*-
4127412Sgad * Copyright (c) 2002 The NetBSD Foundation, Inc.
5127412Sgad * All rights reserved.
6127412Sgad *
7127412Sgad * This code is derived from software contributed to The NetBSD Foundation
8127412Sgad * by Andrew Doran.
9127412Sgad *
10127412Sgad * Redistribution and use in source and binary forms, with or without
11127412Sgad * modification, are permitted provided that the following conditions
12127412Sgad * are met:
13127412Sgad * 1. Redistributions of source code must retain the above copyright
14127412Sgad *    notice, this list of conditions and the following disclaimer.
15127412Sgad * 2. Redistributions in binary form must reproduce the above copyright
16127412Sgad *    notice, this list of conditions and the following disclaimer in the
17127412Sgad *    documentation and/or other materials provided with the distribution.
18127412Sgad * 3. All advertising materials mentioning features or use of this software
19127412Sgad *    must display the following acknowledgement:
20127412Sgad *	This product includes software developed by the NetBSD
21127412Sgad *	Foundation, Inc. and its contributors.
22127412Sgad * 4. Neither the name of The NetBSD Foundation nor the names of its
23127412Sgad *    contributors may be used to endorse or promote products derived
24127412Sgad *    from this software without specific prior written permission.
25127412Sgad *
26127412Sgad * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
27127412Sgad * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
28127412Sgad * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
29127412Sgad * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
30127412Sgad * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
31127412Sgad * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
32127412Sgad * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
33127412Sgad * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
34127412Sgad * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35127412Sgad * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
36127412Sgad * POSSIBILITY OF SUCH DAMAGE.
37127412Sgad */
38127412Sgad
39127412Sgad#include <sys/cdefs.h>
40127412Sgad__FBSDID("$FreeBSD: head/usr.bin/pkill/pkill.c 127425 2004-03-25 21:58:56Z gad $");
41127412Sgad
42127412Sgad#include <sys/types.h>
43127412Sgad#include <sys/param.h>
44127412Sgad#include <sys/sysctl.h>
45127412Sgad#include <sys/proc.h>
46127412Sgad#include <sys/queue.h>
47127412Sgad#include <sys/stat.h>
48127425Sgad#include <sys/user.h>
49127412Sgad
50127412Sgad#include <stdio.h>
51127412Sgad#include <stdlib.h>
52127412Sgad#include <limits.h>
53127425Sgad#include <paths.h>
54127412Sgad#include <string.h>
55127412Sgad#include <unistd.h>
56127412Sgad#include <signal.h>
57127412Sgad#include <regex.h>
58127412Sgad#include <ctype.h>
59127425Sgad#include <fcntl.h>
60127412Sgad#include <kvm.h>
61127412Sgad#include <err.h>
62127412Sgad#include <pwd.h>
63127412Sgad#include <grp.h>
64127412Sgad#include <errno.h>
65127412Sgad
66127412Sgad#define	STATUS_MATCH	0
67127412Sgad#define	STATUS_NOMATCH	1
68127412Sgad#define	STATUS_BADUSAGE	2
69127412Sgad#define	STATUS_ERROR	3
70127412Sgad
71127425Sgad#if defined(__FreeBSD__)
72127425Sgad# if __FreeBSD_version < 300000
73127425Sgad#  define	NEED_KMEM
74127425Sgad# endif
75127425Sgad#endif
76127425Sgad
77127412Sgadenum listtype {
78127412Sgad	LT_GENERIC,
79127412Sgad	LT_USER,
80127412Sgad	LT_GROUP,
81127412Sgad	LT_TTY,
82127412Sgad	LT_PGRP,
83127412Sgad	LT_SID
84127412Sgad};
85127412Sgad
86127412Sgadstruct list {
87127412Sgad	SLIST_ENTRY(list) li_chain;
88127412Sgad	long	li_number;
89127412Sgad};
90127412Sgad
91127412SgadSLIST_HEAD(listhead, list);
92127412Sgad
93127425Sgadstruct kinfo_proc	*plist;
94127412Sgadchar	*selected;
95127412Sgadchar	*delim = "\n";
96127412Sgadint	nproc;
97127412Sgadint	pgrep;
98127412Sgadint	signum = SIGTERM;
99127412Sgadint	newest;
100127412Sgadint	inverse;
101127412Sgadint	longfmt;
102127412Sgadint	matchargs;
103127412Sgadint	fullmatch;
104127412Sgadkvm_t	*kd;
105127412Sgadpid_t	mypid;
106127412Sgad
107127412Sgadstruct listhead euidlist = SLIST_HEAD_INITIALIZER(list);
108127412Sgadstruct listhead ruidlist = SLIST_HEAD_INITIALIZER(list);
109127412Sgadstruct listhead rgidlist = SLIST_HEAD_INITIALIZER(list);
110127412Sgadstruct listhead pgrplist = SLIST_HEAD_INITIALIZER(list);
111127412Sgadstruct listhead ppidlist = SLIST_HEAD_INITIALIZER(list);
112127412Sgadstruct listhead tdevlist = SLIST_HEAD_INITIALIZER(list);
113127412Sgadstruct listhead sidlist = SLIST_HEAD_INITIALIZER(list);
114127412Sgad
115127412Sgadint	main(int, char **);
116127412Sgadvoid	usage(void);
117127425Sgadvoid	killact(struct kinfo_proc *);
118127425Sgadvoid	grepact(struct kinfo_proc *);
119127412Sgadvoid	makelist(struct listhead *, enum listtype, char *);
120127412Sgad
121127412Sgadint
122127412Sgadmain(int argc, char **argv)
123127412Sgad{
124127412Sgad	extern char *optarg;
125127412Sgad	extern int optind;
126127412Sgad	char buf[_POSIX2_LINE_MAX], *mstr, **pargv, *p, *q;
127127425Sgad	char *execf, *coref, *swapf;
128127412Sgad	int i, j, ch, bestidx, rv, criteria;
129127425Sgad	void (*action)(struct kinfo_proc *);
130127425Sgad	struct kinfo_proc *kp;
131127412Sgad	struct list *li;
132127412Sgad	u_int32_t bestsec, bestusec;
133127412Sgad	regex_t reg;
134127412Sgad	regmatch_t regmatch;
135127412Sgad
136127412Sgad	if (strcmp(getprogname(), "pgrep") == 0) {
137127412Sgad		action = grepact;
138127412Sgad		pgrep = 1;
139127412Sgad	} else {
140127412Sgad		action = killact;
141127412Sgad		p = argv[1];
142127412Sgad
143127412Sgad		if (argc > 1 && p[0] == '-') {
144127412Sgad			p++;
145127412Sgad			i = (int)strtol(p, &q, 10);
146127412Sgad			if (*q == '\0') {
147127412Sgad				signum = i;
148127412Sgad				argv++;
149127412Sgad				argc--;
150127412Sgad			} else {
151127412Sgad				if (strncasecmp(p, "sig", 3) == 0)
152127412Sgad					p += 3;
153127412Sgad				for (i = 1; i < NSIG; i++)
154127412Sgad					if (strcasecmp(sys_signame[i], p) == 0)
155127412Sgad						break;
156127412Sgad				if (i != NSIG) {
157127412Sgad					signum = i;
158127412Sgad					argv++;
159127412Sgad					argc--;
160127412Sgad				}
161127412Sgad			}
162127412Sgad		}
163127412Sgad	}
164127412Sgad
165127425Sgad#if defined(NEED_KMEM)
166127425Sgad	execf = coref = swapf = NULL;
167127425Sgad#else
168127425Sgad	execf = coref = swapf = _PATH_DEVNULL;
169127425Sgad#endif
170127425Sgad
171127412Sgad	criteria = 0;
172127412Sgad
173127412Sgad	while ((ch = getopt(argc, argv, "G:P:U:d:fg:lns:t:u:vx")) != -1)
174127412Sgad		switch (ch) {
175127412Sgad		case 'G':
176127412Sgad			makelist(&rgidlist, LT_GROUP, optarg);
177127412Sgad			criteria = 1;
178127412Sgad			break;
179127412Sgad		case 'P':
180127412Sgad			makelist(&ppidlist, LT_GENERIC, optarg);
181127412Sgad			criteria = 1;
182127412Sgad			break;
183127412Sgad		case 'U':
184127412Sgad			makelist(&ruidlist, LT_USER, optarg);
185127412Sgad			criteria = 1;
186127412Sgad			break;
187127412Sgad		case 'd':
188127412Sgad			if (!pgrep)
189127412Sgad				usage();
190127412Sgad			delim = optarg;
191127412Sgad			break;
192127412Sgad		case 'f':
193127412Sgad			matchargs = 1;
194127412Sgad			break;
195127412Sgad		case 'g':
196127412Sgad			makelist(&pgrplist, LT_PGRP, optarg);
197127412Sgad			criteria = 1;
198127412Sgad			break;
199127412Sgad		case 'l':
200127412Sgad			if (!pgrep)
201127412Sgad				usage();
202127412Sgad			longfmt = 1;
203127412Sgad			break;
204127412Sgad		case 'n':
205127412Sgad			newest = 1;
206127412Sgad			criteria = 1;
207127412Sgad			break;
208127412Sgad		case 's':
209127412Sgad			makelist(&sidlist, LT_SID, optarg);
210127412Sgad			criteria = 1;
211127412Sgad			break;
212127412Sgad		case 't':
213127412Sgad			makelist(&tdevlist, LT_TTY, optarg);
214127412Sgad			criteria = 1;
215127412Sgad			break;
216127412Sgad		case 'u':
217127412Sgad			makelist(&euidlist, LT_USER, optarg);
218127412Sgad			criteria = 1;
219127412Sgad			break;
220127412Sgad		case 'v':
221127412Sgad			inverse = 1;
222127412Sgad			break;
223127412Sgad		case 'x':
224127412Sgad			fullmatch = 1;
225127412Sgad			break;
226127412Sgad		default:
227127412Sgad			usage();
228127412Sgad			/* NOTREACHED */
229127412Sgad		}
230127412Sgad
231127412Sgad	argc -= optind;
232127412Sgad	argv += optind;
233127412Sgad	if (argc != 0)
234127412Sgad		criteria = 1;
235127412Sgad	if (!criteria)
236127412Sgad		usage();
237127412Sgad
238127412Sgad	mypid = getpid();
239127412Sgad
240127412Sgad	/*
241127412Sgad	 * Retrieve the list of running processes from the kernel.
242127412Sgad	 */
243127425Sgad	kd = kvm_openfiles(execf, coref, swapf, O_RDONLY, buf);
244127412Sgad	if (kd == NULL)
245127412Sgad		errx(STATUS_ERROR, "kvm_openfiles(): %s", buf);
246127412Sgad
247127425Sgad	plist = kvm_getprocs(kd, KERN_PROC_ALL, 0, &nproc);
248127412Sgad	if (plist == NULL)
249127425Sgad		errx(STATUS_ERROR, "kvm_getprocs() failed");
250127412Sgad
251127412Sgad	/*
252127412Sgad	 * Allocate memory which will be used to keep track of the
253127412Sgad	 * selection.
254127412Sgad	 */
255127412Sgad	if ((selected = malloc(nproc)) == NULL)
256127412Sgad		errx(STATUS_ERROR, "memory allocation failure");
257127412Sgad	memset(selected, 0, nproc);
258127412Sgad
259127412Sgad	/*
260127412Sgad	 * Refine the selection.
261127412Sgad	 */
262127412Sgad	for (; *argv != NULL; argv++) {
263127412Sgad		if ((rv = regcomp(&reg, *argv, REG_EXTENDED)) != 0) {
264127412Sgad			regerror(rv, &reg, buf, sizeof(buf));
265127412Sgad			errx(STATUS_BADUSAGE, "bad expression: %s", buf);
266127412Sgad		}
267127412Sgad
268127412Sgad		for (i = 0, kp = plist; i < nproc; i++, kp++) {
269127425Sgad			if ((kp->ki_flag & P_SYSTEM) != 0 || kp->ki_pid == mypid)
270127412Sgad				continue;
271127412Sgad
272127412Sgad			if (matchargs) {
273127425Sgad				if ((pargv = kvm_getargv(kd, kp, 0)) == NULL)
274127412Sgad					continue;
275127412Sgad
276127412Sgad				j = 0;
277127412Sgad				while (j < sizeof(buf) && *pargv != NULL) {
278127412Sgad					j += snprintf(buf + j, sizeof(buf) - j,
279127412Sgad					    pargv[1] != NULL ? "%s " : "%s",
280127412Sgad					    pargv[0]);
281127412Sgad					pargv++;
282127412Sgad				}
283127412Sgad
284127412Sgad				mstr = buf;
285127412Sgad			} else
286127425Sgad				mstr = kp->ki_comm;
287127412Sgad
288127412Sgad			rv = regexec(&reg, mstr, 1, &regmatch, 0);
289127412Sgad			if (rv == 0) {
290127412Sgad				if (fullmatch) {
291127412Sgad					if (regmatch.rm_so == 0 &&
292127412Sgad					    regmatch.rm_eo == strlen(mstr))
293127412Sgad						selected[i] = 1;
294127412Sgad				} else
295127412Sgad					selected[i] = 1;
296127412Sgad			} else if (rv != REG_NOMATCH) {
297127412Sgad				regerror(rv, &reg, buf, sizeof(buf));
298127412Sgad				errx(STATUS_ERROR, "regexec(): %s", buf);
299127412Sgad			}
300127412Sgad		}
301127412Sgad
302127412Sgad		regfree(&reg);
303127412Sgad	}
304127412Sgad
305127412Sgad	for (i = 0, kp = plist; i < nproc; i++, kp++) {
306127425Sgad		if ((kp->ki_flag & P_SYSTEM) != 0)
307127412Sgad			continue;
308127412Sgad
309127412Sgad		SLIST_FOREACH(li, &ruidlist, li_chain)
310127425Sgad			if (kp->ki_ruid == (uid_t)li->li_number)
311127412Sgad				break;
312127412Sgad		if (SLIST_FIRST(&ruidlist) != NULL && li == NULL) {
313127412Sgad			selected[i] = 0;
314127412Sgad			continue;
315127412Sgad		}
316127412Sgad
317127412Sgad		SLIST_FOREACH(li, &rgidlist, li_chain)
318127425Sgad			if (kp->ki_rgid == (gid_t)li->li_number)
319127412Sgad				break;
320127412Sgad		if (SLIST_FIRST(&rgidlist) != NULL && li == NULL) {
321127412Sgad			selected[i] = 0;
322127412Sgad			continue;
323127412Sgad		}
324127412Sgad
325127412Sgad		SLIST_FOREACH(li, &euidlist, li_chain)
326127425Sgad			if (kp->ki_uid == (uid_t)li->li_number)
327127412Sgad				break;
328127412Sgad		if (SLIST_FIRST(&euidlist) != NULL && li == NULL) {
329127412Sgad			selected[i] = 0;
330127412Sgad			continue;
331127412Sgad		}
332127412Sgad
333127412Sgad		SLIST_FOREACH(li, &ppidlist, li_chain)
334127425Sgad			if (kp->ki_ppid == (uid_t)li->li_number)
335127412Sgad				break;
336127412Sgad		if (SLIST_FIRST(&ppidlist) != NULL && li == NULL) {
337127412Sgad			selected[i] = 0;
338127412Sgad			continue;
339127412Sgad		}
340127412Sgad
341127412Sgad		SLIST_FOREACH(li, &pgrplist, li_chain)
342127425Sgad			if (kp->ki_pgid == (uid_t)li->li_number)
343127412Sgad				break;
344127412Sgad		if (SLIST_FIRST(&pgrplist) != NULL && li == NULL) {
345127412Sgad			selected[i] = 0;
346127412Sgad			continue;
347127412Sgad		}
348127412Sgad
349127412Sgad		SLIST_FOREACH(li, &tdevlist, li_chain) {
350127412Sgad			if (li->li_number == -1 &&
351127425Sgad			    (kp->ki_flag & P_CONTROLT) == 0)
352127412Sgad				break;
353127425Sgad			if (kp->ki_tdev == (uid_t)li->li_number)
354127412Sgad				break;
355127412Sgad		}
356127412Sgad		if (SLIST_FIRST(&tdevlist) != NULL && li == NULL) {
357127412Sgad			selected[i] = 0;
358127412Sgad			continue;
359127412Sgad		}
360127412Sgad
361127412Sgad		SLIST_FOREACH(li, &sidlist, li_chain)
362127425Sgad			if (kp->ki_sid == (uid_t)li->li_number)
363127412Sgad				break;
364127412Sgad		if (SLIST_FIRST(&sidlist) != NULL && li == NULL) {
365127412Sgad			selected[i] = 0;
366127412Sgad			continue;
367127412Sgad		}
368127412Sgad
369127412Sgad		if (argc == 0)
370127412Sgad			selected[i] = 1;
371127412Sgad	}
372127412Sgad
373127412Sgad	if (newest) {
374127412Sgad		bestsec = 0;
375127412Sgad		bestusec = 0;
376127412Sgad		bestidx = -1;
377127412Sgad
378127412Sgad		for (i = 0, kp = plist; i < nproc; i++, kp++) {
379127412Sgad			if (!selected[i])
380127412Sgad				continue;
381127412Sgad
382127425Sgad			if (kp->ki_start.tv_sec > bestsec ||
383127425Sgad			    (kp->ki_start.tv_sec == bestsec
384127425Sgad			    && kp->ki_start.tv_usec> bestusec)) {
385127425Sgad				bestsec = kp->ki_start.tv_sec;
386127425Sgad				bestusec = kp->ki_start.tv_usec;
387127412Sgad				bestidx = i;
388127412Sgad			}
389127412Sgad		}
390127412Sgad
391127412Sgad		memset(selected, 0, nproc);
392127412Sgad		if (bestidx != -1)
393127412Sgad			selected[bestidx] = 1;
394127412Sgad	}
395127412Sgad
396127412Sgad	/*
397127412Sgad	 * Take the appropriate action for each matched process, if any.
398127412Sgad	 */
399127412Sgad	for (i = 0, rv = 0, kp = plist; i < nproc; i++, kp++) {
400127425Sgad		if (kp->ki_pid == mypid)
401127412Sgad			continue;
402127412Sgad		if (selected[i]) {
403127412Sgad			if (inverse)
404127412Sgad				continue;
405127412Sgad		} else if (!inverse)
406127412Sgad			continue;
407127412Sgad
408127425Sgad		if ((kp->ki_flag & P_SYSTEM) != 0)
409127412Sgad			continue;
410127412Sgad
411127412Sgad		rv = 1;
412127412Sgad		(*action)(kp);
413127412Sgad	}
414127412Sgad
415127412Sgad	exit(rv ? STATUS_MATCH : STATUS_NOMATCH);
416127412Sgad}
417127412Sgad
418127412Sgadvoid
419127412Sgadusage(void)
420127412Sgad{
421127412Sgad	const char *ustr;
422127412Sgad
423127412Sgad	if (pgrep)
424127412Sgad		ustr = "[-flnvx] [-d delim]";
425127412Sgad	else
426127412Sgad		ustr = "[-signal] [-fnvx]";
427127412Sgad
428127412Sgad	fprintf(stderr,
429127412Sgad		"usage: %s %s [-G gid] [-P ppid] [-U uid] [-g pgrp] [-s sid]\n"
430127412Sgad		"             [-t tty] [-u euid] pattern ...\n", getprogname(),
431127412Sgad		ustr);
432127412Sgad
433127412Sgad	exit(STATUS_ERROR);
434127412Sgad}
435127412Sgad
436127412Sgadvoid
437127425Sgadkillact(struct kinfo_proc *kp)
438127412Sgad{
439127412Sgad
440127425Sgad	if (kill(kp->ki_pid, signum) == -1)
441127425Sgad		err(STATUS_ERROR, "signalling pid %d", (int)kp->ki_pid);
442127412Sgad}
443127412Sgad
444127412Sgadvoid
445127425Sgadgrepact(struct kinfo_proc *kp)
446127412Sgad{
447127412Sgad	char **argv;
448127412Sgad
449127412Sgad	if (longfmt && matchargs) {
450127425Sgad		if ((argv = kvm_getargv(kd, kp, 0)) == NULL)
451127412Sgad			return;
452127412Sgad
453127425Sgad		printf("%d ", (int)kp->ki_pid);
454127412Sgad		for (; *argv != NULL; argv++) {
455127412Sgad			printf("%s", *argv);
456127412Sgad			if (argv[1] != NULL)
457127412Sgad				putchar(' ');
458127412Sgad		}
459127412Sgad	} else if (longfmt)
460127425Sgad		printf("%d %s", (int)kp->ki_pid, kp->ki_comm);
461127412Sgad	else
462127425Sgad		printf("%d", (int)kp->ki_pid);
463127412Sgad
464127412Sgad	printf("%s", delim);
465127412Sgad}
466127412Sgad
467127412Sgadvoid
468127412Sgadmakelist(struct listhead *head, enum listtype type, char *src)
469127412Sgad{
470127412Sgad	struct list *li;
471127412Sgad	struct passwd *pw;
472127412Sgad	struct group *gr;
473127412Sgad	struct stat st;
474127412Sgad	char *sp, *p, buf[MAXPATHLEN];
475127412Sgad	int empty;
476127412Sgad
477127412Sgad	empty = 1;
478127412Sgad
479127412Sgad	while ((sp = strsep(&src, ",")) != NULL) {
480127412Sgad		if (*sp == '\0')
481127412Sgad			usage();
482127412Sgad
483127412Sgad		if ((li = malloc(sizeof(*li))) == NULL)
484127412Sgad			errx(STATUS_ERROR, "memory allocation failure");
485127412Sgad		SLIST_INSERT_HEAD(head, li, li_chain);
486127412Sgad		empty = 0;
487127412Sgad
488127412Sgad		li->li_number = (uid_t)strtol(sp, &p, 0);
489127412Sgad		if (*p == '\0') {
490127412Sgad			switch (type) {
491127412Sgad			case LT_PGRP:
492127412Sgad				if (li->li_number == 0)
493127412Sgad					li->li_number = getpgrp();
494127412Sgad				break;
495127412Sgad			case LT_SID:
496127412Sgad				if (li->li_number == 0)
497127412Sgad					li->li_number = getsid(mypid);
498127412Sgad				break;
499127412Sgad			case LT_TTY:
500127412Sgad				usage();
501127412Sgad			default:
502127412Sgad				break;
503127412Sgad			}
504127412Sgad			continue;
505127412Sgad		}
506127412Sgad
507127412Sgad		switch (type) {
508127412Sgad		case LT_USER:
509127412Sgad			if ((pw = getpwnam(sp)) == NULL)
510127412Sgad				errx(STATUS_BADUSAGE, "unknown user `%s'",
511127412Sgad				    optarg);
512127412Sgad			li->li_number = pw->pw_uid;
513127412Sgad			break;
514127412Sgad		case LT_GROUP:
515127412Sgad			if ((gr = getgrnam(sp)) == NULL)
516127412Sgad				errx(STATUS_BADUSAGE, "unknown group `%s'",
517127412Sgad				    optarg);
518127412Sgad			li->li_number = gr->gr_gid;
519127412Sgad			break;
520127412Sgad		case LT_TTY:
521127412Sgad			if (strcmp(sp, "-") == 0) {
522127412Sgad				li->li_number = -1;
523127412Sgad				break;
524127412Sgad			} else if (strcmp(sp, "co") == 0)
525127412Sgad				p = "console";
526127412Sgad			else if (strncmp(sp, "tty", 3) == 0)
527127412Sgad				p = sp;
528127412Sgad			else
529127412Sgad				p = NULL;
530127412Sgad
531127412Sgad			if (p == NULL)
532127412Sgad				snprintf(buf, sizeof(buf), "/dev/tty%s", sp);
533127412Sgad			else
534127412Sgad				snprintf(buf, sizeof(buf), "/dev/%s", p);
535127412Sgad
536127412Sgad			if (stat(buf, &st) < 0) {
537127412Sgad				if (errno == ENOENT)
538127412Sgad					errx(STATUS_BADUSAGE,
539127412Sgad					    "no such tty: `%s'", sp);
540127412Sgad				err(STATUS_ERROR, "stat(%s)", sp);
541127412Sgad			}
542127412Sgad
543127412Sgad			if ((st.st_mode & S_IFCHR) == 0)
544127412Sgad				errx(STATUS_BADUSAGE, "not a tty: `%s'", sp);
545127412Sgad
546127412Sgad			li->li_number = st.st_rdev;
547127412Sgad			break;
548127412Sgad		default:
549127412Sgad			usage();
550127412Sgad		};
551127412Sgad	}
552127412Sgad
553127412Sgad	if (empty)
554127412Sgad		usage();
555127412Sgad}
556