/* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2024 Oxide Computer Company */ /* * Print the current working directory of an arbitrary process or core file. * While this uses libproc, it always does this read only, which is in the * spirit of the original pwdx that just read from /proc directly and didn't * support additional options. */ #include #include #include #include #include #include #include #include #define EXIT_USAGE 2 typedef enum { PWDX_CWD = 1 << 0, PWDX_MOUNT = 1 << 1, PWDX_REST = 1 << 2, PWDX_QUIET = 1 << 3 } pwdx_output_t; static void pwdx_usage(const char *fmt, ...) { if (fmt != NULL) { va_list ap; va_start(ap, fmt); vwarnx(fmt, ap); va_end(ap); } (void) fprintf(stderr, "Usage: pwdx [-m] [-q | -v] { pid | core } " "...\n"); (void) fprintf(stderr, "Print process and core current working " "directory\n" "\t-m\t\tshow mountpoint path\n" "\t-q\t\tonly output paths\n" "\t-v\t\toutput verbose mount information\n"); } static void pwdx_escape(char c) { (void) printf("\\%c%c%c", '0' + ((c >> 6) & 07), '0' + ((c >> 3) & 07), '0' + (c & 07)); } /* * We have a string that represents a path which is really an arbitrary byte * string. It may or may not be safe for the user to actually interpret if * we send it to a shell. So we go through and break this into multi-byte * characters and if they are printable, print them. If they are not, then we * write an escape sequence out byte by byte. We use the octal escape sequence, * not because we like it, but because that's consistent with pargs, ls, etc. */ static void pwdx_puts(const char *str, bool newline) { size_t slen = strlen(str), off = 0; while (off < slen) { wchar_t wc; int ret = mbtowc(&wc, str + off, slen - off); if (ret < 0) { pwdx_escape(str[off]); off++; continue; } else if (ret == 0) { break; } if (iswprint(wc)) { (void) putwchar(wc); } else { for (int i = 0; i < ret; i++) { pwdx_escape(str[off + i]); } } off += ret; } if (newline) { (void) putchar('\n'); } } static bool pwdx(const char *str, pwdx_output_t output) { int err; bool ret = false; struct ps_prochandle *P; prcwd_t *cwd = NULL; const psinfo_t *info; P = proc_arg_grab(str, PR_ARG_ANY, PGRAB_RDONLY, &err); if (P == NULL) { warnx("failed to open %s: %s", str, Pgrab_error(err)); return (false); } info = Ppsinfo(P); if (info == NULL) { warn("failed to get psinfo from %s", str); goto out; } if (Pcwd(P, &cwd) != 0) { warn("failed to read cwd of %s", str); goto out; } if ((output & PWDX_QUIET) == 0) { if (Pstate(P) == PS_DEAD) { (void) printf("core '%s' of ", str); } (void) printf("%" _PRIdID ":\t", info->pr_pid); } if ((output & PWDX_CWD) != 0) { pwdx_puts(cwd->prcwd_cwd, true); } else { if (cwd->prcwd_mntpt[0] != '\0') { pwdx_puts(cwd->prcwd_mntpt, true); } else { (void) puts(""); } } if (output & PWDX_REST) { const char *spec, *point; if (cwd->prcwd_mntspec[0] == '\0') { spec = "unknown"; } else { spec = cwd->prcwd_mntspec; } if (cwd->prcwd_mntpt[0] == '\0') { point = "unknown"; } else { point = cwd->prcwd_mntpt; } (void) printf("\tMountpoint "); pwdx_puts(point, false); (void) printf(" on "); pwdx_puts(spec, true); (void) printf("\tFilesystem %s (ID: 0x%" PRIx64 ")\n", cwd->prcwd_fsname, cwd->prcwd_fsid); } ret = true; out: Pcwd_free(cwd); Pfree(P); return (ret); } int main(int argc, char *argv[]) { int ret = EXIT_SUCCESS; pwdx_output_t output = PWDX_CWD; int c; (void) setlocale(LC_ALL, ""); while ((c = getopt(argc, argv, ":mqv")) != -1) { switch (c) { case 'm': output |= PWDX_MOUNT; output &= ~PWDX_CWD; break; case 'q': if (output & PWDX_REST) { errx(EXIT_USAGE, "only one of -q and -v may be " "specified"); } output |= PWDX_QUIET; break; case 'v': if (output & PWDX_QUIET) { errx(EXIT_USAGE, "only one of -q and -v may be " "specified"); } output |= PWDX_CWD | PWDX_MOUNT | PWDX_REST; break; case ':': pwdx_usage("option -%c requires an " "argument", optopt); exit(EXIT_USAGE); case '?': pwdx_usage("unknown option: -%c", optopt); exit(EXIT_USAGE); } } argc -= optind; argv += optind; if (argc < 1) { errx(EXIT_FAILURE, "at least one process must be specified"); } for (int i = 0; i < argc; i++) { if (!pwdx(argv[i], output)) ret = EXIT_FAILURE; } return (ret); }