Mercurial > illumos > git > illumos-omnios
changeset 22337:d38bf5c16630
11906 Add line drawing characters to ptree(1)
Reviewed by: Andy Fiddaman <omnios@citrus-it.co.uk>
Approved by: Dan McDonald <danmcd@joyent.com>
author | Jason King <jason.king@joyent.com> |
---|---|
date | Wed, 30 Oct 2019 23:20:17 -0500 |
parents | e46ab989f580 |
children | 8c43a7d38405 |
files | usr/src/cmd/ptools/Makefile.bld usr/src/cmd/ptools/ptree/ptree.c usr/src/man/man1/ptree.1 |
diffstat | 3 files changed, 278 insertions(+), 173 deletions(-) [+] |
line wrap: on
line diff
--- a/usr/src/cmd/ptools/Makefile.bld Mon Nov 04 15:41:15 2019 +0000 +++ b/usr/src/cmd/ptools/Makefile.bld Wed Oct 30 23:20:17 2019 -0500 @@ -23,7 +23,7 @@ # Copyright 2009 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # -# Copyright (c) 2018, Joyent, Inc. +# Copyright 2019 Joyent, Inc. PROG:sh = basename `cd ..; pwd` @@ -101,6 +101,9 @@ OBJS += $(OBJS_$(PROG)) SRCS += $(SRCS_$(PROG)) +$(OBJS_ptree) := CSTD = $(CSTD_GNU99) +ptree := CSTD = $(CSTD_GNU99) + INSTALL_NEW= INSTALL_LEGACY=$(RM) $(ROOTPROCBINSYMLINK) ; \ $(LN) -s ../../bin/$(PROG) $(ROOTPROCBINSYMLINK)
--- a/usr/src/cmd/ptools/ptree/ptree.c Mon Nov 04 15:41:15 2019 +0000 +++ b/usr/src/cmd/ptools/ptree/ptree.c Wed Oct 30 23:20:17 2019 -0500 @@ -32,7 +32,9 @@ #include <stdio.h> #include <string.h> #include <errno.h> +#include <err.h> #include <fcntl.h> +#include <sys/debug.h> #include <sys/types.h> #include <sys/termios.h> #include <unistd.h> @@ -43,13 +45,17 @@ #include <libzonecfg.h> #include <limits.h> #include <libcontract.h> +#include <locale.h> #include <sys/contract.h> #include <sys/ctfs.h> #include <libcontract_priv.h> #include <sys/stat.h> #include <stdbool.h> +#define COLUMN_DEFAULT 80 +#define CHUNK_SIZE 256 /* Arbitrary amount */ #define FAKEDPID0(p) (p->pid == 0 && p->psargs[0] == '\0') +#define HAS_SIBLING(p) ((p)->sp != NULL && (p)->sp->done != 0) typedef struct ps { int done; @@ -69,6 +75,8 @@ struct ps *cp; /* child */ } ps_t; +enum { DASH = 0, BAR, CORNER, VRIGHT }; + static ps_t **ps; /* array of ps_t's */ static unsigned psize; /* size of array */ static int nps; /* number of ps_t's */ @@ -78,17 +86,35 @@ static ps_t *proc0; /* process 0 */ static ps_t *proc1; /* process 1 */ -static char *command; - static int aflag = 0; static int cflag = 0; +static int gflag = 0; static int sflag = 0; static int zflag = 0; static zoneid_t zoneid; static char *match_svc; static char *match_inst; -static int columns = 80; +static int columns; + +static const char *box_ascii[] = { + [DASH] = "-", + [BAR] = "|", + [CORNER] = "`", + [VRIGHT] = "+" +}; +static const char *box_utf8[] = { + [DASH] = "\xe2\x94\x80", /* \u2500 */ + [BAR] = "\xe2\x94\x82", /* \u2502 */ + [CORNER] = "\xe2\x94\x94", /* \u2514 */ + [VRIGHT] = "\xe2\x94\x9c", /* \u251c */ +}; + +static const char **box; + +static size_t get_termwidth(void); +static const char **get_boxchars(void); +static int add_proc(psinfo_t *, lwpsinfo_t *, void *); static bool match_proc(ps_t *); static void markprocs(ps_t *); static int printone(ps_t *, int); @@ -100,31 +126,45 @@ static zoneid_t getzone(const char *); static ps_t *fakepid0(void); +static void *zalloc(size_t); +static void *xreallocarray(void *, size_t, size_t); +static char *xstrdup(const char *); + +static void __NORETURN +usage(void) +{ + (void) fprintf(stderr, + "usage:\t%s [-ac] [-s svc] [-z zone] [ {pid|user} ... ]\n", + getprogname()); + (void) fprintf(stderr, + " (show process trees)\n"); + (void) fprintf(stderr, + " list can include process-ids and user names\n"); + (void) fprintf(stderr, + " -a : include children of process 0\n"); + (void) fprintf(stderr, + " -c : show contracts\n"); + (void) fprintf(stderr, + " -g : use line drawing characters in output\n"); + (void) fprintf(stderr, + " -s : print only processes with given service FMRI\n"); + (void) fprintf(stderr, + " -z : print only processes in given zone\n"); + exit(2); +} + int main(int argc, char **argv) { - psinfo_t info; /* process information structure from /proc */ int opt; int errflg = 0; - struct winsize winsize; - char *s; int n; int retc = 0; - DIR *dirp; - struct dirent *dentp; - char pname[100]; - int pdlen; - ps_t *p; - if ((command = strrchr(argv[0], '/')) == NULL) - command = argv[0]; - else - command++; - /* options */ - while ((opt = getopt(argc, argv, "acs:z:")) != EOF) { + while ((opt = getopt(argc, argv, "acgs:z:")) != EOF) { switch (opt) { case 'a': /* include children of process 0 */ aflag = 1; @@ -132,6 +172,10 @@ case 'c': /* display contract ownership */ aflag = cflag = 1; break; + case 'g': + gflag = 1; + box = get_boxchars(); + break; case 's': sflag = 1; match_svc = parse_svc(optarg, &match_inst); @@ -149,130 +193,19 @@ argc -= optind; argv += optind; - if (errflg) { - (void) fprintf(stderr, - "usage:\t%s [-ac] [-s svc] [-z zone] [ {pid|user} ... ]\n", - command); - (void) fprintf(stderr, - " (show process trees)\n"); - (void) fprintf(stderr, - " list can include process-ids and user names\n"); - (void) fprintf(stderr, - " -a : include children of process 0\n"); - (void) fprintf(stderr, - " -c : show contracts\n"); - (void) fprintf(stderr, - " -s : print only processes with given service FMRI\n"); - (void) fprintf(stderr, - " -z : print only processes in given zone\n"); - return (2); - } + if (errflg) + usage(); - /* - * Kind of a hack to determine the width of the output... - */ - if ((s = getenv("COLUMNS")) != NULL && (n = atoi(s)) > 0) - columns = n; - else if (isatty(fileno(stdout)) && - ioctl(fileno(stdout), TIOCGWINSZ, &winsize) == 0 && - winsize.ws_col != 0) - columns = winsize.ws_col; + columns = get_termwidth(); + VERIFY3S(columns, >, 0); nps = 0; psize = 0; ps = NULL; - /* - * Search the /proc directory for all processes. - */ - if ((dirp = opendir("/proc")) == NULL) { - (void) fprintf(stderr, "%s: cannot open /proc directory\n", - command); - return (1); - } - - (void) strcpy(pname, "/proc"); - pdlen = strlen(pname); - pname[pdlen++] = '/'; - - /* for each active process --- */ - while (dentp = readdir(dirp)) { - int procfd; /* filedescriptor for /proc/nnnnn/psinfo */ - - if (dentp->d_name[0] == '.') /* skip . and .. */ - continue; - (void) strcpy(pname + pdlen, dentp->d_name); - (void) strcpy(pname + strlen(pname), "/psinfo"); -retry: - if ((procfd = open(pname, O_RDONLY)) == -1) - continue; - - /* - * Get the info structure for the process and close quickly. - */ - if (read(procfd, &info, sizeof (info)) != sizeof (info)) { - int saverr = errno; - - (void) close(procfd); - if (saverr == EAGAIN) - goto retry; - if (saverr != ENOENT) - perror(pname); - continue; - } - (void) close(procfd); + /* Currently, this can only fail if the 3rd argument is invalid */ + VERIFY0(proc_walk(add_proc, NULL, PR_WALK_PROC)); - /* - * We make sure there's always a free slot in the table - * in case we need to add a fake p0. - */ - if (nps + 1 >= psize) { - if ((psize *= 2) == 0) - psize = 20; - if ((ps = realloc(ps, psize*sizeof (ps_t *))) == NULL) { - perror("realloc()"); - return (1); - } - } - if ((p = calloc(1, sizeof (ps_t))) == NULL) { - perror("calloc()"); - return (1); - } - ps[nps++] = p; - p->done = 0; - p->uid = info.pr_uid; - p->gid = info.pr_gid; - p->pid = info.pr_pid; - p->ppid = info.pr_ppid; - p->pgrp = info.pr_pgid; - p->sid = info.pr_sid; - p->zoneid = info.pr_zoneid; - p->ctid = info.pr_contract; - p->start = info.pr_start; - proc_unctrl_psinfo(&info); - if (info.pr_nlwp == 0) - (void) strcpy(p->psargs, "<defunct>"); - else if (info.pr_psargs[0] == '\0') - (void) strncpy(p->psargs, info.pr_fname, - sizeof (p->psargs)); - else - (void) strncpy(p->psargs, info.pr_psargs, - sizeof (p->psargs)); - p->psargs[sizeof (p->psargs)-1] = '\0'; - p->pp = NULL; - p->sp = NULL; - p->cp = NULL; - - if (sflag) - p_get_svc_fmri(p, NULL); - - if (p->pid == p->ppid) - proc0 = p; - if (p->pid == 1) - proc1 = p; - } - - (void) closedir(dirp); if (proc0 == NULL) proc0 = fakepid0(); if (proc1 == NULL) @@ -324,9 +257,7 @@ if (errno != 0 || *next != '\0') { struct passwd *pw = getpwnam(arg); if (pw == NULL) { - (void) fprintf(stderr, - "%s: invalid username: %s\n", - command, arg); + warnx("invalid username: %s", arg); retc = 1; continue; } @@ -368,7 +299,33 @@ return (retc || errflg); } -#define PIDWIDTH 5 + +#define PIDWIDTH 6 + +static void +printlines(ps_t *p, int level) +{ + if (level == 0) + return; + + if (!gflag) { + (void) printf("%*s", level * 2, ""); + return; + } + + for (int i = 1; i < level; i++) { + ps_t *ancestor = p; + + /* Find our ancestor at depth 'i' */ + for (int j = i; j < level; j++) + ancestor = ancestor->pp; + + (void) printf("%s ", HAS_SIBLING(ancestor) ? box[BAR] : " "); + } + + (void) printf("%s%s", HAS_SIBLING(p) ? box[VRIGHT] : box[CORNER], + box[DASH]); +} static int printone(ps_t *p, int level) @@ -379,13 +336,14 @@ indent = level * 2; if ((n = columns - PIDWIDTH - indent - 2) < 0) n = 0; + printlines(p, level); if (p->pid >= 0) { - (void) printf("%*.*s%-*d %.*s\n", indent, indent, " ", - PIDWIDTH, (int)p->pid, n, p->psargs); + (void) printf("%-*d %.*s\n", PIDWIDTH, (int)p->pid, n, + p->psargs); } else { assert(cflag != 0); - (void) printf("%*.*s[process contract %d: %s]\n", - indent, indent, " ", (int)p->ctid, + (void) printf("[process contract %d: %s]\n", + (int)p->ctid, p->svc_fmri == NULL ? "?" : p->svc_fmri); } return (1); @@ -472,18 +430,10 @@ return; if (nctps >= ctsize) { - if ((ctsize *= 2) == 0) - ctsize = 20; - if ((ctps = realloc(ctps, ctsize * sizeof (ps_t *))) == NULL) { - perror("realloc()"); - exit(1); - } + ctsize += CHUNK_SIZE; + ctps = xreallocarray(ctps, ctsize, sizeof (ps_t *)); } - pp = calloc(sizeof (ps_t), 1); - if (pp == NULL) { - perror("calloc()"); - exit(1); - } + pp = zalloc(sizeof (*pp)); ctps[nctps++] = pp; pp->pid = -1; @@ -624,11 +574,7 @@ ps_t *p0, *p; int n; - if ((p0 = calloc(1, sizeof (ps_t))) == NULL) { - perror("calloc()"); - exit(1); - } - (void) memset(p0, '\0', sizeof (ps_t)); + p0 = zalloc(sizeof (*p0)); /* First build all partial process trees. */ for (n = 0; n < nps; n++) { @@ -655,10 +601,9 @@ { zoneid_t zoneid; - if (zone_get_id(arg, &zoneid) != 0) { - (void) fprintf(stderr, "%s: unknown zone: %s\n", command, arg); - exit(1); - } + if (zone_get_id(arg, &zoneid) != 0) + err(EXIT_FAILURE, "unknown zone: %s", arg); + return (zoneid); } @@ -673,10 +618,7 @@ if (strncmp(p, "svc:/", strlen("svc:/")) == 0) p += strlen("svc:/"); - if ((ret = strdup(p)) == NULL) { - perror("strdup()"); - exit(1); - } + ret = xstrdup(p); if ((cp = strrchr(ret, ':')) != NULL) { *cp = '\0'; @@ -685,10 +627,136 @@ cp = ""; } - if ((*instp = strdup(cp)) == NULL) { - perror("strdup()"); - exit(1); + *instp = xstrdup(cp); + return (ret); +} + +static int +add_proc(psinfo_t *info, lwpsinfo_t *lwp __unused, void *arg __unused) +{ + ps_t *p; + + /* + * We make sure there is always a free slot in the table + * in case we need to add a fake p0; + */ + if (nps + 1 >= psize) { + psize += CHUNK_SIZE; + ps = xreallocarray(ps, psize, sizeof (ps_t)); } - return (ret); + p = zalloc(sizeof (*p)); + ps[nps++] = p; + p->done = 0; + p->uid = info->pr_uid; + p->gid = info->pr_gid; + p->pid = info->pr_pid; + p->ppid = info->pr_ppid; + p->pgrp = info->pr_pgid; + p->sid = info->pr_sid; + p->zoneid = info->pr_zoneid; + p->ctid = info->pr_contract; + p->start = info->pr_start; + proc_unctrl_psinfo(info); + if (info->pr_nlwp == 0) + (void) strcpy(p->psargs, "<defunct>"); + else if (info->pr_psargs[0] == '\0') + (void) strncpy(p->psargs, info->pr_fname, + sizeof (p->psargs)); + else + (void) strncpy(p->psargs, info->pr_psargs, + sizeof (p->psargs)); + p->psargs[sizeof (p->psargs)-1] = '\0'; + p->pp = NULL; + p->sp = NULL; + + if (sflag) + p_get_svc_fmri(p, NULL); + + if (p->pid == p->ppid) + proc0 = p; + if (p->pid == 1) + proc1 = p; + + return (0); } + + +static size_t +get_termwidth(void) +{ + char *s; + + if ((s = getenv("COLUMNS")) != NULL) { + unsigned long n; + + errno = 0; + n = strtoul(s, NULL, 10); + if (n != 0 && errno == 0) { + /* Sanity check on the range */ + if (n > INT_MAX) + n = COLUMN_DEFAULT; + return (n); + } + } + + struct winsize winsize; + + if (isatty(STDOUT_FILENO) && + ioctl(STDOUT_FILENO, TIOCGWINSZ, &winsize) == 0 && + winsize.ws_col != 0) { + return (winsize.ws_col); + } + + return (COLUMN_DEFAULT); +} + +static const char ** +get_boxchars(void) +{ + char *loc = setlocale(LC_ALL, ""); + + if (loc == NULL) + return (box_ascii); + + const char *p = strstr(loc, "UTF-8"); + + /* + * Only use the UTF-8 box drawing characters if the locale ends + * with "UTF-8". + */ + if (p != NULL && p[5] == '\0') + return (box_utf8); + + return (box_ascii); +} + +static void * +zalloc(size_t len) +{ + void *p = calloc(1, len); + + if (p == NULL) + err(EXIT_FAILURE, "calloc"); + return (p); +} + +static void * +xreallocarray(void *ptr, size_t nelem, size_t elsize) +{ + void *p = reallocarray(ptr, nelem, elsize); + + if (p == NULL) + err(EXIT_FAILURE, "reallocarray"); + return (p); +} + +static char * +xstrdup(const char *s) +{ + char *news = strdup(s); + + if (news == NULL) + err(EXIT_FAILURE, "strdup"); + return (news); +}
--- a/usr/src/man/man1/ptree.1 Mon Nov 04 15:41:15 2019 +0000 +++ b/usr/src/man/man1/ptree.1 Wed Oct 30 23:20:17 2019 -0500 @@ -4,12 +4,12 @@ .\" The contents of this file are subject to the terms of the Common Development and Distribution License (the "License"). You may not use this file except in compliance with the License. .\" You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE or http://www.opensolaris.org/os/licensing. See the License for the specific language governing permissions and limitations under the License. .\" When distributing Covered Code, include this CDDL HEADER in each file and include the License file at usr/src/OPENSOLARIS.LICENSE. If applicable, add the following below this CDDL HEADER, with the fields enclosed by brackets "[]" replaced with your own identifying information: Portions Copyright [yyyy] [name of copyright owner] -.TH PTREE 1 "Sep 3, 2019" +.TH PTREE 1 "Oct 30, 2019" .SH NAME ptree \- print process trees .SH SYNOPSIS .nf -\fB/usr/bin/ptree\fR [\fB-a\fR] [\fB-c\fR] [\fB-s\fR \fIsvc\fR] [\fB-z\fR \fIzone\fR] [\fIpid\fR | \fIuser\fR]... +\fB/usr/bin/ptree\fR [\fB-a\fR] [\fB-c\fR] [\fB-g\fR] [\fB-s\fR \fIsvc\fR] [\fB-z\fR \fIzone\fR] [\fIpid\fR | \fIuser\fR]... .fi .SH DESCRIPTION @@ -42,6 +42,17 @@ .sp .ne 2 .na +\fB\fB-g\fR\fR +.ad +.RS 11n +Use line drawing characters. If the current locale is a UTF-8 locale, the +UTF-8 line drawing characters are used, otherwise ASCII line drawing +characters are used. +.RE + +.sp +.ne 2 +.na \fB\fB-s\fR \fIsvc\fR\fR .ad .RS 11n @@ -109,6 +120,29 @@ .in -2 .sp +\fBExample 2\fR +.sp +.LP +The following example prints the process tree (including children of process 0) +for processes which match the command name \fBssh\fR with ASCII line drawing +characters: + +.sp +.in +2 +.nf +$ ptree -ag `pgrep ssh` + 1 /sbin/init + `-100909 /usr/lib/ssh/sshd + `-569150 /usr/lib/ssh/sshd + `-569157 /usr/lib/ssh/sshd + `-569159 -ksh + `-569171 bash + `-569173 /bin/ksh + `-569193 bash +.fi +.in -2 +.sp + .SH EXIT STATUS The following exit values are returned: .sp