summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRenaud Métrich <rmetrich@redhat.com>2021-09-19 15:20:37 +0200
committerDmitry V. Levin <ldv@strace.io>2021-11-28 20:00:00 +0000
commit48de116c270d011fc98feb3a01bbfe72eb2d3c88 (patch)
tree7f08a291afa0f0d992b88ad30420842e8395ced9
parent9d043d7491e246b1cc8bf2c537e1e0234ecfefbd (diff)
downloadstrace-48de116c270d011fc98feb3a01bbfe72eb2d3c88.tar.gz
Print cwd path for AT_FDCWD when -y (--decode-fd=path) option is used
When syscalls use AT_FDCWD (e.g. openat()), the corresponding current working directory is not always obvious, one might need to search back for chdir() syscalls in the process or some of its parents. With this change, the current working directory corresponding to AT_FDCWD is printed when -y (--decode-fd=path) option is used, e.g.: openat(AT_FDCWD</home/rmetrich/GIT/strace>, "/lib64/libselinux.so.1" ...) * src/open.c: Include "number_set.h". (print_dirfd) <fd == AT_FDCWD>: Print the current working directory of tracee when DECODE_FD_PATH is set. * tests/dev-yy.c [!PRINT_AT_FDCWD_PATH] (PRINT_AT_FDCWD_PATH): New macro. (main): Update expected output. * tests/dev--decode-fds-all.c (PRINT_DEVNUM): New macro. Include "dev-yy.c" instead of "dev--decode-fds-dev.c". * tests/dev--decode-fds-dev.c (PRINT_AT_FDCWD_PATH): New macro. * tests/dev--decode-fds-path.c (PRINT_AT_FDCWD_PATH): Likewise. * tests/faccessat.c (AT_FDCWD_FMT, AT_FDCWD_ARG): New macros. (tests_with_existing_file, main): Update expected output. * tests/faccessat2.c (YFLAG): New macro. (main): Update expected output. * tests/fsconfig.c (test_fsconfig_set_path): Update expected output. * tests/fspick.c (main): Likewise. * tests/mount_setattr.c (main): Likewise. * tests/move_mount.c (main): Likewise. * tests/open_tree.c (main): Likewise. * tests/openat2.c (YFLAG, AT_FDCWD_FMT, AT_FDCWD_ARG): New macros. (main): Update expected output. Co-authored-by: Dmitry V. Levin <ldv@strace.io> Resolves: https://github.com/strace/strace/issues/194
-rw-r--r--src/open.c29
-rw-r--r--tests/dev--decode-fds-all.c3
-rw-r--r--tests/dev--decode-fds-dev.c1
-rw-r--r--tests/dev--decode-fds-path.c1
-rw-r--r--tests/dev-yy.c16
-rw-r--r--tests/faccessat.c27
-rw-r--r--tests/faccessat2.c11
-rw-r--r--tests/fsconfig.c11
-rw-r--r--tests/fspick.c7
-rw-r--r--tests/mount_setattr.c7
-rw-r--r--tests/move_mount.c46
-rw-r--r--tests/open_tree.c7
-rw-r--r--tests/openat2.c27
13 files changed, 147 insertions, 46 deletions
diff --git a/src/open.c b/src/open.c
index 1dd79e9dc..d7654d7e8 100644
--- a/src/open.c
+++ b/src/open.c
@@ -16,6 +16,7 @@
#include "defs.h"
#include "xstring.h"
#include "kernel_fcntl.h"
+#include "number_set.h"
#include <linux/openat2.h>
#include <linux/fcntl.h>
@@ -29,10 +30,34 @@
void
print_dirfd(struct tcb *tcp, int fd)
{
- if (fd == AT_FDCWD)
+ if (fd == AT_FDCWD) {
print_xlat_d(AT_FDCWD);
- else
+
+ if (!is_number_in_set(DECODE_FD_PATH, decode_fd_set))
+ goto done;
+
+ int proc_pid = get_proc_pid(tcp->pid);
+ if (!proc_pid)
+ goto done;
+
+ static const char cwd_path[] = "/proc/%u/cwd";
+ char linkpath[sizeof(cwd_path) + sizeof(int) * 3];
+ xsprintf(linkpath, cwd_path, proc_pid);
+
+ char buf[PATH_MAX];
+ ssize_t n = readlink(linkpath, buf, sizeof(buf));
+ if ((size_t) n >= sizeof(buf))
+ goto done;
+
+ tprints("<");
+ print_quoted_string_ex(buf, n,
+ QUOTE_OMIT_LEADING_TRAILING_QUOTES,
+ "<>");
+ tprints(">");
+done: ;
+ } else {
printfd(tcp, fd);
+ }
#ifdef ENABLE_SECONTEXT
tcp->last_dirfd = fd;
#endif
diff --git a/tests/dev--decode-fds-all.c b/tests/dev--decode-fds-all.c
index f159f1ed2..eb8eb5e8b 100644
--- a/tests/dev--decode-fds-all.c
+++ b/tests/dev--decode-fds-all.c
@@ -1 +1,2 @@
-#include "dev--decode-fds-dev.c"
+#define PRINT_DEVNUM 1
+#include "dev-yy.c"
diff --git a/tests/dev--decode-fds-dev.c b/tests/dev--decode-fds-dev.c
index eb8eb5e8b..22f7af67c 100644
--- a/tests/dev--decode-fds-dev.c
+++ b/tests/dev--decode-fds-dev.c
@@ -1,2 +1,3 @@
#define PRINT_DEVNUM 1
+#define PRINT_AT_FDCWD_PATH 0
#include "dev-yy.c"
diff --git a/tests/dev--decode-fds-path.c b/tests/dev--decode-fds-path.c
index 2a5fa1297..e75ab41f6 100644
--- a/tests/dev--decode-fds-path.c
+++ b/tests/dev--decode-fds-path.c
@@ -1,2 +1,3 @@
#define PRINT_DEVNUM 0
+#define PRINT_AT_FDCWD_PATH 1
#include "dev-yy.c"
diff --git a/tests/dev-yy.c b/tests/dev-yy.c
index 4cf90587f..9dcdb04d6 100644
--- a/tests/dev-yy.c
+++ b/tests/dev-yy.c
@@ -30,6 +30,10 @@
# endif
#endif
+#ifndef PRINT_AT_FDCWD_PATH
+# define PRINT_AT_FDCWD_PATH PRINT_DEVNUM
+#endif
+
#if PRINT_DEVNUM
# define DEV_FMT "<%s<%s %u:%u>>"
#elif PRINT_PATH
@@ -44,6 +48,9 @@ int
main(void)
{
skip_if_unavailable("/proc/self/fd/");
+# if PRINT_AT_FDCWD_PATH
+ char *cwd = get_fd_path(get_dir_fd("."));
+# endif
static const struct {
const char *path;
@@ -66,7 +73,14 @@ main(void)
long fd = syscall(__NR_openat, AT_FDCWD, checks[i].path,
O_RDONLY|O_PATH);
- printf("openat(AT_FDCWD, \"%s\", O_RDONLY|O_PATH) = %s",
+ printf("openat(AT_FDCWD"
+# if PRINT_AT_FDCWD_PATH
+ "<%s>"
+# endif
+ ", \"%s\", O_RDONLY|O_PATH) = %s",
+# if PRINT_AT_FDCWD_PATH
+ cwd,
+# endif
checks[i].path, sprintrc(fd));
# if PRINT_PATH
if (fd >= 0)
diff --git a/tests/faccessat.c b/tests/faccessat.c
index 9ce7a07ce..5850a4d00 100644
--- a/tests/faccessat.c
+++ b/tests/faccessat.c
@@ -28,6 +28,14 @@
# define SKIP_IF_PROC_IS_UNAVAILABLE
# endif
+# ifdef YFLAG
+# define AT_FDCWD_FMT "<%s>"
+# define AT_FDCWD_ARG(arg) arg,
+# else
+# define AT_FDCWD_FMT
+# define AT_FDCWD_ARG(arg)
+# endif
+
static const char *errstr;
static long
@@ -57,6 +65,9 @@ tests_with_existing_file(void)
*/
create_and_enter_subdir("faccessat_subdir");
+ int cwd_fd = get_dir_fd(".");
+ char *cwd = get_fd_path(cwd_fd);
+
char *my_secontext = SECONTEXT_PID_MY();
k_faccessat(-1, NULL, F_OK);
@@ -76,8 +87,9 @@ tests_with_existing_file(void)
*/
k_faccessat(-100, sample, F_OK);
- printf("%s%s(AT_FDCWD, \"%s\"%s, F_OK) = %s\n",
+ printf("%s%s(AT_FDCWD" AT_FDCWD_FMT ", \"%s\"%s, F_OK) = %s\n",
my_secontext, "faccessat",
+ AT_FDCWD_ARG(cwd)
sample, sample_secontext,
errstr);
@@ -85,8 +97,9 @@ tests_with_existing_file(void)
perror_msg_and_fail("unlink");
k_faccessat(-100, sample, F_OK);
- printf("%s%s(AT_FDCWD, \"%s\", F_OK) = %s\n",
+ printf("%s%s(AT_FDCWD" AT_FDCWD_FMT ", \"%s\", F_OK) = %s\n",
my_secontext, "faccessat",
+ AT_FDCWD_ARG(cwd)
sample,
errstr);
@@ -94,8 +107,6 @@ tests_with_existing_file(void)
* Tests with dirfd.
*/
- int cwd_fd = get_dir_fd(".");
- char *cwd = get_fd_path(cwd_fd);
char *cwd_secontext = SECONTEXT_FILE(".");
char *sample_realpath = xasprintf("%s/%s", cwd, sample);
@@ -196,6 +207,12 @@ main(void)
if (fd < 0)
perror_msg_and_fail("open: %s", path);
char *fd_str = xasprintf("%d%s", fd, FD_PATH);
+ const char *at_fdcwd_str =
+# ifdef YFLAG
+ xasprintf("AT_FDCWD<%s>", get_fd_path(get_dir_fd(".")));
+# else
+ "AT_FDCWD";
+# endif
char *path_quoted = xasprintf("\"%s\"", path);
struct {
@@ -203,7 +220,7 @@ main(void)
const char *str;
} dirfds[] = {
{ ARG_STR(-1) },
- { -100, "AT_FDCWD" },
+ { -100, at_fdcwd_str },
{ fd, fd_str },
}, modes[] = {
{ ARG_STR(F_OK) },
diff --git a/tests/faccessat2.c b/tests/faccessat2.c
index d35b9d8e8..2a994e2e0 100644
--- a/tests/faccessat2.c
+++ b/tests/faccessat2.c
@@ -21,6 +21,8 @@
#ifndef FD_PATH
# define FD_PATH ""
+#else
+# define YFLAG
#endif
#ifndef SKIP_IF_PROC_IS_UNAVAILABLE
# define SKIP_IF_PROC_IS_UNAVAILABLE
@@ -77,6 +79,13 @@ main(void)
if (fd < 0)
perror_msg_and_fail("open: %s", path);
char *fd_str = xasprintf("%d%s", fd, FD_PATH);
+ const char *at_fdcwd_str =
+#ifdef YFLAG
+ xasprintf("AT_FDCWD<%s>", get_fd_path(get_dir_fd(".")));
+#else
+ "AT_FDCWD";
+#endif
+
char *path_quoted = xasprintf("\"%s\"", path);
struct {
@@ -84,7 +93,7 @@ main(void)
const char *str;
} dirfds[] = {
{ ARG_STR(-1) },
- { -100, "AT_FDCWD" },
+ { -100, at_fdcwd_str },
{ fd, fd_str },
}, modes[] = {
{ ARG_STR(F_OK) },
diff --git a/tests/fsconfig.c b/tests/fsconfig.c
index 2cd14452d..feee7ccfa 100644
--- a/tests/fsconfig.c
+++ b/tests/fsconfig.c
@@ -187,11 +187,14 @@ test_fsconfig_set_binary(const unsigned int cmd, const char *cmd_str)
static void
test_fsconfig_set_path(const unsigned int cmd, const char *cmd_str)
{
+ char *cwd = get_fd_path(get_dir_fd("."));
+
fill_memory_ex(fname, PATH_MAX, '0', 10);
k_fsconfig(fd, cmd, key, fname, -100);
#ifndef PATH_TRACING
- printf("fsconfig(%d<%s>, %s, \"%s\", \"%.*s\"..., AT_FDCWD) = %s\n",
- fd, fd_path, cmd_str, key, (int) PATH_MAX - 1, fname, errstr);
+ printf("fsconfig(%d<%s>, %s, \"%s\", \"%.*s\"..., AT_FDCWD<%s>) = %s\n",
+ fd, fd_path, cmd_str, key, (int) PATH_MAX - 1, fname, cwd,
+ errstr);
#endif
fname[PATH_MAX - 1] = '\0';
@@ -206,8 +209,8 @@ test_fsconfig_set_path(const unsigned int cmd, const char *cmd_str)
cmd_str, key, fd, fd_path, errstr);
k_fsconfig(-1, cmd, 0, fd_path, -100);
- printf("fsconfig(-1, %s, NULL, \"%s\", AT_FDCWD) = %s\n",
- cmd_str, fd_path, errstr);
+ printf("fsconfig(-1, %s, NULL, \"%s\", AT_FDCWD<%s>) = %s\n",
+ cmd_str, fd_path, cwd, errstr);
k_fsconfig(-1, cmd, efault, efault + 1, fd);
printf("fsconfig(-1, %s, %p, %p, %d<%s>) = %s\n",
diff --git a/tests/fspick.c b/tests/fspick.c
index 9dd6c9c8f..7d7f48de1 100644
--- a/tests/fspick.c
+++ b/tests/fspick.c
@@ -38,6 +38,9 @@ main(void)
{
skip_if_unavailable("/proc/self/fd/");
+#ifndef PATH_TRACING
+ char *cwd = get_fd_path(get_dir_fd("."));
+#endif
static const char path_full[] = "/dev/full";
const char *const path = tail_memdup(path_full, sizeof(path_full));
char *const fname = tail_alloc(PATH_MAX);
@@ -56,8 +59,8 @@ main(void)
k_fspick(-100, fname, 0);
#ifndef PATH_TRACING
- printf("fspick(%s, \"%.*s\"..., 0) = %s\n",
- "AT_FDCWD", (int) PATH_MAX - 1, fname, errstr);
+ printf("fspick(AT_FDCWD<%s>, \"%.*s\"..., 0) = %s\n",
+ cwd, (int) PATH_MAX - 1, fname, errstr);
#endif
fname[PATH_MAX - 1] = '\0';
diff --git a/tests/mount_setattr.c b/tests/mount_setattr.c
index 5532d7527..55fe10305 100644
--- a/tests/mount_setattr.c
+++ b/tests/mount_setattr.c
@@ -45,6 +45,9 @@ main(void)
{
skip_if_unavailable("/proc/self/fd/");
+#ifndef PATH_TRACING
+ char *cwd = get_fd_path(get_dir_fd("."));
+#endif
static const char path_full[] = "/dev/full";
const char *const path = tail_memdup(path_full, sizeof(path_full));
char *const fname = tail_alloc(PATH_MAX);
@@ -65,8 +68,8 @@ main(void)
k_mount_setattr(-100, fname, 0, attr, MOUNT_ATTR_SIZE_VER0 - 1);
#ifndef PATH_TRACING
- printf("mount_setattr(%s, \"%.*s\"..., 0, %p, %u) = %s\n",
- "AT_FDCWD", (int) PATH_MAX - 1, fname,
+ printf("mount_setattr(AT_FDCWD<%s>, \"%.*s\"..., 0, %p, %u) = %s\n",
+ cwd, (int) PATH_MAX - 1, fname,
attr, MOUNT_ATTR_SIZE_VER0 - 1, rcstr);
#endif
diff --git a/tests/move_mount.c b/tests/move_mount.c
index 76dda17f6..903afda18 100644
--- a/tests/move_mount.c
+++ b/tests/move_mount.c
@@ -41,6 +41,7 @@ main(void)
{
skip_if_unavailable("/proc/self/fd/");
+ char *cwd = get_fd_path(get_dir_fd("."));
static const char path_full[] = "/dev/full";
const char *const path = tail_memdup(path_full, sizeof(path_full));
const void *const efault = path + sizeof(path_full);
@@ -54,52 +55,55 @@ main(void)
k_move_mount(-1, 0, -100, efault, 0);
#ifndef PATH_TRACING
- printf("move_mount(-1, NULL, AT_FDCWD, %p, 0) = %s\n", efault, errstr);
+ printf("move_mount(-1, NULL, AT_FDCWD<%s>, %p, 0) = %s\n",
+ cwd, efault, errstr);
#endif
k_move_mount(-100, efault, -1, 0, 0);
#ifndef PATH_TRACING
- printf("move_mount(AT_FDCWD, %p, -1, NULL, 0) = %s\n", efault, errstr);
+ printf("move_mount(AT_FDCWD<%s>, %p, -1, NULL, 0) = %s\n",
+ cwd, efault, errstr);
#endif
k_move_mount(dfd, fname, -100, empty, 1);
- printf("move_mount(%d<%s>, \"%.*s\"..., AT_FDCWD, \"\", %s) = %s\n",
- dfd, path, (int) PATH_MAX - 1, fname, "MOVE_MOUNT_F_SYMLINKS",
- errstr);
+ printf("move_mount(%d<%s>, \"%.*s\"..., AT_FDCWD<%s>, \"\", %s) = %s\n",
+ dfd, path, (int) PATH_MAX - 1, fname, cwd,
+ "MOVE_MOUNT_F_SYMLINKS", errstr);
k_move_mount(-100, empty, dfd, fname, 0x10);
- printf("move_mount(AT_FDCWD, \"\", %d<%s>, \"%.*s\"..., %s) = %s\n",
- dfd, path, (int) PATH_MAX - 1, fname, "MOVE_MOUNT_T_SYMLINKS",
- errstr);
+ printf("move_mount(AT_FDCWD<%s>, \"\", %d<%s>, \"%.*s\"..., %s) = %s\n",
+ cwd, dfd, path, (int) PATH_MAX - 1, fname,
+ "MOVE_MOUNT_T_SYMLINKS", errstr);
k_move_mount(-100, empty, dfd, fname, 0x100);
- printf("move_mount(AT_FDCWD, \"\", %d<%s>, \"%.*s\"..., %s) = %s\n",
- dfd, path, (int) PATH_MAX - 1, fname, "MOVE_MOUNT_SET_GROUP",
- errstr);
+ printf("move_mount(AT_FDCWD<%s>, \"\", %d<%s>, \"%.*s\"..., %s) = %s\n",
+ cwd, dfd, path, (int) PATH_MAX - 1, fname,
+ "MOVE_MOUNT_SET_GROUP", errstr);
#define f_flags_str "MOVE_MOUNT_F_SYMLINKS|MOVE_MOUNT_F_AUTOMOUNTS|MOVE_MOUNT_F_EMPTY_PATH"
fname[PATH_MAX - 1] = '\0';
k_move_mount(dfd, fname, -100, empty, 7);
- printf("move_mount(%d<%s>, \"%s\", AT_FDCWD, \"\", %s) = %s\n",
- dfd, path, fname, f_flags_str, errstr);
+ printf("move_mount(%d<%s>, \"%s\", AT_FDCWD<%s>, \"\", %s) = %s\n",
+ dfd, path, fname, cwd, f_flags_str, errstr);
#define t_flags_str "MOVE_MOUNT_T_SYMLINKS|MOVE_MOUNT_T_AUTOMOUNTS|MOVE_MOUNT_T_EMPTY_PATH"
k_move_mount(-100, empty, dfd, fname, 0x70);
- printf("move_mount(AT_FDCWD, \"\", %d<%s>, \"%s\", %s) = %s\n",
- dfd, path, fname, t_flags_str, errstr);
+ printf("move_mount(AT_FDCWD<%s>, \"\", %d<%s>, \"%s\", %s) = %s\n",
+ cwd, dfd, path, fname, t_flags_str, errstr);
#define set_group_str "MOVE_MOUNT_SET_GROUP"
k_move_mount(-100, empty, dfd, fname, 0x100);
- printf("move_mount(AT_FDCWD, \"\", %d<%s>, \"%s\", %s) = %s\n",
- dfd, path, fname, set_group_str, errstr);
+ printf("move_mount(AT_FDCWD<%s>, \"\", %d<%s>, \"%s\", %s) = %s\n",
+ cwd, dfd, path, fname, set_group_str, errstr);
k_move_mount(-1, path, -100, empty, 0x177);
- printf("move_mount(-1, \"%s\", AT_FDCWD, \"\", %s) = %s\n",
- path, f_flags_str "|" t_flags_str "|" set_group_str, errstr);
+ printf("move_mount(-1, \"%s\", AT_FDCWD<%s>, \"\", %s) = %s\n",
+ path, cwd, f_flags_str "|" t_flags_str "|" set_group_str,
+ errstr);
k_move_mount(-100, empty, -1, path, -1);
- printf("move_mount(AT_FDCWD, \"\", -1, \"%s\", %s) = %s\n",
- path,
+ printf("move_mount(AT_FDCWD<%s>, \"\", -1, \"%s\", %s) = %s\n",
+ cwd, path,
f_flags_str "|" t_flags_str "|" set_group_str "|0xfffffe88",
errstr);
diff --git a/tests/open_tree.c b/tests/open_tree.c
index f88d1dd01..a2025fc7d 100644
--- a/tests/open_tree.c
+++ b/tests/open_tree.c
@@ -39,6 +39,9 @@ main(void)
{
skip_if_unavailable("/proc/self/fd/");
+#ifndef PATH_TRACING
+ char *cwd = get_fd_path(get_dir_fd("."));
+#endif
static const char path_full[] = "/dev/full";
const char *const path = tail_memdup(path_full, sizeof(path_full));
char *const fname = tail_alloc(PATH_MAX);
@@ -57,8 +60,8 @@ main(void)
k_open_tree(-100, fname, 0);
#ifndef PATH_TRACING
- printf("open_tree(%s, \"%.*s\"..., 0) = %s\n",
- "AT_FDCWD", (int) PATH_MAX - 1, fname, errstr);
+ printf("open_tree(AT_FDCWD<%s>, \"%.*s\"..., 0) = %s\n",
+ cwd, (int) PATH_MAX - 1, fname, errstr);
#endif
fname[PATH_MAX - 1] = '\0';
diff --git a/tests/openat2.c b/tests/openat2.c
index 2b6511463..beae992ce 100644
--- a/tests/openat2.c
+++ b/tests/openat2.c
@@ -23,11 +23,21 @@
#endif
#ifndef FD0_PATH
# define FD0_PATH ""
+#else
+# define YFLAG
#endif
#ifndef SKIP_IF_PROC_IS_UNAVAILABLE
# define SKIP_IF_PROC_IS_UNAVAILABLE
#endif
+#ifdef YFLAG
+# define AT_FDCWD_FMT "<%s>"
+# define AT_FDCWD_ARG(arg) arg,
+#else
+# define AT_FDCWD_FMT
+# define AT_FDCWD_ARG(arg)
+#endif
+
static const char sample[] = "openat2.sample";
int
@@ -35,6 +45,9 @@ main(void)
{
SKIP_IF_PROC_IS_UNAVAILABLE;
+# ifdef YFLAG
+ char *cwd = get_fd_path(get_dir_fd("."));
+# endif
long rc;
const char *rcstr;
struct open_how *how = tail_alloc(sizeof(*how));
@@ -47,8 +60,10 @@ main(void)
sprintrc(rc));
rc = syscall(__NR_openat2, -100, "", how + 1, sizeof(*how));
- printf("openat2(%s, \"\", %p, %zu) = %s\n",
- XLAT_KNOWN(-100, "AT_FDCWD"), how + 1, sizeof(*how),
+ printf("openat2(%s" AT_FDCWD_FMT ", \"\", %p, %zu) = %s\n",
+ XLAT_KNOWN(-100, "AT_FDCWD"),
+ AT_FDCWD_ARG(cwd)
+ how + 1, sizeof(*how),
sprintrc(rc));
rc = syscall(__NR_openat2, -1, sample, how, 11);
@@ -121,9 +136,11 @@ main(void)
how->mode = 0;
how->resolve = 0;
rc = syscall(__NR_openat2, -100, "/dev/full", how, sizeof(*how));
- printf("openat2(%s, \"/dev/full\", {flags=%s, resolve=0}, %zu)"
- " = %s%s\n",
- XLAT_KNOWN(-100, "AT_FDCWD"), XLAT_STR(O_RDONLY|O_NOCTTY),
+ printf("openat2(%s" AT_FDCWD_FMT ", \"/dev/full\""
+ ", {flags=%s, resolve=0}, %zu) = %s%s\n",
+ XLAT_KNOWN(-100, "AT_FDCWD"),
+ AT_FDCWD_ARG(cwd)
+ XLAT_STR(O_RDONLY|O_NOCTTY),
sizeof(*how), sprintrc(rc), rc >= 0 ? FD0_PATH : "");
puts("+++ exited with 0 +++");