@gtkx/vitest 1.6.0 → 2.0.0-beta.10

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.
@@ -0,0 +1,309 @@
1
+ import {
2
+ type CleanupDirectoryIdentity,
3
+ cleanupDirectoryIdentity,
4
+ info,
5
+ removeCleanupDirectory,
6
+ } from "@gtkx/utils";
7
+ import { constants, lstatSync, readdirSync, readFileSync } from "node:fs";
8
+ import { tmpdir } from "node:os";
9
+ import { basename, dirname, join } from "node:path";
10
+ import {
11
+ createBusConfig,
12
+ createHeadlessRuntimeMarker,
13
+ HEADLESS_RUNTIME_MARKER,
14
+ isSwayConfig,
15
+ } from "./headless-config.ts";
16
+
17
+ type StaleHeadlessDisplay = {
18
+ runtimeDir: string;
19
+ cleanupDirectory: CleanupDirectoryIdentity;
20
+ };
21
+
22
+ const RUNTIME_DIRECTORY_PATTERN = /^gtkx-xdg-[A-Za-z0-9]{6}$/;
23
+ const STOPPED_PROCESS_STATES: Set<string> = new Set(["Z", "X", "x"]);
24
+ const CONFIG_SIZE_LIMIT = 2048;
25
+ const MINIMUM_STALE_AGE_MS = 5000;
26
+ const RUNTIME_ROOT = tmpdir();
27
+
28
+ const currentUserId = (): number | undefined => {
29
+ const getuid = process.getuid;
30
+
31
+ return getuid === undefined ? undefined : getuid();
32
+ };
33
+
34
+ const isUserOwned = (path: string, userId: number): boolean => {
35
+ try {
36
+ return lstatSync(path).uid === userId;
37
+ } catch {
38
+ return false;
39
+ }
40
+ };
41
+
42
+ const isPrivateRuntimeDirectory = (runtimeDir: string, userId: number): boolean => {
43
+ try {
44
+ const stat = lstatSync(runtimeDir);
45
+
46
+ return stat.isDirectory() &&
47
+ stat.uid === userId &&
48
+ (stat.mode & 0o777) === 0o700 &&
49
+ Date.now() - stat.mtimeMs >= MINIMUM_STALE_AGE_MS &&
50
+ RUNTIME_DIRECTORY_PATTERN.test(basename(runtimeDir)) &&
51
+ dirname(runtimeDir) === RUNTIME_ROOT;
52
+ } catch {
53
+ return false;
54
+ }
55
+ };
56
+
57
+ const readOwnedFile = (
58
+ path: string,
59
+ runtimeDir: string,
60
+ userId: number,
61
+ requiredMode?: number,
62
+ ): string | undefined => {
63
+ try {
64
+ if (dirname(path) !== runtimeDir) {
65
+ return undefined;
66
+ }
67
+
68
+ const contents = readFileSync(path, {
69
+ encoding: "utf8",
70
+ flag: constants.O_RDONLY | constants.O_NOFOLLOW,
71
+ });
72
+ const stat = lstatSync(path);
73
+
74
+ return stat.isFile() &&
75
+ stat.uid === userId &&
76
+ stat.size < CONFIG_SIZE_LIMIT &&
77
+ (requiredMode === undefined || (stat.mode & 0o777) === requiredMode)
78
+ ? contents
79
+ : undefined;
80
+ } catch {
81
+ return undefined;
82
+ }
83
+ };
84
+
85
+ const hasOnlyBusConfig = (runtimeDir: string): boolean => {
86
+ try {
87
+ const entries = readdirSync(runtimeDir);
88
+
89
+ return entries.length === 1 && entries[0] === "session.conf";
90
+ } catch {
91
+ return false;
92
+ }
93
+ };
94
+
95
+ const hasGeneratedRuntimeFiles = (runtimeDir: string, userId: number): boolean => {
96
+ const bus = readOwnedFile(join(runtimeDir, "session.conf"), runtimeDir, userId);
97
+
98
+ if (bus !== createBusConfig(join(runtimeDir, "bus"))) {
99
+ return false;
100
+ }
101
+
102
+ const sway = readOwnedFile(join(runtimeDir, "sway.conf"), runtimeDir, userId);
103
+ const marker = readOwnedFile(join(runtimeDir, HEADLESS_RUNTIME_MARKER), runtimeDir, userId, 0o600);
104
+
105
+ return marker === createHeadlessRuntimeMarker(runtimeDir) ||
106
+ (sway !== undefined && isSwayConfig(sway)) ||
107
+ hasOnlyBusConfig(runtimeDir);
108
+ };
109
+
110
+ const readProcessArguments = (pid: number): string[] | undefined => {
111
+ try {
112
+ const stat = readFileSync(`/proc/${String(pid)}/stat`, "utf8");
113
+ const state = stat.slice(stat.lastIndexOf(") ") + 2).split(" ", 1)[0];
114
+
115
+ if (state === undefined || STOPPED_PROCESS_STATES.has(state)) {
116
+ return undefined;
117
+ }
118
+
119
+ return readFileSync(`/proc/${String(pid)}/cmdline`)
120
+ .toString()
121
+ .split("\0")
122
+ .filter((argument) => argument.length > 0);
123
+ } catch {
124
+ return undefined;
125
+ }
126
+ };
127
+
128
+ const runtimeFromArgument = (argument: string): string | undefined => {
129
+ const path = argument.startsWith("--config-file=") ? argument.slice("--config-file=".length) : argument;
130
+ const name = basename(path);
131
+
132
+ if (name !== "sway.conf" && name !== "session.conf") {
133
+ return undefined;
134
+ }
135
+
136
+ const runtimeDir = dirname(path);
137
+
138
+ return RUNTIME_DIRECTORY_PATTERN.test(basename(runtimeDir)) && dirname(runtimeDir) === RUNTIME_ROOT
139
+ ? runtimeDir
140
+ : undefined;
141
+ };
142
+
143
+ const runtimeFromEnvironment = (pid: number): string | undefined => {
144
+ try {
145
+ const prefix = "XDG_RUNTIME_DIR=";
146
+ const entry = readFileSync(`/proc/${String(pid)}/environ`, "utf8")
147
+ .split("\0")
148
+ .find((value) => value.startsWith(prefix));
149
+ const runtimeDir = entry?.slice(prefix.length);
150
+
151
+ return runtimeDir !== undefined &&
152
+ RUNTIME_DIRECTORY_PATTERN.test(basename(runtimeDir)) &&
153
+ dirname(runtimeDir) === RUNTIME_ROOT
154
+ ? runtimeDir
155
+ : undefined;
156
+ } catch {
157
+ return undefined;
158
+ }
159
+ };
160
+
161
+ const isOwnedProcessId = (pid: number, userId: number): boolean =>
162
+ Number.isSafeInteger(pid) && pid > 1 && isUserOwned(`/proc/${String(pid)}`, userId);
163
+
164
+ const addRuntimeArguments = (live: Set<string>, processArgs: string[]): void => {
165
+ for (const argument of processArgs) {
166
+ const runtimeDir = runtimeFromArgument(argument);
167
+
168
+ if (runtimeDir !== undefined) {
169
+ live.add(runtimeDir);
170
+ }
171
+ }
172
+ };
173
+
174
+ const addProcessRuntimes = (live: Set<string>, pid: number): void => {
175
+ const processArgs = readProcessArguments(pid);
176
+
177
+ if (processArgs === undefined) {
178
+ return;
179
+ }
180
+
181
+ addRuntimeArguments(live, processArgs);
182
+ const runtimeDir = runtimeFromEnvironment(pid);
183
+
184
+ if (runtimeDir !== undefined) {
185
+ live.add(runtimeDir);
186
+ }
187
+ };
188
+
189
+ const findLiveRuntimeDirectories = (userId: number): Set<string> => {
190
+ const live: Set<string> = new Set();
191
+ const processEntries = readdirSync("/proc");
192
+
193
+ for (const entry of processEntries) {
194
+ const pid = Number(entry);
195
+
196
+ if (!isOwnedProcessId(pid, userId)) {
197
+ continue;
198
+ }
199
+
200
+ addProcessRuntimes(live, pid);
201
+ }
202
+
203
+ return live;
204
+ };
205
+
206
+ const classifyRuntimeDirectory = (
207
+ name: string,
208
+ userId: number,
209
+ live: ReadonlySet<string>,
210
+ ): StaleHeadlessDisplay[] => {
211
+ const runtimeDir = join(RUNTIME_ROOT, name);
212
+
213
+ if (
214
+ live.has(runtimeDir) ||
215
+ !isPrivateRuntimeDirectory(runtimeDir, userId) ||
216
+ !hasGeneratedRuntimeFiles(runtimeDir, userId)
217
+ ) {
218
+ return [];
219
+ }
220
+
221
+ const cleanupDirectory = cleanupDirectoryIdentity(runtimeDir);
222
+
223
+ return cleanupDirectory === undefined ? [] : [{ runtimeDir, cleanupDirectory }];
224
+ };
225
+
226
+ const findStaleHeadlessDisplays = (): StaleHeadlessDisplay[] => {
227
+ const userId = currentUserId();
228
+
229
+ if (userId === undefined) {
230
+ return [];
231
+ }
232
+
233
+ const live = findLiveRuntimeDirectories(userId);
234
+
235
+ return readdirSync(RUNTIME_ROOT, { withFileTypes: true })
236
+ .filter((entry) => entry.isDirectory() && RUNTIME_DIRECTORY_PATTERN.test(entry.name))
237
+ .flatMap((entry) => classifyRuntimeDirectory(entry.name, userId, live));
238
+ };
239
+
240
+ const isSameCleanupDirectory = (
241
+ left: CleanupDirectoryIdentity | undefined,
242
+ right: CleanupDirectoryIdentity,
243
+ ): boolean =>
244
+ left?.device === right.device &&
245
+ left.inode === right.inode &&
246
+ left.userId === right.userId;
247
+
248
+ const isReapable = (
249
+ candidate: StaleHeadlessDisplay,
250
+ userId: number,
251
+ live: ReadonlySet<string>,
252
+ ): boolean =>
253
+ !live.has(candidate.runtimeDir) &&
254
+ isSameCleanupDirectory(cleanupDirectoryIdentity(candidate.runtimeDir), candidate.cleanupDirectory) &&
255
+ isPrivateRuntimeDirectory(candidate.runtimeDir, userId) &&
256
+ hasGeneratedRuntimeFiles(candidate.runtimeDir, userId);
257
+
258
+ const didReapCandidate = (
259
+ candidate: StaleHeadlessDisplay,
260
+ userId: number,
261
+ live: ReadonlySet<string>,
262
+ ): boolean => {
263
+ if (!isReapable(candidate, userId, live)) {
264
+ return false;
265
+ }
266
+
267
+ removeCleanupDirectory(candidate.cleanupDirectory);
268
+
269
+ return cleanupDirectoryIdentity(candidate.runtimeDir) === undefined;
270
+ };
271
+
272
+ const reapStaleHeadlessDisplays = (
273
+ candidates: readonly StaleHeadlessDisplay[] = findStaleHeadlessDisplays(),
274
+ ): string[] => {
275
+ const userId = currentUserId();
276
+
277
+ if (userId === undefined) {
278
+ return [];
279
+ }
280
+
281
+ const live = findLiveRuntimeDirectories(userId);
282
+ const removed: string[] = [];
283
+
284
+ for (const candidate of candidates) {
285
+ if (didReapCandidate(candidate, userId, live)) {
286
+ removed.push(candidate.runtimeDir);
287
+ }
288
+ }
289
+
290
+ return removed;
291
+ };
292
+
293
+ const reapStaleHeadlessDisplaysAtStartup = (): void => {
294
+ const removed = reapStaleHeadlessDisplays();
295
+
296
+ if (removed.length === 0) {
297
+ return;
298
+ }
299
+
300
+ const noun = removed.length === 1 ? "directory" : "directories";
301
+ info(`removed stale headless runtime ${noun}: ${removed.join(", ")}`);
302
+ };
303
+
304
+ export {
305
+ findStaleHeadlessDisplays,
306
+ reapStaleHeadlessDisplays,
307
+ reapStaleHeadlessDisplaysAtStartup,
308
+ type StaleHeadlessDisplay,
309
+ };
@@ -1,8 +1,13 @@
1
- import { installGracefulShutdown } from "@gtkx/utils";
1
+ import { installGracefulShutdown, watchParentProcess } from "@gtkx/utils";
2
+ import { isMainThread } from "node:worker_threads";
2
3
  import { readHeadlessOptions, resolveHeadlessOptions, startHeadlessDisplay } from "./headless-display.ts";
3
4
 
4
- const options = readHeadlessOptions(new URL(import.meta.url).searchParams);
5
- const teardown = await startHeadlessDisplay(resolveHeadlessOptions(options));
5
+ if (isMainThread) {
6
+ watchParentProcess();
6
7
 
7
- process.on("exit", teardown);
8
- installGracefulShutdown({ onSignal: teardown });
8
+ const options = readHeadlessOptions(new URL(import.meta.url).searchParams);
9
+ const teardown = await startHeadlessDisplay(resolveHeadlessOptions(options));
10
+
11
+ process.on("exit", teardown);
12
+ installGracefulShutdown({ onSignal: teardown });
13
+ }