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