xref: /illumos-gate/usr/src/cmd/ptools/pwdx/pwdx.c (revision 8119dad84d6416f13557b0ba8e2aaf9064cbcfd3)
1 /*
2  * This file and its contents are supplied under the terms of the
3  * Common Development and Distribution License ("CDDL"), version 1.0.
4  * You may only use this file in accordance with the terms of version
5  * 1.0 of the CDDL.
6  *
7  * A full copy of the text of the CDDL should have accompanied this
8  * source.  A copy of the CDDL is also available via the Internet at
9  * http://www.illumos.org/license/CDDL.
10  */
11 
12 /*
13  * Copyright 2024 Oxide Computer Company
14  */
15 
16 /*
17  * Print the current working directory of an arbitrary process or core file.
18  * While this uses libproc, it always does this read only, which is in the
19  * spirit of the original pwdx that just read from /proc directly and didn't
20  * support additional options.
21  */
22 
23 #include <err.h>
24 #include <unistd.h>
25 #include <stdlib.h>
26 #include <sys/stdbool.h>
27 #include <libproc.h>
28 #include <locale.h>
29 #include <stdio.h>
30 #include <wchar.h>
31 
32 #define	EXIT_USAGE	2
33 
34 typedef enum {
35 	PWDX_CWD	= 1 << 0,
36 	PWDX_MOUNT	= 1 << 1,
37 	PWDX_REST	= 1 << 2,
38 	PWDX_QUIET	= 1 << 3
39 } pwdx_output_t;
40 
41 static void
42 pwdx_usage(const char *fmt, ...)
43 {
44 	if (fmt != NULL) {
45 		va_list ap;
46 
47 		va_start(ap, fmt);
48 		vwarnx(fmt, ap);
49 		va_end(ap);
50 	}
51 
52 	(void) fprintf(stderr, "Usage:  pwdx [-m] [-q | -v] { pid | core } "
53 	    "...\n");
54 	(void) fprintf(stderr, "Print process and core current working "
55 	    "directory\n"
56 	    "\t-m\t\tshow mountpoint path\n"
57 	    "\t-q\t\tonly output paths\n"
58 	    "\t-v\t\toutput verbose mount information\n");
59 }
60 
61 static void
62 pwdx_escape(char c)
63 {
64 	(void) printf("\\%c%c%c", '0' + ((c >> 6) & 07), '0' + ((c >> 3) & 07),
65 	    '0' + (c & 07));
66 }
67 
68 /*
69  * We have a string that represents a path which is really an arbitrary byte
70  * string. It may or may not be safe for the user to actually interpret if
71  * we send it to a shell. So we go through and break this into multi-byte
72  * characters and if they are printable, print them. If they are not, then we
73  * write an escape sequence out byte by byte. We use the octal escape sequence,
74  * not because we like it, but because that's consistent with pargs, ls, etc.
75  */
76 static void
77 pwdx_puts(const char *str, bool newline)
78 {
79 	size_t slen = strlen(str), off = 0;
80 
81 	while (off < slen) {
82 		wchar_t wc;
83 		int ret = mbtowc(&wc, str + off, slen - off);
84 
85 		if (ret < 0) {
86 			pwdx_escape(str[off]);
87 			off++;
88 			continue;
89 		} else if (ret == 0) {
90 			break;
91 		}
92 
93 		if (iswprint(wc)) {
94 			(void) putwchar(wc);
95 		} else {
96 			for (int i = 0; i < ret; i++) {
97 				pwdx_escape(str[off + i]);
98 			}
99 		}
100 
101 		off += ret;
102 	}
103 
104 	if (newline) {
105 		(void) putchar('\n');
106 	}
107 }
108 
109 static bool
110 pwdx(const char *str, pwdx_output_t output)
111 {
112 	int err;
113 	bool ret = false;
114 	struct ps_prochandle *P;
115 	prcwd_t *cwd = NULL;
116 	const psinfo_t *info;
117 
118 	P = proc_arg_grab(str, PR_ARG_ANY, PGRAB_RDONLY, &err);
119 	if (P == NULL) {
120 		warnx("failed to open %s: %s", str, Pgrab_error(err));
121 		return (false);
122 	}
123 
124 	info = Ppsinfo(P);
125 	if (info == NULL) {
126 		warn("failed to get psinfo from %s", str);
127 		goto out;
128 	}
129 
130 	if (Pcwd(P, &cwd) != 0) {
131 		warn("failed to read cwd of %s", str);
132 		goto out;
133 	}
134 
135 	if ((output & PWDX_QUIET) == 0) {
136 		if (Pstate(P) == PS_DEAD) {
137 			(void) printf("core '%s' of ", str);
138 		}
139 		(void) printf("%" _PRIdID ":\t", info->pr_pid);
140 	}
141 
142 	if ((output & PWDX_CWD) != 0) {
143 		pwdx_puts(cwd->prcwd_cwd, true);
144 	} else {
145 		if (cwd->prcwd_mntpt[0] != '\0') {
146 			pwdx_puts(cwd->prcwd_mntpt, true);
147 		} else {
148 			(void) puts("<unknown>");
149 		}
150 	}
151 
152 	if (output & PWDX_REST) {
153 		const char *spec, *point;
154 
155 		if (cwd->prcwd_mntspec[0] == '\0') {
156 			spec = "unknown";
157 		} else {
158 			spec = cwd->prcwd_mntspec;
159 		}
160 
161 		if (cwd->prcwd_mntpt[0] == '\0') {
162 			point = "unknown";
163 		} else {
164 			point = cwd->prcwd_mntpt;
165 		}
166 
167 		(void) printf("\tMountpoint ");
168 		pwdx_puts(point, false);
169 		(void) printf(" on ");
170 		pwdx_puts(spec, true);
171 		(void) printf("\tFilesystem %s (ID: 0x%" PRIx64 ")\n",
172 		    cwd->prcwd_fsname, cwd->prcwd_fsid);
173 	}
174 
175 	ret = true;
176 
177 out:
178 	Pcwd_free(cwd);
179 	Pfree(P);
180 	return (ret);
181 }
182 
183 int
184 main(int argc, char *argv[])
185 {
186 	int ret = EXIT_SUCCESS;
187 	pwdx_output_t output = PWDX_CWD;
188 	int c;
189 
190 	(void) setlocale(LC_ALL, "");
191 
192 	while ((c = getopt(argc, argv, ":mqv")) != -1) {
193 		switch (c) {
194 		case 'm':
195 			output |= PWDX_MOUNT;
196 			output &= ~PWDX_CWD;
197 			break;
198 		case 'q':
199 			if (output & PWDX_REST) {
200 				errx(EXIT_USAGE, "only one of -q and -v may be "
201 				    "specified");
202 			}
203 			output |= PWDX_QUIET;
204 			break;
205 		case 'v':
206 			if (output & PWDX_QUIET) {
207 				errx(EXIT_USAGE, "only one of -q and -v may be "
208 				    "specified");
209 			}
210 			output |= PWDX_CWD | PWDX_MOUNT | PWDX_REST;
211 			break;
212 		case ':':
213 			pwdx_usage("option -%c requires an "
214 			    "argument", optopt);
215 			exit(EXIT_USAGE);
216 		case '?':
217 			pwdx_usage("unknown option: -%c", optopt);
218 			exit(EXIT_USAGE);
219 		}
220 	}
221 
222 	argc -= optind;
223 	argv += optind;
224 
225 	if (argc < 1) {
226 		errx(EXIT_FAILURE, "at least one process must be specified");
227 	}
228 
229 	for (int i = 0; i < argc; i++) {
230 		if (!pwdx(argv[i], output))
231 			ret = EXIT_FAILURE;
232 	}
233 
234 	return (ret);
235 }
236