rvlib-shipkit 1.4.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +114 -0
- package/README.md +17 -17
- package/package.json +3 -2
- package/src/assets/gen/comfyts-gen.ts +12 -8
- package/src/assets/server/studio-server.ts +14 -2
- package/src/check.ts +73 -103
- package/src/checks/SK001-package-json-shape.check.ts +11 -6
- package/src/checks/SK002-contracts-fulfillness.check.ts +4 -0
- package/src/checks/SK003-claude-md-shape.check.ts +7 -3
- package/src/checks/SK004-gitignore-policy.check.ts +7 -4
- package/src/checks/SK005-rvlib-versions.check.ts +53 -21
- package/src/checks/SK006-npm-publish-safety.check.ts +4 -0
- package/src/checks/SK007-github-visibility.check.ts +4 -2
- package/src/checks/SK008-reviewer-agent.check.ts +6 -4
- package/src/checks/SK009-commit-ai-tells.check.ts +4 -2
- package/src/checks/SK010-undeclared-unit.check.ts +4 -0
- package/src/checks/SK011-sibling-unit-ranges.check.ts +4 -0
- package/src/checks/SK012-config-barrel-import.check.ts +5 -1
- package/src/checks/SK013-one-way-in-policy.check.ts +7 -3
- package/src/checks/SK014-prose-ste.check.ts +4 -0
- package/src/checks/SK015-nested-repo.check.ts +4 -2
- package/src/checks/SK016-legacy-check-shapes.check.ts +13 -12
- package/src/checks/SK017-vscode-hub-task.check.ts +22 -7
- package/src/checks/SK018-test-files-run.check.ts +6 -2
- package/src/checks/SK019-tests-run-in-units.check.ts +8 -4
- package/src/checks/SK020-self-spawning-check.check.ts +4 -0
- package/src/checks/SK022-machine-runs-the-pin.check.ts +6 -4
- package/src/checks/SK024-identity-colors.check.ts +5 -3
- package/src/checks/SK025-release-drift.check.ts +4 -5
- package/src/checks/SK026-asks-autoclose.check.ts +4 -4
- package/src/checks/SK027-agent-wiki-links.check.ts +4 -1
- package/src/checks/SK028-readme.check.ts +5 -1
- package/src/checks/SK029-explicit-check-files.check.ts +95 -0
- package/src/checks/SK030-cache-audit.check.ts +34 -0
- package/src/checks/SK031-trace-audit.check.ts +39 -0
- package/src/checks/SK032-ignored-inputs.check.ts +79 -0
- package/src/cli/cmd_Asks.ts +5 -5
- package/src/cli/cmd_Assets.ts +17 -13
- package/src/cli/cmd_Bump.ts +12 -11
- package/src/cli/cmd_CacheAudit.ts +43 -0
- package/src/cli/cmd_Check.ts +8 -7
- package/src/cli/cmd_Checks.ts +7 -6
- package/src/cli/cmd_Ci.ts +12 -11
- package/src/cli/cmd_Commands.ts +16 -13
- package/src/cli/cmd_ConfigKeys.ts +7 -6
- package/src/cli/cmd_Contracts.ts +3 -3
- package/src/cli/cmd_CrossRepoActivity.ts +8 -6
- package/src/cli/cmd_Fleet.ts +9 -7
- package/src/cli/cmd_GlobalProcesses.ts +120 -7
- package/src/cli/cmd_Home.ts +26 -19
- package/src/cli/cmd_Hooks.ts +6 -5
- package/src/cli/cmd_Init.ts +5 -4
- package/src/cli/cmd_Keys.ts +20 -8
- package/src/cli/cmd_Mv.ts +7 -6
- package/src/cli/cmd_Needs.ts +4 -3
- package/src/cli/cmd_Overview.ts +5 -4
- package/src/cli/cmd_Pb.ts +2 -2
- package/src/cli/cmd_Planned.ts +6 -5
- package/src/cli/cmd_Prose.ts +5 -5
- package/src/cli/cmd_Reflect.ts +7 -7
- package/src/cli/cmd_Release.ts +6 -5
- package/src/cli/cmd_Releases.ts +5 -4
- package/src/cli/cmd_Serve.ts +6 -4
- package/src/cli/cmd_Smoke.ts +5 -4
- package/src/cli/cmd_Start.ts +7 -6
- package/src/cli/cmd_Status.ts +4 -3
- package/src/cli/cmd_TraceAudit.ts +62 -0
- package/src/cli/cmd_Tui.ts +3 -2
- package/src/cli/cmd_When.ts +71 -0
- package/src/cli/cmd_Wiki.ts +4 -4
- package/src/cli.ts +11 -2
- package/src/collections/checks.ts +2 -0
- package/src/collections/collection-types.ts +12 -4
- package/src/collections/providers.ts +5 -3
- package/src/core/app-icon.ts +154 -0
- package/src/core/audit-report.ts +21 -0
- package/src/core/cache-audit.ts +72 -0
- package/src/core/canonical-scripts.ts +2 -2
- package/src/core/check-ctx.ts +14 -5
- package/src/core/check-runner.ts +43 -23
- package/src/core/config-rewrite.ts +37 -24
- package/src/core/discover-checks.ts +23 -2
- package/src/core/floor-resolve.ts +5 -7
- package/src/core/git-root.ts +13 -0
- package/src/core/ignored-block.ts +94 -0
- package/src/core/ignored-inputs.ts +47 -0
- package/src/core/jobs.ts +9 -2
- package/src/core/legacy-shapes.ts +39 -10
- package/src/core/one-lib.ts +59 -4
- package/src/core/one-way-in-policy.ts +2 -2
- package/src/core/outcome-words.ts +18 -2
- package/src/core/port-check-cli.ts +52 -0
- package/src/core/port-check-file.ts +295 -0
- package/src/core/port-check-files.ts +31 -0
- package/src/core/read-set.ts +97 -9
- package/src/core/retention.ts +0 -14
- package/src/core/run-check.ts +12 -9
- package/src/core/run-ci.ts +14 -5
- package/src/core/run-fix.ts +30 -23
- package/src/core/run-fixers.ts +48 -5
- package/src/core/runs-matrix.ts +179 -0
- package/src/core/runs.ts +242 -0
- package/src/core/trace-audit.ts +120 -0
- package/src/core/update-policy.ts +20 -0
- package/src/core/when-block.ts +29 -30
- package/src/core/when.ts +41 -103
- package/src/core/wire.ts +10 -14
- package/src/fleet/scan-fleet.ts +20 -4
- package/src/fleet/update-fleet.ts +6 -6
- package/src/home/gui-app.ts +24 -27
- package/src/home/home-launchd.ts +20 -4
- package/src/home/home-routes.ts +12 -1
- package/src/home/home-start.ts +11 -5
- package/src/home/hubs.ts +5 -5
- package/src/home/open-app.ts +2 -2
- package/src/home/pm.ts +8 -0
- package/src/home/run-pm.ts +1 -0
- package/src/home/serve-web.ts +2 -2
- package/src/home/supervise.ts +24 -1
- package/src/home/via-hub.ts +14 -0
- package/src/hooks/run-hooks.ts +48 -4
- package/src/init-repo.ts +6 -6
- package/src/ledger/db.ts +2 -13
- package/src/ledger/gate-checks.ts +133 -61
- package/src/ledger/git-key.ts +153 -31
- package/src/ledger/git-scan.ts +5 -2
- package/src/ledger/ledger.ts +136 -21
- package/src/ledger/store.ts +14 -0
- package/src/ledger/toolchain-identity.ts +248 -45
- package/src/plan/repo-source.ts +18 -39
- package/src/problems/extract.ts +3 -0
- package/src/release/run-release.ts +5 -0
- package/src/repo-config.ts +46 -54
- package/src/rules/rule.ts +2 -2
- package/src/scaffold/files/agent-coding.md +1 -1
- package/src/scaffold/files/agent-index.md +1 -1
- package/src/scaffold/files/verify-skill.md +2 -2
- package/src/serve/hub-app/activity-st.ts +5 -4
- package/src/serve/hub-app/activity-view.tsx +1 -1
- package/src/serve/hub-app/admin-svc.ts +36 -3
- package/src/serve/hub-app/admin-view.tsx +46 -12
- package/src/serve/hub-app/check-view.tsx +66 -46
- package/src/serve/hub-app/check-words.ts +1 -11
- package/src/serve/hub-app/checks-svc.ts +14 -35
- package/src/serve/hub-app/col-card.tsx +1 -44
- package/src/serve/hub-app/col-tables-view.tsx +84 -11
- package/src/serve/hub-app/col-tables.ts +27 -0
- package/src/serve/hub-app/cols-svc.ts +5 -4
- package/src/serve/hub-app/dock-st.ts +3 -2
- package/src/serve/hub-app/dock-view.tsx +36 -0
- package/src/serve/hub-app/dock.css +6 -0
- package/src/serve/hub-app/float-cards.tsx +30 -0
- package/src/serve/hub-app/floating.tsx +41 -4
- package/src/serve/hub-app/git-st.ts +42 -0
- package/src/serve/hub-app/git-view.tsx +61 -0
- package/src/serve/hub-app/hash-route.ts +45 -1
- package/src/serve/hub-app/header-bar.tsx +62 -72
- package/src/serve/hub-app/health-svc.ts +3 -2
- package/src/serve/hub-app/home-st.ts +14 -2
- package/src/serve/hub-app/home-strip.css +43 -12
- package/src/serve/hub-app/home-strip.tsx +115 -64
- package/src/serve/hub-app/home.html +1 -0
- package/src/serve/hub-app/hub-app.tsx +32 -8
- package/src/serve/hub-app/hub-types.ts +5 -2
- package/src/serve/hub-app/hub.css +89 -63
- package/src/serve/hub-app/icons.tsx +11 -0
- package/src/serve/hub-app/index.html +1 -0
- package/src/serve/hub-app/keys.ts +15 -4
- package/src/serve/hub-app/live-svc.ts +3 -2
- package/src/serve/hub-app/machine-st.ts +18 -22
- package/src/serve/hub-app/plan-view.tsx +51 -0
- package/src/serve/hub-app/planned-st.ts +9 -4
- package/src/serve/hub-app/processes-view.tsx +45 -0
- package/src/serve/hub-app/project-list.css +4 -1
- package/src/serve/hub-app/project-list.tsx +6 -3
- package/src/serve/hub-app/projects-svc.ts +25 -10
- package/src/serve/hub-app/prose-hits-st.ts +3 -2
- package/src/serve/hub-app/reach.ts +36 -0
- package/src/serve/hub-app/readme-svc.ts +3 -2
- package/src/serve/hub-app/repo-sections.ts +131 -0
- package/src/serve/hub-app/repo-st.ts +70 -14
- package/src/serve/hub-app/sidebar.tsx +17 -42
- package/src/serve/hub-app/timeline-svc.ts +3 -2
- package/src/serve/hub-app/tree-build.ts +151 -155
- package/src/serve/hub-app/tree-row.tsx +55 -12
- package/src/serve/hub-app/tree-svc.ts +97 -56
- package/src/serve/hub-app/when-view.tsx +89 -0
- package/src/serve/hub-app/when.css +17 -0
- package/src/serve/hub-runtime.ts +7 -18
- package/src/serve/project-gutter-route.ts +22 -1
- package/src/serve/run-serve.ts +1 -1
- package/src/serve/serve-hub.ts +4 -14
- package/src/suite/judge-suites.ts +6 -0
- package/src/trace/interpose.c +655 -0
- package/src/trace/read-trace.ts +485 -0
- package/src/trace/tracer.ts +282 -0
- package/src/serve/hub-app/processes-svc.ts +0 -63
- package/src/serve/hub-app/repo-sections.tsx +0 -61
- package/src/serve/repo-processes.ts +0 -89
|
@@ -0,0 +1,655 @@
|
|
|
1
|
+
// what a task's processes touch, logged from inside them. Loaded through
|
|
2
|
+
// DYLD_INSERT_LIBRARIES, it wraps the libc calls that read, probe, write,
|
|
3
|
+
// spawn and connect, and appends one line per call to <pid>.log in every dir
|
|
4
|
+
// of SHIPKIT_TRACE_DIR (colon separated: a traced gate inside a traced task
|
|
5
|
+
// logs to both). trace/read-trace.ts turns the logs into a read set.
|
|
6
|
+
//
|
|
7
|
+
// Lines, tab separated:
|
|
8
|
+
// H pid ppid exe this process image started, traced
|
|
9
|
+
// L path an image loaded: the executable, a library,
|
|
10
|
+
// a dlopen'd addon (dyld's shared cache left out)
|
|
11
|
+
// O flags kind dir path size mtime an open that succeeded (kind f or d)
|
|
12
|
+
// E op errno dir path a call on a path that failed
|
|
13
|
+
// S op kind dir path [size mtime ino] a probe that succeeded (kind f, d or l),
|
|
14
|
+
// with what stat said for a file
|
|
15
|
+
// D path mtime now a directory's entries were read: the
|
|
16
|
+
// dir's mtime then, and the wall clock
|
|
17
|
+
// M path name... the entry names one getdirentries call
|
|
18
|
+
// returned (0x1f between names)
|
|
19
|
+
// K dir path target a symlink's target, as readlink read it
|
|
20
|
+
// C path size mtime ino a file the task wrote, as it closed it
|
|
21
|
+
// W op dir path a path was written, created or removed
|
|
22
|
+
// P op r pid path argv a spawn or an exec (argv joined by 0x1f)
|
|
23
|
+
// N family addr port r errno fd a connect, and how it ended
|
|
24
|
+
// R fd errno how a connect left in progress ended
|
|
25
|
+
// B family addr port r errno a bind, with the port the kernel gave
|
|
26
|
+
// `dir` is the dir a relative path was resolved against, empty for an
|
|
27
|
+
// absolute path.
|
|
28
|
+
//
|
|
29
|
+
// A child that never writes its H line ran untraced, and the reader refuses
|
|
30
|
+
// to key the answer on a trace with a hole in it. The logs themselves must
|
|
31
|
+
// never become the hole, nor land in the task's files: a program that closes
|
|
32
|
+
// every fd above 2 (python's subprocess does, before its exec) or reuses the
|
|
33
|
+
// number for its own file would otherwise lose lines or receive them. So a
|
|
34
|
+
// log fd sits above 1000, and before each write its dev and inode must still
|
|
35
|
+
// be the log's, else the log is opened again. A forked child opens its own. Two ways a child goes
|
|
36
|
+
// untraced are repaired here instead: a `#!/usr/bin/env X` script is spawned
|
|
37
|
+
// as `X script`, since the kernel would run the protected /usr/bin/env, which
|
|
38
|
+
// drops DYLD_* for everything below it, and a binary named in the remap file
|
|
39
|
+
// (SHIPKIT_TRACE_REMAP) is swapped for its traceable copy.
|
|
40
|
+
#include <arpa/inet.h>
|
|
41
|
+
#include <dirent.h>
|
|
42
|
+
#include <dlfcn.h>
|
|
43
|
+
#include <mach-o/dyld.h>
|
|
44
|
+
#include <sys/attr.h>
|
|
45
|
+
#include <errno.h>
|
|
46
|
+
#include <fcntl.h>
|
|
47
|
+
#include <limits.h>
|
|
48
|
+
#include <netinet/in.h>
|
|
49
|
+
#include <spawn.h>
|
|
50
|
+
#include <stdarg.h>
|
|
51
|
+
#include <stdio.h>
|
|
52
|
+
#include <stdlib.h>
|
|
53
|
+
#include <string.h>
|
|
54
|
+
#include <sys/socket.h>
|
|
55
|
+
#include <sys/stat.h>
|
|
56
|
+
#include <sys/un.h>
|
|
57
|
+
#include <time.h>
|
|
58
|
+
#include <unistd.h>
|
|
59
|
+
|
|
60
|
+
extern char **environ;
|
|
61
|
+
extern int _NSGetExecutablePath(char *buf, uint32_t *size);
|
|
62
|
+
|
|
63
|
+
#define INTERPOSE(mine, theirs) \
|
|
64
|
+
__attribute__((used)) static struct { \
|
|
65
|
+
const void *replacement; \
|
|
66
|
+
const void *replacee; \
|
|
67
|
+
} interpose_##theirs __attribute__((section("__DATA,__interpose"))) = {(const void *)&mine, (const void *)&theirs};
|
|
68
|
+
|
|
69
|
+
#define MAX_DIRS 4
|
|
70
|
+
|
|
71
|
+
typedef struct {
|
|
72
|
+
int fd;
|
|
73
|
+
dev_t dev;
|
|
74
|
+
ino_t ino;
|
|
75
|
+
char path[PATH_MAX];
|
|
76
|
+
} log_t;
|
|
77
|
+
static log_t logs[MAX_DIRS];
|
|
78
|
+
static int nlogs = -1;
|
|
79
|
+
static pid_t logpid = 0;
|
|
80
|
+
// the wrappers call libc themselves (getcwd opens "."), so a wrapper running
|
|
81
|
+
// inside another one logs nothing: without this they recurse until the stack
|
|
82
|
+
// is gone
|
|
83
|
+
static __thread int busy = 0;
|
|
84
|
+
|
|
85
|
+
/** open a log above fd 1000, remembering which file it is */
|
|
86
|
+
static int open_high(log_t *l) {
|
|
87
|
+
l->fd = -1;
|
|
88
|
+
int fd = open(l->path, O_WRONLY | O_APPEND | O_CREAT | O_CLOEXEC, 0644);
|
|
89
|
+
if (fd < 0) return -1;
|
|
90
|
+
int hi = fcntl(fd, F_DUPFD_CLOEXEC, 1000);
|
|
91
|
+
if (hi >= 0) { close(fd); fd = hi; }
|
|
92
|
+
struct stat s;
|
|
93
|
+
if (fstat(fd, &s) != 0) { close(fd); return -1; }
|
|
94
|
+
l->dev = s.st_dev;
|
|
95
|
+
l->ino = s.st_ino;
|
|
96
|
+
l->fd = fd;
|
|
97
|
+
return fd;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
static void open_logs(void) {
|
|
101
|
+
nlogs = 0;
|
|
102
|
+
logpid = getpid();
|
|
103
|
+
const char *list = getenv("SHIPKIT_TRACE_DIR");
|
|
104
|
+
if (list == NULL || list[0] == '\0') return;
|
|
105
|
+
char buf[4096];
|
|
106
|
+
strlcpy(buf, list, sizeof buf);
|
|
107
|
+
char *save = NULL;
|
|
108
|
+
for (char *d = strtok_r(buf, ":", &save); d != NULL && nlogs < MAX_DIRS; d = strtok_r(NULL, ":", &save)) {
|
|
109
|
+
log_t *l = &logs[nlogs];
|
|
110
|
+
snprintf(l->path, sizeof l->path, "%s/%d.log", d, logpid);
|
|
111
|
+
if (open_high(l) >= 0) nlogs++;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
static void lg(const char *fmt, ...) {
|
|
116
|
+
int was = busy;
|
|
117
|
+
busy = 1;
|
|
118
|
+
int e = errno;
|
|
119
|
+
// a forked child writes its own log, never its parent's
|
|
120
|
+
if (nlogs < 0 || logpid != getpid()) open_logs();
|
|
121
|
+
if (nlogs > 0) {
|
|
122
|
+
char b[8192];
|
|
123
|
+
va_list a;
|
|
124
|
+
va_start(a, fmt);
|
|
125
|
+
int n = vsnprintf(b, sizeof b - 1, fmt, a);
|
|
126
|
+
va_end(a);
|
|
127
|
+
if (n > 0) {
|
|
128
|
+
if (n > (int)sizeof b - 2) n = sizeof b - 2;
|
|
129
|
+
b[n++] = '\n';
|
|
130
|
+
for (int i = 0; i < nlogs; i++) {
|
|
131
|
+
log_t *l = &logs[i];
|
|
132
|
+
struct stat st;
|
|
133
|
+
// the program closed this fd, or reuses its number for its own
|
|
134
|
+
// file: the old number is not ours any more, so reopen
|
|
135
|
+
if (l->fd < 0 || fstat(l->fd, &st) != 0 || st.st_dev != l->dev || st.st_ino != l->ino) {
|
|
136
|
+
if (open_high(l) < 0) continue;
|
|
137
|
+
}
|
|
138
|
+
// one write per line: O_APPEND keeps lines from parallel
|
|
139
|
+
// threads and processes whole
|
|
140
|
+
write(l->fd, b, n);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
errno = e;
|
|
145
|
+
busy = was;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** the dir a relative path resolves against: the dirfd's path, else the cwd */
|
|
149
|
+
static void base_of(int dfd, const char *p, char *dir, size_t n) {
|
|
150
|
+
dir[0] = '\0';
|
|
151
|
+
if (p == NULL || p[0] == '/') return;
|
|
152
|
+
if (dfd != AT_FDCWD && dfd >= 0) fcntl(dfd, F_GETPATH, dir);
|
|
153
|
+
else getcwd(dir, n);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** fds this process opened for writing: their close logs what it left */
|
|
157
|
+
#define MAX_TRACKED 8192
|
|
158
|
+
static unsigned char writing[MAX_TRACKED];
|
|
159
|
+
|
|
160
|
+
static void after_open(int dfd, const char *p, int flags, int r) {
|
|
161
|
+
if (r >= 0 && r < MAX_TRACKED) writing[r] = (flags & (O_WRONLY | O_RDWR | O_APPEND | O_CREAT | O_TRUNC)) != 0;
|
|
162
|
+
if (busy || p == NULL) return;
|
|
163
|
+
busy = 1;
|
|
164
|
+
int e = errno;
|
|
165
|
+
char dir[PATH_MAX];
|
|
166
|
+
base_of(dfd, p, dir, sizeof dir);
|
|
167
|
+
if (r >= 0) {
|
|
168
|
+
struct stat s;
|
|
169
|
+
if (fstat(r, &s) == 0)
|
|
170
|
+
lg("O\t%d\t%s\t%s\t%s\t%lld\t%ld.%09ld", flags, S_ISDIR(s.st_mode) ? "d" : "f", dir, p, (long long)s.st_size, (long)s.st_mtimespec.tv_sec, s.st_mtimespec.tv_nsec);
|
|
171
|
+
} else
|
|
172
|
+
lg("E\topen\t%d\t%s\t%s", e, dir, p);
|
|
173
|
+
errno = e;
|
|
174
|
+
busy = 0;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
static void probe(const char *op, int dfd, const char *p, int r, const struct stat *s) {
|
|
178
|
+
if (busy || p == NULL) return;
|
|
179
|
+
busy = 1;
|
|
180
|
+
int e = errno;
|
|
181
|
+
char dir[PATH_MAX];
|
|
182
|
+
base_of(dfd, p, dir, sizeof dir);
|
|
183
|
+
// a file's size, mtime and inode as the task saw them: a tool with an
|
|
184
|
+
// mtime cache decides from this alone whether to read the file at all
|
|
185
|
+
if (r == 0 && s != NULL && S_ISREG(s->st_mode))
|
|
186
|
+
lg("S\t%s\tf\t%s\t%s\t%lld\t%ld.%09ld\t%llu", op, dir, p, (long long)s->st_size, (long)s->st_mtimespec.tv_sec, s->st_mtimespec.tv_nsec, (unsigned long long)s->st_ino);
|
|
187
|
+
else if (r == 0) lg("S\t%s\t%s\t%s\t%s", op, s == NULL ? "?" : S_ISDIR(s->st_mode) ? "d" : S_ISLNK(s->st_mode) ? "l" : "f", dir, p);
|
|
188
|
+
else lg("E\t%s\t%d\t%s\t%s", op, e, dir, p);
|
|
189
|
+
errno = e;
|
|
190
|
+
busy = 0;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
static void wrote(const char *op, int dfd, const char *p) {
|
|
194
|
+
if (busy || p == NULL) return;
|
|
195
|
+
busy = 1;
|
|
196
|
+
int e = errno;
|
|
197
|
+
char dir[PATH_MAX];
|
|
198
|
+
base_of(dfd, p, dir, sizeof dir);
|
|
199
|
+
lg("W\t%s\t%s\t%s", op, dir, p);
|
|
200
|
+
errno = e;
|
|
201
|
+
busy = 0;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// ── reads and probes ────────────────────────────────────────────────────
|
|
205
|
+
static int mode_of(int flags, va_list a) { return (flags & O_CREAT) ? va_arg(a, int) : 0; }
|
|
206
|
+
|
|
207
|
+
int my_open(const char *p, int flags, ...) {
|
|
208
|
+
va_list a; va_start(a, flags); int m = mode_of(flags, a); va_end(a);
|
|
209
|
+
int r = open(p, flags, m); after_open(AT_FDCWD, p, flags, r); return r;
|
|
210
|
+
}
|
|
211
|
+
INTERPOSE(my_open, open)
|
|
212
|
+
extern int open_nc(const char *p, int flags, ...) __asm("_open$NOCANCEL");
|
|
213
|
+
int my_open_nc(const char *p, int flags, ...) {
|
|
214
|
+
va_list a; va_start(a, flags); int m = mode_of(flags, a); va_end(a);
|
|
215
|
+
int r = open_nc(p, flags, m); after_open(AT_FDCWD, p, flags, r); return r;
|
|
216
|
+
}
|
|
217
|
+
INTERPOSE(my_open_nc, open_nc)
|
|
218
|
+
int my_openat(int d, const char *p, int flags, ...) {
|
|
219
|
+
va_list a; va_start(a, flags); int m = mode_of(flags, a); va_end(a);
|
|
220
|
+
int r = openat(d, p, flags, m); after_open(d, p, flags, r); return r;
|
|
221
|
+
}
|
|
222
|
+
INTERPOSE(my_openat, openat)
|
|
223
|
+
extern int openat_nc(int d, const char *p, int flags, ...) __asm("_openat$NOCANCEL");
|
|
224
|
+
int my_openat_nc(int d, const char *p, int flags, ...) {
|
|
225
|
+
va_list a; va_start(a, flags); int m = mode_of(flags, a); va_end(a);
|
|
226
|
+
int r = openat_nc(d, p, flags, m); after_open(d, p, flags, r); return r;
|
|
227
|
+
}
|
|
228
|
+
INTERPOSE(my_openat_nc, openat_nc)
|
|
229
|
+
|
|
230
|
+
int my_stat(const char *p, struct stat *s) { int r = stat(p, s); probe("stat", AT_FDCWD, p, r, s); return r; }
|
|
231
|
+
INTERPOSE(my_stat, stat)
|
|
232
|
+
int my_lstat(const char *p, struct stat *s) { int r = lstat(p, s); probe("lstat", AT_FDCWD, p, r, s); return r; }
|
|
233
|
+
INTERPOSE(my_lstat, lstat)
|
|
234
|
+
int my_fstatat(int d, const char *p, struct stat *s, int f) {
|
|
235
|
+
int r = fstatat(d, p, s, f); probe(f & AT_SYMLINK_NOFOLLOW ? "lstat" : "stat", d, p, r, s); return r;
|
|
236
|
+
}
|
|
237
|
+
INTERPOSE(my_fstatat, fstatat)
|
|
238
|
+
int my_access(const char *p, int m) {
|
|
239
|
+
int r = access(p, m);
|
|
240
|
+
if (!busy) { struct stat s; int e = errno; int ok = r == 0 && stat(p, &s) == 0; errno = e; probe("access", AT_FDCWD, p, r, ok ? &s : NULL); }
|
|
241
|
+
return r;
|
|
242
|
+
}
|
|
243
|
+
INTERPOSE(my_access, access)
|
|
244
|
+
int my_faccessat(int d, const char *p, int m, int f) {
|
|
245
|
+
int r = faccessat(d, p, m, f);
|
|
246
|
+
if (!busy) { struct stat s; int e = errno; int ok = r == 0 && fstatat(d, p, &s, 0) == 0; errno = e; probe("access", d, p, r, ok ? &s : NULL); }
|
|
247
|
+
return r;
|
|
248
|
+
}
|
|
249
|
+
INTERPOSE(my_faccessat, faccessat)
|
|
250
|
+
/** a symlink read: its target is what the task learned, not the file */
|
|
251
|
+
static void link_read(int dfd, const char *p, const char *b, ssize_t r) {
|
|
252
|
+
if (busy || p == NULL) return;
|
|
253
|
+
busy = 1;
|
|
254
|
+
int e = errno;
|
|
255
|
+
char dir[PATH_MAX];
|
|
256
|
+
base_of(dfd, p, dir, sizeof dir);
|
|
257
|
+
if (r >= 0) {
|
|
258
|
+
char t[PATH_MAX];
|
|
259
|
+
size_t n = (size_t)r < sizeof t - 1 ? (size_t)r : sizeof t - 1;
|
|
260
|
+
memcpy(t, b, n);
|
|
261
|
+
t[n] = '\0';
|
|
262
|
+
lg("K\t%s\t%s\t%s", dir, p, t);
|
|
263
|
+
} else lg("E\treadlink\t%d\t%s\t%s", e, dir, p);
|
|
264
|
+
errno = e;
|
|
265
|
+
busy = 0;
|
|
266
|
+
}
|
|
267
|
+
ssize_t my_readlink(const char *p, char *b, size_t n) { ssize_t r = readlink(p, b, n); link_read(AT_FDCWD, p, b, r); return r; }
|
|
268
|
+
INTERPOSE(my_readlink, readlink)
|
|
269
|
+
ssize_t my_readlinkat(int d, const char *p, char *b, size_t n) { ssize_t r = readlinkat(d, p, b, n); link_read(d, p, b, r); return r; }
|
|
270
|
+
INTERPOSE(my_readlinkat, readlinkat)
|
|
271
|
+
extern char *realpath_ext(const char *p, char *out) __asm("_realpath$DARWIN_EXTSN");
|
|
272
|
+
char *my_realpath(const char *p, char *out) { char *r = realpath_ext(p, out); probe("realpath", AT_FDCWD, p, r != NULL ? 0 : -1, NULL); return r; }
|
|
273
|
+
INTERPOSE(my_realpath, realpath_ext)
|
|
274
|
+
|
|
275
|
+
extern ssize_t getdirentries64(int fd, char *buf, size_t n, off_t *basep) __asm("___getdirentries64");
|
|
276
|
+
/** a dir read through its fd, with the dir's mtime at that moment: an entry
|
|
277
|
+
* another process adds while the task runs moves it */
|
|
278
|
+
static void listed(int fd) {
|
|
279
|
+
if (busy) return;
|
|
280
|
+
busy = 1;
|
|
281
|
+
int e = errno;
|
|
282
|
+
char p[PATH_MAX] = "";
|
|
283
|
+
struct stat s;
|
|
284
|
+
struct timespec now;
|
|
285
|
+
clock_gettime(CLOCK_REALTIME, &now);
|
|
286
|
+
if (fcntl(fd, F_GETPATH, p) == 0 && fstat(fd, &s) == 0) lg("D\t%s\t%ld.%09ld\t%ld.%09ld", p, (long)s.st_mtimespec.tv_sec, s.st_mtimespec.tv_nsec, (long)now.tv_sec, now.tv_nsec);
|
|
287
|
+
errno = e;
|
|
288
|
+
busy = 0;
|
|
289
|
+
}
|
|
290
|
+
/** the names a getdirentries call returned, in lines of at most ~6KB */
|
|
291
|
+
static void names_of(int fd, const char *buf, ssize_t n) {
|
|
292
|
+
if (busy || n <= 0) return;
|
|
293
|
+
busy = 1;
|
|
294
|
+
int e = errno;
|
|
295
|
+
char p[PATH_MAX] = "";
|
|
296
|
+
if (fcntl(fd, F_GETPATH, p) == 0) {
|
|
297
|
+
char line[6400];
|
|
298
|
+
size_t o = 0;
|
|
299
|
+
for (ssize_t at = 0; at < n;) {
|
|
300
|
+
const struct dirent *d = (const struct dirent *)(buf + at);
|
|
301
|
+
if (d->d_reclen == 0) break;
|
|
302
|
+
at += d->d_reclen;
|
|
303
|
+
if (d->d_ino == 0 || strcmp(d->d_name, ".") == 0 || strcmp(d->d_name, "..") == 0) continue;
|
|
304
|
+
if (o + d->d_namlen + 2 > sizeof line) { line[o] = '\0'; lg("M\t%s\t%s", p, line); o = 0; }
|
|
305
|
+
memcpy(line + o, d->d_name, d->d_namlen);
|
|
306
|
+
o += d->d_namlen;
|
|
307
|
+
line[o++] = '\x1f';
|
|
308
|
+
}
|
|
309
|
+
line[o] = '\0';
|
|
310
|
+
lg("M\t%s\t%s", p, line);
|
|
311
|
+
}
|
|
312
|
+
errno = e;
|
|
313
|
+
busy = 0;
|
|
314
|
+
}
|
|
315
|
+
ssize_t my_getdirentries64(int fd, char *buf, size_t n, off_t *basep) { listed(fd); ssize_t r = getdirentries64(fd, buf, n, basep); names_of(fd, buf, r); return r; }
|
|
316
|
+
INTERPOSE(my_getdirentries64, getdirentries64)
|
|
317
|
+
int my_getattrlistbulk(int fd, void *al, void *buf, size_t n, uint64_t o) { listed(fd); return getattrlistbulk(fd, al, buf, n, o); }
|
|
318
|
+
INTERPOSE(my_getattrlistbulk, getattrlistbulk)
|
|
319
|
+
|
|
320
|
+
// ── writes ───────────────────────────────────────────────────────────────
|
|
321
|
+
int my_mkdir(const char *p, mode_t m) { int r = mkdir(p, m); if (r == 0) wrote("mkdir", AT_FDCWD, p); return r; }
|
|
322
|
+
INTERPOSE(my_mkdir, mkdir)
|
|
323
|
+
int my_mkdirat(int d, const char *p, mode_t m) { int r = mkdirat(d, p, m); if (r == 0) wrote("mkdir", d, p); return r; }
|
|
324
|
+
INTERPOSE(my_mkdirat, mkdirat)
|
|
325
|
+
int my_rmdir(const char *p) { int r = rmdir(p); if (r == 0) wrote("rmdir", AT_FDCWD, p); return r; }
|
|
326
|
+
INTERPOSE(my_rmdir, rmdir)
|
|
327
|
+
int my_unlink(const char *p) { int r = unlink(p); if (r == 0) wrote("unlink", AT_FDCWD, p); return r; }
|
|
328
|
+
INTERPOSE(my_unlink, unlink)
|
|
329
|
+
int my_unlinkat(int d, const char *p, int f) { int r = unlinkat(d, p, f); if (r == 0) wrote("unlink", d, p); return r; }
|
|
330
|
+
INTERPOSE(my_unlinkat, unlinkat)
|
|
331
|
+
int my_rename(const char *a, const char *b) { int r = rename(a, b); if (r == 0) { wrote("rename", AT_FDCWD, a); wrote("rename", AT_FDCWD, b); } return r; }
|
|
332
|
+
INTERPOSE(my_rename, rename)
|
|
333
|
+
int my_renameat(int da, const char *a, int db, const char *b) { int r = renameat(da, a, db, b); if (r == 0) { wrote("rename", da, a); wrote("rename", db, b); } return r; }
|
|
334
|
+
INTERPOSE(my_renameat, renameat)
|
|
335
|
+
int my_renameatx_np(int da, const char *a, int db, const char *b, unsigned int f) { int r = renameatx_np(da, a, db, b, f); if (r == 0) { wrote("rename", da, a); wrote("rename", db, b); } return r; }
|
|
336
|
+
INTERPOSE(my_renameatx_np, renameatx_np)
|
|
337
|
+
int my_symlink(const char *t, const char *p) { int r = symlink(t, p); if (r == 0) wrote("symlink", AT_FDCWD, p); return r; }
|
|
338
|
+
INTERPOSE(my_symlink, symlink)
|
|
339
|
+
int my_symlinkat(const char *t, int d, const char *p) { int r = symlinkat(t, d, p); if (r == 0) wrote("symlink", d, p); return r; }
|
|
340
|
+
INTERPOSE(my_symlinkat, symlinkat)
|
|
341
|
+
int my_link(const char *a, const char *b) { int r = link(a, b); if (r == 0) wrote("link", AT_FDCWD, b); return r; }
|
|
342
|
+
INTERPOSE(my_link, link)
|
|
343
|
+
int my_truncate(const char *p, off_t n) { int r = truncate(p, n); if (r == 0) wrote("truncate", AT_FDCWD, p); return r; }
|
|
344
|
+
INTERPOSE(my_truncate, truncate)
|
|
345
|
+
extern int clonefile(const char *src, const char *dst, uint32_t flags);
|
|
346
|
+
int my_clonefile(const char *a, const char *b, uint32_t f) {
|
|
347
|
+
int r = clonefile(a, b, f);
|
|
348
|
+
if (r == 0) { struct stat s; int e = errno; int ok = stat(a, &s) == 0; errno = e; probe("clone", AT_FDCWD, a, ok ? 0 : -1, ok ? &s : NULL); wrote("clone", AT_FDCWD, b); }
|
|
349
|
+
return r;
|
|
350
|
+
}
|
|
351
|
+
INTERPOSE(my_clonefile, clonefile)
|
|
352
|
+
|
|
353
|
+
// ── the network ──────────────────────────────────────────────────────────
|
|
354
|
+
static void addr_line(const char *op, int sock, const struct sockaddr *a, int actual, int r) {
|
|
355
|
+
if (busy) return;
|
|
356
|
+
busy = 1;
|
|
357
|
+
int e = errno;
|
|
358
|
+
int err = r == 0 ? 0 : e;
|
|
359
|
+
struct sockaddr_storage st;
|
|
360
|
+
socklen_t l = sizeof st;
|
|
361
|
+
if (actual && getsockname(sock, (struct sockaddr *)&st, &l) == 0) a = (const struct sockaddr *)&st;
|
|
362
|
+
char host[INET6_ADDRSTRLEN] = "";
|
|
363
|
+
if (a == NULL) lg("%s\t?\t\t0\t%d\t%d\t%d", op, r, err, sock);
|
|
364
|
+
else if (a->sa_family == AF_UNIX) lg("%s\tunix\t%s\t0\t%d\t%d\t%d", op, ((const struct sockaddr_un *)a)->sun_path, r, err, sock);
|
|
365
|
+
else if (a->sa_family == AF_INET) {
|
|
366
|
+
const struct sockaddr_in *i = (const struct sockaddr_in *)a;
|
|
367
|
+
inet_ntop(AF_INET, &i->sin_addr, host, sizeof host);
|
|
368
|
+
lg("%s\tinet\t%s\t%d\t%d\t%d\t%d", op, host, ntohs(i->sin_port), r, err, sock);
|
|
369
|
+
} else if (a->sa_family == AF_INET6) {
|
|
370
|
+
const struct sockaddr_in6 *i = (const struct sockaddr_in6 *)a;
|
|
371
|
+
inet_ntop(AF_INET6, &i->sin6_addr, host, sizeof host);
|
|
372
|
+
lg("%s\tinet6\t%s\t%d\t%d\t%d\t%d", op, host, ntohs(i->sin6_port), r, err, sock);
|
|
373
|
+
} else lg("%s\tfamily%d\t\t0\t%d\t%d\t%d", op, a->sa_family, r, err, sock);
|
|
374
|
+
errno = e;
|
|
375
|
+
busy = 0;
|
|
376
|
+
}
|
|
377
|
+
int my_connect(int s, const struct sockaddr *a, socklen_t l) { int r = connect(s, a, l); addr_line("N", s, a, 0, r); return r; }
|
|
378
|
+
INTERPOSE(my_connect, connect)
|
|
379
|
+
extern int connect_nc(int s, const struct sockaddr *a, socklen_t l) __asm("_connect$NOCANCEL");
|
|
380
|
+
int my_connect_nc(int s, const struct sockaddr *a, socklen_t l) { int r = connect_nc(s, a, l); addr_line("N", s, a, 0, r); return r; }
|
|
381
|
+
INTERPOSE(my_connect_nc, connect_nc)
|
|
382
|
+
// a non-blocking connect ends later, and the runtime reads how through
|
|
383
|
+
// SO_ERROR: that answer says whether anything was listening
|
|
384
|
+
int my_getsockopt(int s, int level, int name, void *v, socklen_t *l) {
|
|
385
|
+
int r = getsockopt(s, level, name, v, l);
|
|
386
|
+
if (r == 0 && level == SOL_SOCKET && name == SO_ERROR && v != NULL && !busy) lg("R\t%d\t%d", s, *(int *)v);
|
|
387
|
+
return r;
|
|
388
|
+
}
|
|
389
|
+
INTERPOSE(my_getsockopt, getsockopt)
|
|
390
|
+
int my_bind(int s, const struct sockaddr *a, socklen_t l) { int r = bind(s, a, l); if (r == 0) addr_line("B", s, a, 1, r); return r; }
|
|
391
|
+
INTERPOSE(my_bind, bind)
|
|
392
|
+
|
|
393
|
+
// ── spawn and exec ───────────────────────────────────────────────────────
|
|
394
|
+
/** the value of NAME in an env array, or NULL */
|
|
395
|
+
static const char *env_in(char *const envp[], const char *name) {
|
|
396
|
+
size_t n = strlen(name);
|
|
397
|
+
for (int i = 0; envp != NULL && envp[i] != NULL; i++)
|
|
398
|
+
if (strncmp(envp[i], name, n) == 0 && envp[i][n] == '=') return envp[i] + n + 1;
|
|
399
|
+
return NULL;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/** `prog` resolved on PATH, into out; 0 when found */
|
|
403
|
+
static int on_path(const char *prog, const char *path, char *out, size_t n) {
|
|
404
|
+
if (strchr(prog, '/') != NULL) { strlcpy(out, prog, n); return access(prog, X_OK); }
|
|
405
|
+
char buf[8192];
|
|
406
|
+
strlcpy(buf, path != NULL ? path : "/usr/bin:/bin", sizeof buf);
|
|
407
|
+
char *save = NULL;
|
|
408
|
+
for (char *d = strtok_r(buf, ":", &save); d != NULL; d = strtok_r(NULL, ":", &save)) {
|
|
409
|
+
snprintf(out, n, "%s/%s", *d == '\0' ? "." : d, prog);
|
|
410
|
+
struct stat s;
|
|
411
|
+
if (access(out, X_OK) == 0 && stat(out, &s) == 0 && S_ISREG(s.st_mode)) return 0;
|
|
412
|
+
}
|
|
413
|
+
return -1;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
typedef struct {
|
|
417
|
+
char from[PATH_MAX];
|
|
418
|
+
char to[PATH_MAX];
|
|
419
|
+
long long size;
|
|
420
|
+
long mtime;
|
|
421
|
+
char env[1024];
|
|
422
|
+
} remap_t;
|
|
423
|
+
static remap_t *remaps = NULL;
|
|
424
|
+
static int nremaps = -1;
|
|
425
|
+
|
|
426
|
+
static void load_remaps(void) {
|
|
427
|
+
nremaps = 0;
|
|
428
|
+
const char *f = getenv("SHIPKIT_TRACE_REMAP");
|
|
429
|
+
if (f == NULL) return;
|
|
430
|
+
int fd = open(f, O_RDONLY | O_CLOEXEC);
|
|
431
|
+
if (fd < 0) return;
|
|
432
|
+
char buf[65536];
|
|
433
|
+
ssize_t n = read(fd, buf, sizeof buf - 1);
|
|
434
|
+
close(fd);
|
|
435
|
+
if (n <= 0) return;
|
|
436
|
+
buf[n] = '\0';
|
|
437
|
+
int cap = 0;
|
|
438
|
+
for (ssize_t i = 0; i < n; i++) if (buf[i] == '\n') cap++;
|
|
439
|
+
remaps = calloc(cap + 1, sizeof(remap_t));
|
|
440
|
+
if (remaps == NULL) return;
|
|
441
|
+
char *save = NULL;
|
|
442
|
+
for (char *line = strtok_r(buf, "\n", &save); line != NULL; line = strtok_r(NULL, "\n", &save)) {
|
|
443
|
+
remap_t *r = &remaps[nremaps];
|
|
444
|
+
char *parts[5] = {0};
|
|
445
|
+
int k = 0;
|
|
446
|
+
char *p2 = NULL;
|
|
447
|
+
for (char *t = strtok_r(line, "\t", &p2); t != NULL && k < 5; t = strtok_r(NULL, "\t", &p2)) parts[k++] = t;
|
|
448
|
+
if (k < 4) continue;
|
|
449
|
+
strlcpy(r->from, parts[0], sizeof r->from);
|
|
450
|
+
strlcpy(r->to, parts[1], sizeof r->to);
|
|
451
|
+
r->size = atoll(parts[2]);
|
|
452
|
+
r->mtime = atol(parts[3]);
|
|
453
|
+
strlcpy(r->env, k > 4 ? parts[4] : "", sizeof r->env);
|
|
454
|
+
nremaps++;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/** the traceable copy of `path`, while `path` is still the file the copy was
|
|
459
|
+
* made from */
|
|
460
|
+
static const remap_t *remap_of(const char *path) {
|
|
461
|
+
if (nremaps < 0) load_remaps();
|
|
462
|
+
for (int i = 0; i < nremaps; i++) {
|
|
463
|
+
if (strcmp(remaps[i].from, path) != 0) continue;
|
|
464
|
+
struct stat s;
|
|
465
|
+
if (stat(path, &s) != 0 || s.st_size != remaps[i].size || s.st_mtimespec.tv_sec != remaps[i].mtime) return NULL;
|
|
466
|
+
return &remaps[i];
|
|
467
|
+
}
|
|
468
|
+
return NULL;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/** a new env array: envp, the tracing vars when a caller dropped them, and a
|
|
472
|
+
* remap's own assignments (`K=V;K=V`) */
|
|
473
|
+
static char **env_for(char *const envp[], const remap_t *r) {
|
|
474
|
+
int n = 0;
|
|
475
|
+
while (envp != NULL && envp[n] != NULL) n++;
|
|
476
|
+
char **out = calloc(n + 16, sizeof(char *));
|
|
477
|
+
if (out == NULL) return (char **)envp;
|
|
478
|
+
int k = 0;
|
|
479
|
+
for (int i = 0; i < n; i++) out[k++] = envp[i];
|
|
480
|
+
const char *keep[] = {"DYLD_INSERT_LIBRARIES", "SHIPKIT_TRACE_DIR", "SHIPKIT_TRACE_REMAP"};
|
|
481
|
+
for (int i = 0; i < 3; i++) {
|
|
482
|
+
const char *mine = getenv(keep[i]);
|
|
483
|
+
if (mine == NULL || env_in(envp, keep[i]) != NULL) continue;
|
|
484
|
+
size_t l = strlen(keep[i]) + strlen(mine) + 2;
|
|
485
|
+
char *v = malloc(l);
|
|
486
|
+
if (v != NULL) { snprintf(v, l, "%s=%s", keep[i], mine); out[k++] = v; }
|
|
487
|
+
}
|
|
488
|
+
if (r != NULL && r->env[0] != '\0') {
|
|
489
|
+
char *copy = strdup(r->env);
|
|
490
|
+
char *save = NULL;
|
|
491
|
+
for (char *t = strtok_r(copy, ";", &save); t != NULL && k < n + 15; t = strtok_r(NULL, ";", &save)) out[k++] = t;
|
|
492
|
+
}
|
|
493
|
+
out[k] = NULL;
|
|
494
|
+
return out;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
typedef struct {
|
|
498
|
+
char path[PATH_MAX];
|
|
499
|
+
char **argv;
|
|
500
|
+
char **envp;
|
|
501
|
+
} plan_t;
|
|
502
|
+
|
|
503
|
+
/** what to actually run for (path, argv): a `#!/usr/bin/env X` script runs
|
|
504
|
+
* as X, a remapped binary as its traceable copy */
|
|
505
|
+
static void plan(const char *path, char *const argv[], char *const envp[], int search, plan_t *out) {
|
|
506
|
+
char *const *env = envp != NULL ? envp : environ;
|
|
507
|
+
out->argv = (char **)argv;
|
|
508
|
+
strlcpy(out->path, path, sizeof out->path);
|
|
509
|
+
char found[PATH_MAX];
|
|
510
|
+
if (search && on_path(path, env_in(env, "PATH"), found, sizeof found) == 0) strlcpy(out->path, found, sizeof out->path);
|
|
511
|
+
int fd = open(out->path, O_RDONLY | O_CLOEXEC);
|
|
512
|
+
if (fd >= 0) {
|
|
513
|
+
char head[512] = "";
|
|
514
|
+
ssize_t n = read(fd, head, sizeof head - 1);
|
|
515
|
+
close(fd);
|
|
516
|
+
if (n > 2 && head[0] == '#' && head[1] == '!') {
|
|
517
|
+
head[n] = '\0';
|
|
518
|
+
char *nl = strchr(head, '\n');
|
|
519
|
+
if (nl != NULL) *nl = '\0';
|
|
520
|
+
char *save = NULL;
|
|
521
|
+
char *interp = strtok_r(head + 2, " \t", &save);
|
|
522
|
+
char *prog = interp == NULL ? NULL : strtok_r(NULL, " \t", &save);
|
|
523
|
+
char *rest = prog == NULL ? NULL : strtok_r(NULL, "", &save);
|
|
524
|
+
char resolved[PATH_MAX];
|
|
525
|
+
// `env -S` and friends are left to the kernel: the reader then
|
|
526
|
+
// sees the hole and keeps nothing, which is the safe way to fail
|
|
527
|
+
if (interp != NULL && strcmp(interp, "/usr/bin/env") == 0 && prog != NULL && prog[0] != '-' && rest == NULL &&
|
|
528
|
+
on_path(prog, env_in(env, "PATH"), resolved, sizeof resolved) == 0) {
|
|
529
|
+
int argc = 0;
|
|
530
|
+
while (argv != NULL && argv[argc] != NULL) argc++;
|
|
531
|
+
char **nargv = calloc(argc + 3, sizeof(char *));
|
|
532
|
+
if (nargv != NULL) {
|
|
533
|
+
nargv[0] = strdup(prog);
|
|
534
|
+
nargv[1] = strdup(out->path);
|
|
535
|
+
for (int i = 1; i < argc; i++) nargv[i + 1] = argv[i];
|
|
536
|
+
out->argv = nargv;
|
|
537
|
+
strlcpy(out->path, resolved, sizeof out->path);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
const remap_t *r = remap_of(out->path);
|
|
543
|
+
if (r != NULL) strlcpy(out->path, r->to, sizeof out->path);
|
|
544
|
+
out->envp = env_for(env, r);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
static int setexec(const posix_spawnattr_t *at) {
|
|
548
|
+
short f = 0;
|
|
549
|
+
return at != NULL && posix_spawnattr_getflags(at, &f) == 0 && (f & POSIX_SPAWN_SETEXEC) != 0;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
static void spawn_line(const char *op, int r, int pid, const char *asked, const plan_t *p) {
|
|
553
|
+
if (busy) return;
|
|
554
|
+
busy = 1;
|
|
555
|
+
char b[4096] = "";
|
|
556
|
+
size_t o = 0;
|
|
557
|
+
for (int i = 0; p->argv != NULL && p->argv[i] != NULL && o < sizeof b - 256; i++) o += snprintf(b + o, sizeof b - o, "%s\x1f", p->argv[i]);
|
|
558
|
+
lg("P\t%s\t%d\t%d\t%s\t%s\t%s", op, r, pid, asked, p->path, b);
|
|
559
|
+
busy = 0;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
int my_posix_spawn(pid_t *pid, const char *path, const posix_spawn_file_actions_t *fa, const posix_spawnattr_t *at, char *const argv[], char *const envp[]) {
|
|
563
|
+
if (busy) return posix_spawn(pid, path, fa, at, argv, envp);
|
|
564
|
+
plan_t p;
|
|
565
|
+
busy = 1;
|
|
566
|
+
plan(path, argv, envp, 0, &p);
|
|
567
|
+
busy = 0;
|
|
568
|
+
pid_t child = -1;
|
|
569
|
+
// a SETEXEC spawn replaces this process and never returns: logged first,
|
|
570
|
+
// exactly like an exec
|
|
571
|
+
if (setexec(at)) spawn_line("exec", 0, getpid(), path, &p);
|
|
572
|
+
int r = posix_spawn(&child, p.path, fa, at, p.argv, p.envp);
|
|
573
|
+
if (setexec(at)) { spawn_line("execfail", r, getpid(), path, &p); return r; }
|
|
574
|
+
if (pid != NULL) *pid = child;
|
|
575
|
+
spawn_line("spawn", r, child, path, &p);
|
|
576
|
+
return r;
|
|
577
|
+
}
|
|
578
|
+
INTERPOSE(my_posix_spawn, posix_spawn)
|
|
579
|
+
int my_posix_spawnp(pid_t *pid, const char *path, const posix_spawn_file_actions_t *fa, const posix_spawnattr_t *at, char *const argv[], char *const envp[]) {
|
|
580
|
+
if (busy) return posix_spawnp(pid, path, fa, at, argv, envp);
|
|
581
|
+
plan_t p;
|
|
582
|
+
busy = 1;
|
|
583
|
+
plan(path, argv, envp, 1, &p);
|
|
584
|
+
busy = 0;
|
|
585
|
+
pid_t child = -1;
|
|
586
|
+
if (setexec(at)) spawn_line("exec", 0, getpid(), path, &p);
|
|
587
|
+
int r = posix_spawn(&child, p.path, fa, at, p.argv, p.envp);
|
|
588
|
+
if (setexec(at)) { spawn_line("execfail", r, getpid(), path, &p); return r; }
|
|
589
|
+
if (pid != NULL) *pid = child;
|
|
590
|
+
spawn_line("spawn", r, child, path, &p);
|
|
591
|
+
return r;
|
|
592
|
+
}
|
|
593
|
+
INTERPOSE(my_posix_spawnp, posix_spawnp)
|
|
594
|
+
/** set by the constructor: libSystem closes fds while it starts, before
|
|
595
|
+
* thread-local storage exists, and touching `busy` then aborts the process */
|
|
596
|
+
static volatile int ready = 0;
|
|
597
|
+
|
|
598
|
+
static void closing(int fd) {
|
|
599
|
+
if (!ready || fd < 0 || fd >= MAX_TRACKED || !writing[fd]) return;
|
|
600
|
+
// what a write left, read before the fd goes: another writer after this
|
|
601
|
+
// moved the file under the task
|
|
602
|
+
if (!busy) {
|
|
603
|
+
writing[fd] = 0;
|
|
604
|
+
busy = 1;
|
|
605
|
+
int e = errno;
|
|
606
|
+
char p[PATH_MAX] = "";
|
|
607
|
+
struct stat st;
|
|
608
|
+
if (fcntl(fd, F_GETPATH, p) == 0 && fstat(fd, &st) == 0 && S_ISREG(st.st_mode))
|
|
609
|
+
lg("C\t%s\t%lld\t%ld.%09ld\t%llu", p, (long long)st.st_size, (long)st.st_mtimespec.tv_sec, st.st_mtimespec.tv_nsec, (unsigned long long)st.st_ino);
|
|
610
|
+
errno = e;
|
|
611
|
+
busy = 0;
|
|
612
|
+
} else writing[fd] = 0;
|
|
613
|
+
}
|
|
614
|
+
int my_close(int fd) { closing(fd); return close(fd); }
|
|
615
|
+
INTERPOSE(my_close, close)
|
|
616
|
+
extern int close_nc(int fd) __asm("_close$NOCANCEL");
|
|
617
|
+
int my_close_nc(int fd) { closing(fd); return close_nc(fd); }
|
|
618
|
+
INTERPOSE(my_close_nc, close_nc)
|
|
619
|
+
int my_execve(const char *path, char *const argv[], char *const envp[]) {
|
|
620
|
+
if (busy) return execve(path, argv, envp);
|
|
621
|
+
plan_t p;
|
|
622
|
+
busy = 1;
|
|
623
|
+
plan(path, argv, envp, 0, &p);
|
|
624
|
+
busy = 0;
|
|
625
|
+
// written BEFORE the exec, which does not return when it works
|
|
626
|
+
spawn_line("exec", 0, getpid(), path, &p);
|
|
627
|
+
int r = execve(p.path, p.argv, p.envp);
|
|
628
|
+
int e = errno;
|
|
629
|
+
spawn_line("execfail", r, getpid(), path, &p);
|
|
630
|
+
errno = e;
|
|
631
|
+
return r;
|
|
632
|
+
}
|
|
633
|
+
INTERPOSE(my_execve, execve)
|
|
634
|
+
|
|
635
|
+
/** every image dyld maps: the executable, its libraries, a dlopen'd addon.
|
|
636
|
+
* dyld opens them with its own syscalls, so no open wrapper sees them. The
|
|
637
|
+
* OS images in the shared cache are left out */
|
|
638
|
+
static void loaded(const struct mach_header *mh, intptr_t slide) {
|
|
639
|
+
(void)slide;
|
|
640
|
+
Dl_info info;
|
|
641
|
+
if (dladdr(mh, &info) == 0 || info.dli_fname == NULL) return;
|
|
642
|
+
const char *p = info.dli_fname;
|
|
643
|
+
if (strncmp(p, "/System/", 8) == 0 || strncmp(p, "/usr/lib/", 9) == 0) return;
|
|
644
|
+
lg("L\t%s", p);
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
__attribute__((constructor)) static void hello(void) {
|
|
648
|
+
char exe[PATH_MAX] = "";
|
|
649
|
+
uint32_t n = sizeof exe;
|
|
650
|
+
_NSGetExecutablePath(exe, &n);
|
|
651
|
+
lg("H\t%d\t%d\t%s", getpid(), getppid(), exe);
|
|
652
|
+
// called at once for every image already loaded, then for each new one
|
|
653
|
+
_dyld_register_func_for_add_image(loaded);
|
|
654
|
+
ready = 1;
|
|
655
|
+
}
|