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
pwdx_usage(const char * fmt,...)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
pwdx_escape(char c)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
pwdx_puts(const char * str,bool newline)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
pwdx(const char * str,pwdx_output_t output)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
main(int argc,char * argv[])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