agl 21.0.2 → 22.0.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 +9 -0
- package/dist_serve/bundle.js +1 -1
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.aglhome.d.ts +25 -0
- package/dist_ts/classes.aglhome.js +65 -0
- package/dist_ts/classes.authmodels.js +7 -12
- package/dist_ts/classes.authstore.js +2 -29
- package/dist_ts/classes.cli.js +93 -35
- package/dist_ts/classes.config.d.ts +2 -11
- package/dist_ts/classes.config.js +14 -80
- package/dist_ts/classes.controller.d.ts +4 -1
- package/dist_ts/classes.controller.js +64 -26
- package/dist_ts/classes.embeddeddb.js +6 -5
- package/dist_ts/classes.gitreversion.js +3 -2
- package/dist_ts/classes.upgradecoordinator.d.ts +6 -1
- package/dist_ts/classes.upgradecoordinator.js +588 -177
- package/dist_ts/classes.upgradetransaction.js +46 -12
- package/dist_ts/classes.uploadmanager.d.ts +12 -0
- package/dist_ts/classes.uploadmanager.js +204 -2
- package/dist_ts/constants.upgradeenvironment.d.ts +2 -0
- package/dist_ts/constants.upgradeenvironment.js +3 -0
- package/dist_ts/functions.controllerdataroot.d.ts +4 -5
- package/dist_ts/functions.controllerdataroot.js +4 -29
- package/dist_ts/functions.embeddeddb.d.ts +1 -1
- package/dist_ts/functions.embeddeddb.js +8 -3
- package/dist_ts/functions.runtimeenvironment.js +2 -1
- package/dist_ts/index.d.ts +1 -0
- package/dist_ts/index.js +2 -1
- package/dist_ts/interfaces.config.d.ts +5 -7
- package/dist_ts_migration/classes.documentmigrationrunner.js +3 -1
- package/dist_ts_migration/index.d.ts +2 -0
- package/dist_ts_migration/index.js +3 -1
- package/dist_ts_migration/v23_aglhome.d.ts +91 -0
- package/dist_ts_migration/v23_aglhome.js +1775 -0
- package/dist_ts_migration/v23_runtimeconfig.d.ts +11 -0
- package/dist_ts_migration/v23_runtimeconfig.js +87 -0
- package/dist_ts_migration/v2_controllerdataroot.d.ts +5 -0
- package/dist_ts_migration/v2_controllerdataroot.js +176 -15
- package/package.json +1 -1
- package/readme.md +116 -35
- package/readme.plan.md +24 -9
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.aglhome.ts +116 -0
- package/ts/classes.authmodels.ts +5 -13
- package/ts/classes.authstore.ts +1 -39
- package/ts/classes.cli.ts +92 -34
- package/ts/classes.config.ts +20 -117
- package/ts/classes.controller.ts +92 -26
- package/ts/classes.embeddeddb.ts +5 -4
- package/ts/classes.gitreversion.ts +3 -2
- package/ts/classes.upgradecoordinator.ts +668 -191
- package/ts/classes.upgradetransaction.ts +46 -11
- package/ts/classes.uploadmanager.ts +231 -1
- package/ts/constants.upgradeenvironment.ts +2 -0
- package/ts/functions.controllerdataroot.ts +11 -47
- package/ts/functions.embeddeddb.ts +13 -2
- package/ts/functions.runtimeenvironment.ts +1 -0
- package/ts/index.ts +1 -0
- package/ts/interfaces.config.ts +5 -7
- package/ts_migration/classes.documentmigrationrunner.ts +2 -0
- package/ts_migration/index.ts +2 -0
- package/ts_migration/v23_aglhome.ts +2101 -0
- package/ts_migration/v23_runtimeconfig.ts +112 -0
- package/ts_migration/v2_controllerdataroot.ts +174 -16
- package/ts_web/00_commitinfo_data.ts +1 -1
|
@@ -9,6 +9,11 @@ import {
|
|
|
9
9
|
readControllerProcessIdentity,
|
|
10
10
|
readProcessGroupMemberPids,
|
|
11
11
|
} from './classes.processinspection.js';
|
|
12
|
+
import { resolveAGLHomePaths } from './classes.aglhome.js';
|
|
13
|
+
import {
|
|
14
|
+
upgradeCoordinationRootEnvironmentVariable,
|
|
15
|
+
upgradeTokenEnvironmentVariable,
|
|
16
|
+
} from './constants.upgradeenvironment.js';
|
|
12
17
|
|
|
13
18
|
export const upgradeCoordinationVersion = 2 as const;
|
|
14
19
|
const coordinationVersion = upgradeCoordinationVersion;
|
|
@@ -42,15 +47,17 @@ const transactionMetadataPattern = /^transaction-[a-f0-9]{20}\.json$/;
|
|
|
42
47
|
const transactionTemporaryMetadataPattern = /^transaction-[a-f0-9]{20}\.json\.tmp-[1-9][0-9]*-[a-f0-9]{8}$/;
|
|
43
48
|
const transactionMutationDirectoryPattern = /^transaction-[a-f0-9]{20}\.json\.lock$/;
|
|
44
49
|
const ownershipGuardDirectoryPattern = /^[a-f0-9]{64}\.guard$/;
|
|
50
|
+
const ownershipGuardTemporaryPattern = /^[a-f0-9]{64}\.guard\.tmp-[1-9][0-9]*-[a-f0-9]{32}$/;
|
|
51
|
+
const ownerRemovalPattern = /^\.owner-removing-([1-9][0-9]*)-([a-f0-9]{64})-([a-f0-9]{16})$/;
|
|
52
|
+
const legacyOwnerRemovalPattern = /^\.owner-removing-([1-9][0-9]*)-([a-f0-9]{16})$/;
|
|
45
53
|
const tokenMetadataRetentionMs = 10 * 60 * 1000;
|
|
46
54
|
const transactionRetentionMs = 24 * 60 * 60 * 1000;
|
|
47
55
|
const terminalStatusRetentionMs = 10 * 60 * 1000;
|
|
48
56
|
const transactionMutationTimeoutMs = 10_000;
|
|
49
57
|
const ownershipGuardTimeoutMs = 10_000;
|
|
58
|
+
const legacyOwnerRemovalGraceMs = 30_000;
|
|
50
59
|
const workerTerminationGraceMs = 5_000;
|
|
51
60
|
|
|
52
|
-
export const upgradeTokenEnvironmentVariable = 'HARNESS_CONTROLLER_UPGRADE_TOKEN';
|
|
53
|
-
|
|
54
61
|
type TUpgradeOwnerKind = 'upgrade' | 'start';
|
|
55
62
|
|
|
56
63
|
interface IUpgradeProcessOwner {
|
|
@@ -207,6 +214,13 @@ interface IUpgradeOwnershipMutationGuardOwner {
|
|
|
207
214
|
createdAt: number;
|
|
208
215
|
}
|
|
209
216
|
|
|
217
|
+
interface IUpgradeOwnershipMutationGuardState {
|
|
218
|
+
state: 'absent' | 'empty' | 'live' | 'stale';
|
|
219
|
+
identity?: { dev: number; ino: number };
|
|
220
|
+
owner?: IUpgradeOwnershipMutationGuardOwner;
|
|
221
|
+
ownerMetadata?: IOwnedDirectoryOwnerMetadata<IUpgradeOwnershipMutationGuardOwner>;
|
|
222
|
+
}
|
|
223
|
+
|
|
210
224
|
const assertUid = (): number => {
|
|
211
225
|
if (typeof process.getuid !== 'function') {
|
|
212
226
|
throw new Error(`${currentCliName} upgrade requires a POSIX effective user identity.`);
|
|
@@ -986,6 +1000,16 @@ interface IOwnedDirectoryRemovalExpectation {
|
|
|
986
1000
|
assertRemovalAuthorized?: () => void;
|
|
987
1001
|
}
|
|
988
1002
|
|
|
1003
|
+
interface IOwnedDirectoryOwnerMetadata<TOwner> {
|
|
1004
|
+
owner: TOwner;
|
|
1005
|
+
shape: 'owner' | 'removing' | 'restoring';
|
|
1006
|
+
tombstoneName?: string;
|
|
1007
|
+
tombstoneIdentity?: { dev: number; ino: number };
|
|
1008
|
+
tombstoneCtimeMs?: number;
|
|
1009
|
+
removalPid?: number;
|
|
1010
|
+
removalFingerprintHash?: string;
|
|
1011
|
+
}
|
|
1012
|
+
|
|
989
1013
|
const sameOwnedDirectoryIdentity = (
|
|
990
1014
|
statsArg: plugins.fs.Stats,
|
|
991
1015
|
identityArg: IOwnedDirectoryRemovalExpectation['identity'],
|
|
@@ -998,6 +1022,114 @@ const exactPrimitiveRecordMatches = (leftArg: object, rightArg: object): boolean
|
|
|
998
1022
|
return exactKeys(right, keys) && keys.every((keyArg) => left[keyArg] === right[keyArg]);
|
|
999
1023
|
};
|
|
1000
1024
|
|
|
1025
|
+
const parseOwnerRemovalName = (nameArg: string): {
|
|
1026
|
+
pid: number;
|
|
1027
|
+
fingerprintHash?: string;
|
|
1028
|
+
} | undefined => {
|
|
1029
|
+
const current = ownerRemovalPattern.exec(nameArg);
|
|
1030
|
+
if (current) return { pid: Number(current[1]), fingerprintHash: current[2] };
|
|
1031
|
+
const legacy = legacyOwnerRemovalPattern.exec(nameArg);
|
|
1032
|
+
return legacy ? { pid: Number(legacy[1]) } : undefined;
|
|
1033
|
+
};
|
|
1034
|
+
|
|
1035
|
+
const inspectOwnedDirectoryOwnerMetadata = async <TOwner>(
|
|
1036
|
+
directoryArg: string,
|
|
1037
|
+
parseOwnerArg: (valueArg: unknown) => TOwner,
|
|
1038
|
+
): Promise<IOwnedDirectoryOwnerMetadata<TOwner> | undefined> => {
|
|
1039
|
+
const entries = await plugins.fs.promises.readdir(directoryArg, { withFileTypes: true });
|
|
1040
|
+
if (entries.length === 0) return undefined;
|
|
1041
|
+
if (entries.length > 2) {
|
|
1042
|
+
throw new Error(`Upgrade coordination owner directory contains unsafe entries: ${directoryArg}`);
|
|
1043
|
+
}
|
|
1044
|
+
const ownerEntry = entries.find((entryArg) => entryArg.name === ownerFileName);
|
|
1045
|
+
const tombstoneEntry = entries.find((entryArg) => parseOwnerRemovalName(entryArg.name));
|
|
1046
|
+
if (
|
|
1047
|
+
entries.some((entryArg) => entryArg !== ownerEntry && entryArg !== tombstoneEntry)
|
|
1048
|
+
|| !ownerEntry && !tombstoneEntry
|
|
1049
|
+
) throw new Error(`Upgrade coordination owner directory contains unsafe entries: ${directoryArg}`);
|
|
1050
|
+
for (const entry of entries) {
|
|
1051
|
+
if (!entry.isFile() || entry.isSymbolicLink()) {
|
|
1052
|
+
throw new Error(`Upgrade coordination owner metadata is unsafe: ${directoryArg}`);
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
const ownerPath = ownerEntry ? plugins.path.join(directoryArg, ownerFileName) : undefined;
|
|
1056
|
+
const tombstonePath = tombstoneEntry
|
|
1057
|
+
? plugins.path.join(directoryArg, tombstoneEntry.name)
|
|
1058
|
+
: undefined;
|
|
1059
|
+
const [ownerStats, tombstoneStats] = await Promise.all([
|
|
1060
|
+
ownerPath ? plugins.fs.promises.lstat(ownerPath) : undefined,
|
|
1061
|
+
tombstonePath ? plugins.fs.promises.lstat(tombstonePath) : undefined,
|
|
1062
|
+
]);
|
|
1063
|
+
for (const metadata of [ownerStats, tombstoneStats]) {
|
|
1064
|
+
if (!metadata) continue;
|
|
1065
|
+
if (
|
|
1066
|
+
!metadata.isFile()
|
|
1067
|
+
|| metadata.isSymbolicLink()
|
|
1068
|
+
|| (typeof process.getuid === 'function' && metadata.uid !== process.getuid())
|
|
1069
|
+
|| (metadata.mode & 0o077) !== 0
|
|
1070
|
+
) throw new Error(`Upgrade coordination owner metadata is unsafe: ${directoryArg}`);
|
|
1071
|
+
}
|
|
1072
|
+
let shape: IOwnedDirectoryOwnerMetadata<TOwner>['shape'];
|
|
1073
|
+
if (ownerStats && tombstoneStats) {
|
|
1074
|
+
if (
|
|
1075
|
+
ownerStats.dev !== tombstoneStats.dev
|
|
1076
|
+
|| ownerStats.ino !== tombstoneStats.ino
|
|
1077
|
+
|| ownerStats.nlink !== 2
|
|
1078
|
+
|| tombstoneStats.nlink !== 2
|
|
1079
|
+
) throw new Error(`Upgrade coordination owner restoration is invalid: ${directoryArg}`);
|
|
1080
|
+
shape = 'restoring';
|
|
1081
|
+
} else if (ownerStats) {
|
|
1082
|
+
if (ownerStats.nlink !== 1) {
|
|
1083
|
+
throw new Error(`Upgrade coordination owner link count is invalid: ${directoryArg}`);
|
|
1084
|
+
}
|
|
1085
|
+
shape = 'owner';
|
|
1086
|
+
} else {
|
|
1087
|
+
if (tombstoneStats!.nlink !== 1) {
|
|
1088
|
+
throw new Error(`Upgrade coordination owner tombstone link count is invalid: ${directoryArg}`);
|
|
1089
|
+
}
|
|
1090
|
+
shape = 'removing';
|
|
1091
|
+
}
|
|
1092
|
+
return {
|
|
1093
|
+
owner: parseOwnerArg(await readPrivateJson(ownerPath ?? tombstonePath!)),
|
|
1094
|
+
shape,
|
|
1095
|
+
...(tombstoneEntry
|
|
1096
|
+
? (() => {
|
|
1097
|
+
const removal = parseOwnerRemovalName(tombstoneEntry.name)!;
|
|
1098
|
+
return {
|
|
1099
|
+
tombstoneName: tombstoneEntry.name,
|
|
1100
|
+
tombstoneIdentity: { dev: tombstoneStats!.dev, ino: tombstoneStats!.ino },
|
|
1101
|
+
tombstoneCtimeMs: tombstoneStats!.ctimeMs,
|
|
1102
|
+
removalPid: removal.pid,
|
|
1103
|
+
...(removal.fingerprintHash
|
|
1104
|
+
? { removalFingerprintHash: removal.fingerprintHash }
|
|
1105
|
+
: {}),
|
|
1106
|
+
};
|
|
1107
|
+
})()
|
|
1108
|
+
: {}),
|
|
1109
|
+
};
|
|
1110
|
+
};
|
|
1111
|
+
|
|
1112
|
+
const restoreOwnedDirectoryOwnerMetadata = async <TOwner>(
|
|
1113
|
+
directoryArg: string,
|
|
1114
|
+
metadataArg: IOwnedDirectoryOwnerMetadata<TOwner>,
|
|
1115
|
+
): Promise<void> => {
|
|
1116
|
+
if (metadataArg.shape === 'owner') return;
|
|
1117
|
+
const tombstonePath = plugins.path.join(directoryArg, metadataArg.tombstoneName!);
|
|
1118
|
+
const tombstoneStats = await plugins.fs.promises.lstat(tombstonePath);
|
|
1119
|
+
if (!sameOwnedDirectoryIdentity(tombstoneStats, metadataArg.tombstoneIdentity!)) {
|
|
1120
|
+
throw new Error('Upgrade coordination owner tombstone changed during recovery.');
|
|
1121
|
+
}
|
|
1122
|
+
if (metadataArg.shape === 'restoring') {
|
|
1123
|
+
await plugins.fs.promises.unlink(tombstonePath);
|
|
1124
|
+
} else {
|
|
1125
|
+
await plugins.fs.promises.rename(
|
|
1126
|
+
tombstonePath,
|
|
1127
|
+
plugins.path.join(directoryArg, ownerFileName),
|
|
1128
|
+
);
|
|
1129
|
+
}
|
|
1130
|
+
await syncDirectory(directoryArg);
|
|
1131
|
+
};
|
|
1132
|
+
|
|
1001
1133
|
const removeExactOwnedDirectory = async (
|
|
1002
1134
|
directoryArg: string,
|
|
1003
1135
|
expectationArg: IOwnedDirectoryRemovalExpectation,
|
|
@@ -1020,9 +1152,17 @@ const removeExactOwnedDirectory = async (
|
|
|
1020
1152
|
}
|
|
1021
1153
|
if (entries.length === 1) {
|
|
1022
1154
|
const ownerPath = plugins.path.join(directoryArg, ownerFileName);
|
|
1155
|
+
const removerIdentity = await readControllerProcessIdentity(process.pid);
|
|
1156
|
+
if (!removerIdentity) {
|
|
1157
|
+
throw new Error('Unable to establish upgrade ownership-removal identity.');
|
|
1158
|
+
}
|
|
1159
|
+
const removerFingerprintHash = plugins.crypto
|
|
1160
|
+
.createHash('sha256')
|
|
1161
|
+
.update(removerIdentity.fingerprint, 'utf8')
|
|
1162
|
+
.digest('hex');
|
|
1023
1163
|
const tombstonePath = plugins.path.join(
|
|
1024
1164
|
directoryArg,
|
|
1025
|
-
`.owner-removing-${process.pid}-${plugins.crypto.randomBytes(8).toString('hex')}`,
|
|
1165
|
+
`.owner-removing-${process.pid}-${removerFingerprintHash}-${plugins.crypto.randomBytes(8).toString('hex')}`,
|
|
1026
1166
|
);
|
|
1027
1167
|
expectationArg.assertRemovalAuthorized?.();
|
|
1028
1168
|
await plugins.fs.promises.rename(ownerPath, tombstonePath);
|
|
@@ -1065,6 +1205,51 @@ const removeExactOwnedDirectory = async (
|
|
|
1065
1205
|
await syncDirectory(plugins.path.dirname(directoryArg));
|
|
1066
1206
|
};
|
|
1067
1207
|
|
|
1208
|
+
const removeExactFailedOwnerPublication = async (
|
|
1209
|
+
directoryArg: string,
|
|
1210
|
+
identityArg: { dev: number; ino: number },
|
|
1211
|
+
): Promise<void> => {
|
|
1212
|
+
const initialStats = await lstatIfPresent(directoryArg);
|
|
1213
|
+
if (!initialStats) return;
|
|
1214
|
+
if (!sameOwnedDirectoryIdentity(initialStats, identityArg)) {
|
|
1215
|
+
throw new Error('Upgrade coordination publication changed before cleanup.');
|
|
1216
|
+
}
|
|
1217
|
+
const entries = await plugins.fs.promises.readdir(directoryArg, { withFileTypes: true });
|
|
1218
|
+
if (
|
|
1219
|
+
entries.length > 1
|
|
1220
|
+
|| entries.some((entryArg) => (
|
|
1221
|
+
entryArg.name !== ownerFileName
|
|
1222
|
+
|| !entryArg.isFile()
|
|
1223
|
+
|| entryArg.isSymbolicLink()
|
|
1224
|
+
))
|
|
1225
|
+
) throw new Error(`Upgrade coordination publication contains unsafe entries: ${directoryArg}`);
|
|
1226
|
+
if (entries.length === 1) {
|
|
1227
|
+
const ownerPath = plugins.path.join(directoryArg, ownerFileName);
|
|
1228
|
+
const ownerStats = await plugins.fs.promises.lstat(ownerPath);
|
|
1229
|
+
if (
|
|
1230
|
+
!ownerStats.isFile()
|
|
1231
|
+
|| ownerStats.isSymbolicLink()
|
|
1232
|
+
|| (typeof process.getuid === 'function' && ownerStats.uid !== process.getuid())
|
|
1233
|
+
|| (ownerStats.mode & 0o077) !== 0
|
|
1234
|
+
|| ownerStats.nlink !== 1
|
|
1235
|
+
) throw new Error(`Upgrade coordination publication metadata is unsafe: ${ownerPath}`);
|
|
1236
|
+
await plugins.fs.promises.unlink(ownerPath);
|
|
1237
|
+
await syncDirectory(directoryArg);
|
|
1238
|
+
}
|
|
1239
|
+
const currentStats = await lstatIfPresent(directoryArg);
|
|
1240
|
+
if (!currentStats) return;
|
|
1241
|
+
if (!sameOwnedDirectoryIdentity(currentStats, identityArg)) {
|
|
1242
|
+
throw new Error('Upgrade coordination publication changed during cleanup.');
|
|
1243
|
+
}
|
|
1244
|
+
try {
|
|
1245
|
+
await plugins.fs.promises.rmdir(directoryArg);
|
|
1246
|
+
} catch (errorArg) {
|
|
1247
|
+
if ((errorArg as NodeJS.ErrnoException).code === 'ENOENT') return;
|
|
1248
|
+
throw errorArg;
|
|
1249
|
+
}
|
|
1250
|
+
await syncDirectory(plugins.path.dirname(directoryArg));
|
|
1251
|
+
};
|
|
1252
|
+
|
|
1068
1253
|
class UpgradeOwnershipMutationGuard {
|
|
1069
1254
|
private released = false;
|
|
1070
1255
|
private releaseTask?: Promise<void>;
|
|
@@ -1072,6 +1257,7 @@ class UpgradeOwnershipMutationGuard {
|
|
|
1072
1257
|
constructor(
|
|
1073
1258
|
public readonly directory: string,
|
|
1074
1259
|
public readonly owner: IUpgradeOwnershipMutationGuardOwner,
|
|
1260
|
+
private readonly identity: { dev: number; ino: number },
|
|
1075
1261
|
) {}
|
|
1076
1262
|
|
|
1077
1263
|
public release(): Promise<void> {
|
|
@@ -1079,21 +1265,34 @@ class UpgradeOwnershipMutationGuard {
|
|
|
1079
1265
|
if (!this.releaseTask) {
|
|
1080
1266
|
let task: Promise<void>;
|
|
1081
1267
|
task = (async () => {
|
|
1082
|
-
const stats = await
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1268
|
+
const stats = await lstatIfPresent(this.directory);
|
|
1269
|
+
if (!stats) {
|
|
1270
|
+
this.released = true;
|
|
1271
|
+
return;
|
|
1272
|
+
}
|
|
1273
|
+
if (!sameOwnedDirectoryIdentity(stats, this.identity)) {
|
|
1274
|
+
throw new Error('Upgrade ownership mutation guard changed unexpectedly.');
|
|
1275
|
+
}
|
|
1276
|
+
const metadata = await inspectOwnedDirectoryOwnerMetadata(
|
|
1277
|
+
this.directory,
|
|
1278
|
+
parseOwnershipMutationGuardOwner,
|
|
1279
|
+
);
|
|
1280
|
+
if (metadata && !exactPrimitiveRecordMatches(metadata.owner, this.owner)) {
|
|
1087
1281
|
throw new Error('Upgrade ownership mutation guard changed unexpectedly.');
|
|
1088
1282
|
}
|
|
1283
|
+
if (metadata) await restoreOwnedDirectoryOwnerMetadata(this.directory, metadata);
|
|
1089
1284
|
await removeExactOwnedDirectory(this.directory, {
|
|
1090
|
-
identity:
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1285
|
+
identity: this.identity,
|
|
1286
|
+
...(metadata
|
|
1287
|
+
? {
|
|
1288
|
+
assertOwner: (valueArg: unknown) => {
|
|
1289
|
+
const removedOwner = parseOwnershipMutationGuardOwner(valueArg);
|
|
1290
|
+
if (!exactPrimitiveRecordMatches(removedOwner, this.owner)) {
|
|
1291
|
+
throw new Error('Upgrade ownership mutation guard changed during release.');
|
|
1292
|
+
}
|
|
1293
|
+
},
|
|
1294
|
+
}
|
|
1295
|
+
: {}),
|
|
1097
1296
|
});
|
|
1098
1297
|
this.released = true;
|
|
1099
1298
|
})().finally(() => {
|
|
@@ -1165,17 +1364,48 @@ export class UpgradeCoordinator {
|
|
|
1165
1364
|
public readonly startLeasesDirectory: string;
|
|
1166
1365
|
public readonly transactionsDirectory: string;
|
|
1167
1366
|
public readonly ownershipGuardsDirectory: string;
|
|
1367
|
+
private readonly usesLegacyLocation: boolean;
|
|
1368
|
+
private readonly usesInheritedLocation: boolean;
|
|
1168
1369
|
private initializationTask?: Promise<void>;
|
|
1169
1370
|
|
|
1170
|
-
constructor(
|
|
1371
|
+
constructor(
|
|
1372
|
+
public readonly canonicalGlobalRoot: string,
|
|
1373
|
+
) {
|
|
1171
1374
|
if (!plugins.path.isAbsolute(canonicalGlobalRoot)) {
|
|
1172
1375
|
throw new Error('The canonical pnpm global root must be absolute.');
|
|
1173
1376
|
}
|
|
1174
1377
|
this.uid = assertUid();
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
`
|
|
1378
|
+
const canonicalBaseDirectory = plugins.path.join(
|
|
1379
|
+
resolveAGLHomePaths().upgrade,
|
|
1380
|
+
`coordination-${this.uid}-${coordinationPathHash(canonicalGlobalRoot)}`,
|
|
1178
1381
|
);
|
|
1382
|
+
const inheritedBaseDirectory = process.env[upgradeCoordinationRootEnvironmentVariable];
|
|
1383
|
+
if (inheritedBaseDirectory !== undefined) {
|
|
1384
|
+
if (
|
|
1385
|
+
!plugins.path.isAbsolute(inheritedBaseDirectory)
|
|
1386
|
+
|| plugins.path.normalize(inheritedBaseDirectory) !== inheritedBaseDirectory
|
|
1387
|
+
|| plugins.path.parse(inheritedBaseDirectory).root === inheritedBaseDirectory
|
|
1388
|
+
|| inheritedBaseDirectory.includes('\0')
|
|
1389
|
+
|| Buffer.byteLength(inheritedBaseDirectory, 'utf8') > 4096
|
|
1390
|
+
) throw new Error('The inherited AGL upgrade coordination root is invalid.');
|
|
1391
|
+
}
|
|
1392
|
+
const inheritedUpgradeToken = process.env[upgradeTokenEnvironmentVariable];
|
|
1393
|
+
const usesLegacyLocation = inheritedUpgradeToken !== undefined
|
|
1394
|
+
&& process.env[upgradeCoordinationRootEnvironmentVariable] === undefined;
|
|
1395
|
+
if (
|
|
1396
|
+
usesLegacyLocation
|
|
1397
|
+
&& (inheritedUpgradeToken === undefined || !upgradeTokenPattern.test(inheritedUpgradeToken))
|
|
1398
|
+
) {
|
|
1399
|
+
throw new Error('Legacy upgrade coordination requires the exact inherited upgrade token.');
|
|
1400
|
+
}
|
|
1401
|
+
this.usesLegacyLocation = usesLegacyLocation;
|
|
1402
|
+
this.usesInheritedLocation = inheritedBaseDirectory !== undefined;
|
|
1403
|
+
this.baseDirectory = this.usesLegacyLocation
|
|
1404
|
+
? plugins.path.join(
|
|
1405
|
+
'/tmp',
|
|
1406
|
+
`harness-controller-upgrade-${this.uid}-${coordinationPathHash(canonicalGlobalRoot)}`,
|
|
1407
|
+
)
|
|
1408
|
+
: inheritedBaseDirectory ?? canonicalBaseDirectory;
|
|
1179
1409
|
this.upgradeLockDirectory = plugins.path.join(this.baseDirectory, 'upgrade.lock');
|
|
1180
1410
|
this.startLeasesDirectory = plugins.path.join(this.baseDirectory, 'start-leases');
|
|
1181
1411
|
this.transactionsDirectory = plugins.path.join(this.baseDirectory, 'transactions');
|
|
@@ -1195,19 +1425,17 @@ export class UpgradeCoordinator {
|
|
|
1195
1425
|
}
|
|
1196
1426
|
|
|
1197
1427
|
private async performInitialization(): Promise<void> {
|
|
1428
|
+
if (!this.usesLegacyLocation && !this.usesInheritedLocation) {
|
|
1429
|
+
const home = resolveAGLHomePaths();
|
|
1430
|
+
await ensurePrivateDirectory(home.root);
|
|
1431
|
+
await ensurePrivateDirectory(home.upgrade);
|
|
1432
|
+
}
|
|
1198
1433
|
await ensurePrivateDirectory(this.baseDirectory);
|
|
1199
1434
|
await ensurePrivateDirectory(this.startLeasesDirectory);
|
|
1200
1435
|
await ensurePrivateDirectory(this.transactionsDirectory);
|
|
1201
1436
|
await ensurePrivateDirectory(this.ownershipGuardsDirectory);
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
})) {
|
|
1205
|
-
const guardPath = plugins.path.join(this.ownershipGuardsDirectory, entry.name);
|
|
1206
|
-
if (!entry.isDirectory() || !ownershipGuardDirectoryPattern.test(entry.name)) {
|
|
1207
|
-
throw new Error(`Upgrade ownership guard directory contains an unexpected entry: ${guardPath}`);
|
|
1208
|
-
}
|
|
1209
|
-
await assertPrivateDirectory(guardPath);
|
|
1210
|
-
}
|
|
1437
|
+
await this.cleanupOwnershipGuardTemporaryDirectories();
|
|
1438
|
+
await this.cleanupOwnershipGuardDirectories();
|
|
1211
1439
|
const entries = await plugins.fs.promises.readdir(this.baseDirectory, { withFileTypes: true });
|
|
1212
1440
|
for (const entry of entries) {
|
|
1213
1441
|
if (!entry.isFile() || !tokenMetadataPattern.test(entry.name)) continue;
|
|
@@ -1293,6 +1521,205 @@ export class UpgradeCoordinator {
|
|
|
1293
1521
|
);
|
|
1294
1522
|
}
|
|
1295
1523
|
|
|
1524
|
+
private async inspectOwnershipGuardDirectory(
|
|
1525
|
+
directoryArg: string,
|
|
1526
|
+
expectedTargetHashArg: string,
|
|
1527
|
+
attemptArg = 0,
|
|
1528
|
+
): Promise<IUpgradeOwnershipMutationGuardState> {
|
|
1529
|
+
const stats = await lstatIfPresent(directoryArg);
|
|
1530
|
+
if (!stats) return { state: 'absent' };
|
|
1531
|
+
if (
|
|
1532
|
+
!stats.isDirectory()
|
|
1533
|
+
|| stats.isSymbolicLink()
|
|
1534
|
+
|| stats.uid !== this.uid
|
|
1535
|
+
|| (stats.mode & 0o077) !== 0
|
|
1536
|
+
) throw new Error(`Upgrade ownership mutation guard is unsafe: ${directoryArg}`);
|
|
1537
|
+
let ownerMetadata: IOwnedDirectoryOwnerMetadata<IUpgradeOwnershipMutationGuardOwner> | undefined;
|
|
1538
|
+
try {
|
|
1539
|
+
ownerMetadata = await inspectOwnedDirectoryOwnerMetadata(
|
|
1540
|
+
directoryArg,
|
|
1541
|
+
parseOwnershipMutationGuardOwner,
|
|
1542
|
+
);
|
|
1543
|
+
} catch (errorArg) {
|
|
1544
|
+
const currentStats = await lstatIfPresent(directoryArg);
|
|
1545
|
+
if (!currentStats) return { state: 'absent' };
|
|
1546
|
+
if (!sameOwnedDirectoryIdentity(currentStats, { dev: stats.dev, ino: stats.ino })) {
|
|
1547
|
+
if (attemptArg < 2) {
|
|
1548
|
+
return await this.inspectOwnershipGuardDirectory(
|
|
1549
|
+
directoryArg,
|
|
1550
|
+
expectedTargetHashArg,
|
|
1551
|
+
attemptArg + 1,
|
|
1552
|
+
);
|
|
1553
|
+
}
|
|
1554
|
+
throw new Error(`Upgrade ownership mutation guard changed repeatedly: ${directoryArg}`, {
|
|
1555
|
+
cause: errorArg,
|
|
1556
|
+
});
|
|
1557
|
+
}
|
|
1558
|
+
if ((errorArg as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
1559
|
+
return {
|
|
1560
|
+
state: Date.now() - currentStats.mtimeMs < initializationGraceMs ? 'live' : 'empty',
|
|
1561
|
+
identity: { dev: currentStats.dev, ino: currentStats.ino },
|
|
1562
|
+
};
|
|
1563
|
+
}
|
|
1564
|
+
throw errorArg;
|
|
1565
|
+
}
|
|
1566
|
+
if (!ownerMetadata) {
|
|
1567
|
+
return {
|
|
1568
|
+
state: Date.now() - stats.mtimeMs < initializationGraceMs ? 'live' : 'empty',
|
|
1569
|
+
identity: { dev: stats.dev, ino: stats.ino },
|
|
1570
|
+
};
|
|
1571
|
+
}
|
|
1572
|
+
const owner = ownerMetadata.owner;
|
|
1573
|
+
if (owner.uid !== this.uid || owner.targetHash !== expectedTargetHashArg) {
|
|
1574
|
+
throw new Error(`Upgrade ownership mutation guard binding is invalid: ${directoryArg}`);
|
|
1575
|
+
}
|
|
1576
|
+
let processIdentity: Awaited<ReturnType<typeof readControllerProcessIdentity>>;
|
|
1577
|
+
let removalProcessIdentity: Awaited<ReturnType<typeof readControllerProcessIdentity>>;
|
|
1578
|
+
try {
|
|
1579
|
+
processIdentity = await readControllerProcessIdentity(owner.pid);
|
|
1580
|
+
removalProcessIdentity = ownerMetadata.removalPid === undefined
|
|
1581
|
+
? null
|
|
1582
|
+
: ownerMetadata.removalPid === owner.pid
|
|
1583
|
+
? processIdentity
|
|
1584
|
+
: await readControllerProcessIdentity(ownerMetadata.removalPid);
|
|
1585
|
+
} catch (errorArg) {
|
|
1586
|
+
throw new Error(`The upgrade ownership mutation guard cannot be verified safely: ${directoryArg}`, {
|
|
1587
|
+
cause: errorArg,
|
|
1588
|
+
});
|
|
1589
|
+
}
|
|
1590
|
+
const ownerIsStillLive = processIdentity?.fingerprint === owner.fingerprint;
|
|
1591
|
+
const removerIsStillLive = ownerMetadata.removalFingerprintHash
|
|
1592
|
+
? removalProcessIdentity !== null
|
|
1593
|
+
&& plugins.crypto.createHash('sha256')
|
|
1594
|
+
.update(removalProcessIdentity.fingerprint, 'utf8')
|
|
1595
|
+
.digest('hex') === ownerMetadata.removalFingerprintHash
|
|
1596
|
+
: ownerMetadata.removalPid !== undefined
|
|
1597
|
+
&& removalProcessIdentity !== null
|
|
1598
|
+
&& Date.now() - ownerMetadata.tombstoneCtimeMs! < legacyOwnerRemovalGraceMs;
|
|
1599
|
+
return {
|
|
1600
|
+
state: ownerIsStillLive || removerIsStillLive ? 'live' : 'stale',
|
|
1601
|
+
identity: { dev: stats.dev, ino: stats.ino },
|
|
1602
|
+
owner,
|
|
1603
|
+
ownerMetadata,
|
|
1604
|
+
};
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
private async reclaimOwnershipGuardDirectory(
|
|
1608
|
+
directoryArg: string,
|
|
1609
|
+
targetHashArg: string,
|
|
1610
|
+
): Promise<'retry' | 'live'> {
|
|
1611
|
+
const inspected = await this.inspectOwnershipGuardDirectory(directoryArg, targetHashArg);
|
|
1612
|
+
if (inspected.state === 'absent') return 'retry';
|
|
1613
|
+
if (inspected.state === 'live') return 'live';
|
|
1614
|
+
const identity = inspected.identity!;
|
|
1615
|
+
if (inspected.state === 'empty') {
|
|
1616
|
+
const current = await lstatIfPresent(directoryArg);
|
|
1617
|
+
if (!current) return 'retry';
|
|
1618
|
+
if (!sameOwnedDirectoryIdentity(current, identity)) {
|
|
1619
|
+
throw new Error('Upgrade ownership mutation guard changed before empty recovery.');
|
|
1620
|
+
}
|
|
1621
|
+
if ((await plugins.fs.promises.readdir(directoryArg)).length !== 0) {
|
|
1622
|
+
throw new Error('Upgrade ownership mutation guard changed during empty recovery.');
|
|
1623
|
+
}
|
|
1624
|
+
await plugins.fs.promises.rmdir(directoryArg).catch((errorArg) => {
|
|
1625
|
+
if ((errorArg as NodeJS.ErrnoException).code !== 'ENOENT') throw errorArg;
|
|
1626
|
+
});
|
|
1627
|
+
await syncDirectory(this.ownershipGuardsDirectory);
|
|
1628
|
+
return 'retry';
|
|
1629
|
+
}
|
|
1630
|
+
await restoreOwnedDirectoryOwnerMetadata(directoryArg, inspected.ownerMetadata!);
|
|
1631
|
+
await removeExactOwnedDirectory(directoryArg, {
|
|
1632
|
+
identity,
|
|
1633
|
+
assertOwner: (valueArg) => {
|
|
1634
|
+
const removedOwner = parseOwnershipMutationGuardOwner(valueArg);
|
|
1635
|
+
if (!exactPrimitiveRecordMatches(removedOwner, inspected.owner!)) {
|
|
1636
|
+
throw new Error('Upgrade ownership mutation guard changed during stale recovery.');
|
|
1637
|
+
}
|
|
1638
|
+
},
|
|
1639
|
+
});
|
|
1640
|
+
return 'retry';
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
private async cleanupOwnershipGuardDirectories(): Promise<void> {
|
|
1644
|
+
const entries = await plugins.fs.promises.readdir(this.ownershipGuardsDirectory, {
|
|
1645
|
+
withFileTypes: true,
|
|
1646
|
+
});
|
|
1647
|
+
for (const entry of entries) {
|
|
1648
|
+
const path = plugins.path.join(this.ownershipGuardsDirectory, entry.name);
|
|
1649
|
+
if (ownershipGuardTemporaryPattern.test(entry.name)) continue;
|
|
1650
|
+
if (!entry.isDirectory() || !ownershipGuardDirectoryPattern.test(entry.name)) {
|
|
1651
|
+
throw new Error(`Upgrade ownership guard directory contains an unexpected entry: ${path}`);
|
|
1652
|
+
}
|
|
1653
|
+
await this.reclaimOwnershipGuardDirectory(path, entry.name.slice(0, 64));
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
private async cleanupOwnershipGuardTemporaryDirectories(): Promise<void> {
|
|
1658
|
+
const entries = await plugins.fs.promises.readdir(this.ownershipGuardsDirectory, {
|
|
1659
|
+
withFileTypes: true,
|
|
1660
|
+
});
|
|
1661
|
+
for (const entry of entries) {
|
|
1662
|
+
if (!ownershipGuardTemporaryPattern.test(entry.name)) continue;
|
|
1663
|
+
const match = /^([a-f0-9]{64})\.guard\.tmp-([1-9][0-9]*)-([a-f0-9]{32})$/.exec(entry.name);
|
|
1664
|
+
if (!match) throw new Error(`Upgrade ownership guard temporary name is invalid: ${entry.name}`);
|
|
1665
|
+
const pid = Number(match[2]);
|
|
1666
|
+
const path = plugins.path.join(this.ownershipGuardsDirectory, entry.name);
|
|
1667
|
+
const stats = await lstatIfPresent(path);
|
|
1668
|
+
if (
|
|
1669
|
+
!stats
|
|
1670
|
+
|| !stats.isDirectory()
|
|
1671
|
+
|| stats.isSymbolicLink()
|
|
1672
|
+
|| stats.uid !== this.uid
|
|
1673
|
+
|| (stats.mode & 0o077) !== 0
|
|
1674
|
+
) throw new Error(`Upgrade ownership guard temporary is unsafe: ${path}`);
|
|
1675
|
+
const ownerPath = plugins.path.join(path, ownerFileName);
|
|
1676
|
+
const ownerStats = await lstatIfPresent(ownerPath);
|
|
1677
|
+
let owner: IUpgradeOwnershipMutationGuardOwner | undefined;
|
|
1678
|
+
if (ownerStats) {
|
|
1679
|
+
if (
|
|
1680
|
+
!ownerStats.isFile()
|
|
1681
|
+
|| ownerStats.isSymbolicLink()
|
|
1682
|
+
|| ownerStats.uid !== this.uid
|
|
1683
|
+
|| (ownerStats.mode & 0o077) !== 0
|
|
1684
|
+
|| ownerStats.nlink !== 1
|
|
1685
|
+
) throw new Error(`Upgrade ownership guard temporary owner is unsafe: ${ownerPath}`);
|
|
1686
|
+
try {
|
|
1687
|
+
owner = parseOwnershipMutationGuardOwner(await readPrivateJson(ownerPath));
|
|
1688
|
+
} catch (errorArg) {
|
|
1689
|
+
if (await readControllerProcessIdentity(pid)) {
|
|
1690
|
+
throw new Error(`Upgrade ownership guard temporary is owned by live PID ${pid}.`, {
|
|
1691
|
+
cause: errorArg,
|
|
1692
|
+
});
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
const live = await readControllerProcessIdentity(pid);
|
|
1697
|
+
if (live && (!owner || owner.pid !== pid || owner.fingerprint !== live.fingerprint)) {
|
|
1698
|
+
throw new Error(`Upgrade ownership guard temporary is owned by live PID ${pid}.`);
|
|
1699
|
+
}
|
|
1700
|
+
if (live) continue;
|
|
1701
|
+
if (owner && (owner.pid !== pid || owner.uid !== this.uid || owner.targetHash !== match[1])) {
|
|
1702
|
+
throw new Error(`Upgrade ownership guard temporary binding is invalid: ${path}`);
|
|
1703
|
+
}
|
|
1704
|
+
const stable = await lstatIfPresent(path);
|
|
1705
|
+
if (!stable || stable.dev !== stats.dev || stable.ino !== stats.ino) {
|
|
1706
|
+
throw new Error(`Upgrade ownership guard temporary changed before cleanup: ${path}`);
|
|
1707
|
+
}
|
|
1708
|
+
if (ownerStats) {
|
|
1709
|
+
const stableOwner = await lstatIfPresent(ownerPath);
|
|
1710
|
+
if (!stableOwner || stableOwner.dev !== ownerStats.dev || stableOwner.ino !== ownerStats.ino) {
|
|
1711
|
+
throw new Error(`Upgrade ownership guard temporary owner changed before cleanup: ${ownerPath}`);
|
|
1712
|
+
}
|
|
1713
|
+
await plugins.fs.promises.unlink(ownerPath);
|
|
1714
|
+
}
|
|
1715
|
+
if ((await plugins.fs.promises.readdir(path)).length !== 0) {
|
|
1716
|
+
throw new Error(`Upgrade ownership guard temporary contains unexpected entries: ${path}`);
|
|
1717
|
+
}
|
|
1718
|
+
await plugins.fs.promises.rmdir(path);
|
|
1719
|
+
await syncDirectory(this.ownershipGuardsDirectory);
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1296
1723
|
private async acquireOwnershipMutationGuard(
|
|
1297
1724
|
directoryArg: string,
|
|
1298
1725
|
): Promise<UpgradeOwnershipMutationGuard> {
|
|
@@ -1310,70 +1737,63 @@ export class UpgradeCoordinator {
|
|
|
1310
1737
|
createdAt: Date.now(),
|
|
1311
1738
|
};
|
|
1312
1739
|
const deadline = Date.now() + ownershipGuardTimeoutMs;
|
|
1740
|
+
const temporaryDirectory = `${guardDirectory}.tmp-${owner.pid}-${owner.nonce}`;
|
|
1313
1741
|
while (true) {
|
|
1742
|
+
let temporaryCreated = false;
|
|
1743
|
+
let publishedIdentity: { dev: number; ino: number } | undefined;
|
|
1314
1744
|
try {
|
|
1315
|
-
await plugins.fs.promises.mkdir(
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1745
|
+
await plugins.fs.promises.mkdir(temporaryDirectory, { mode: 0o700 });
|
|
1746
|
+
temporaryCreated = true;
|
|
1747
|
+
await writePrivateJson(plugins.path.join(temporaryDirectory, ownerFileName), owner);
|
|
1748
|
+
await syncDirectory(temporaryDirectory);
|
|
1749
|
+
const temporaryStats = await plugins.fs.promises.lstat(temporaryDirectory);
|
|
1750
|
+
const temporaryIdentity = { dev: temporaryStats.dev, ino: temporaryStats.ino };
|
|
1751
|
+
await plugins.fs.promises.rename(temporaryDirectory, guardDirectory);
|
|
1752
|
+
temporaryCreated = false;
|
|
1753
|
+
publishedIdentity = temporaryIdentity;
|
|
1754
|
+
const publishedStats = await plugins.fs.promises.lstat(guardDirectory);
|
|
1755
|
+
if (!sameOwnedDirectoryIdentity(publishedStats, publishedIdentity)) {
|
|
1756
|
+
throw new Error('Published upgrade ownership guard identity changed unexpectedly.');
|
|
1324
1757
|
}
|
|
1325
|
-
|
|
1758
|
+
await syncDirectory(this.ownershipGuardsDirectory);
|
|
1759
|
+
return new UpgradeOwnershipMutationGuard(guardDirectory, owner, publishedIdentity);
|
|
1326
1760
|
} catch (errorArg) {
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|| stats.isSymbolicLink()
|
|
1335
|
-
|| stats.uid !== this.uid
|
|
1336
|
-
|| (stats.mode & 0o077) !== 0
|
|
1337
|
-
) throw new Error(`Upgrade ownership mutation guard is unsafe: ${guardDirectory}`);
|
|
1338
|
-
|
|
1339
|
-
let existingOwner: IUpgradeOwnershipMutationGuardOwner;
|
|
1340
|
-
try {
|
|
1341
|
-
existingOwner = parseOwnershipMutationGuardOwner(await readPrivateJson(
|
|
1342
|
-
plugins.path.join(guardDirectory, ownerFileName),
|
|
1343
|
-
));
|
|
1344
|
-
} catch (errorArg) {
|
|
1345
|
-
const refreshedStats = await lstatIfPresent(guardDirectory);
|
|
1346
|
-
if (!refreshedStats || refreshedStats.dev !== stats.dev || refreshedStats.ino !== stats.ino) {
|
|
1347
|
-
continue;
|
|
1761
|
+
const cleanupErrors: unknown[] = [];
|
|
1762
|
+
if (temporaryCreated) {
|
|
1763
|
+
try {
|
|
1764
|
+
await plugins.fs.promises.rm(temporaryDirectory, { recursive: true, force: true });
|
|
1765
|
+
} catch (cleanupErrorArg) {
|
|
1766
|
+
cleanupErrors.push(cleanupErrorArg);
|
|
1767
|
+
}
|
|
1348
1768
|
}
|
|
1349
|
-
if (
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1769
|
+
if (publishedIdentity) {
|
|
1770
|
+
try {
|
|
1771
|
+
await removeExactOwnedDirectory(guardDirectory, {
|
|
1772
|
+
identity: publishedIdentity,
|
|
1773
|
+
assertOwner: (valueArg) => {
|
|
1774
|
+
const publishedOwner = parseOwnershipMutationGuardOwner(valueArg);
|
|
1775
|
+
if (!exactPrimitiveRecordMatches(publishedOwner, owner)) {
|
|
1776
|
+
throw new Error('Published upgrade ownership guard changed during cleanup.');
|
|
1777
|
+
}
|
|
1778
|
+
},
|
|
1779
|
+
});
|
|
1780
|
+
} catch (cleanupErrorArg) {
|
|
1781
|
+
cleanupErrors.push(cleanupErrorArg);
|
|
1782
|
+
}
|
|
1355
1783
|
}
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
try {
|
|
1366
|
-
existingIdentity = await readControllerProcessIdentity(existingOwner.pid);
|
|
1367
|
-
} catch (errorArg) {
|
|
1368
|
-
throw new Error(
|
|
1369
|
-
`The existing upgrade ownership mutation guard cannot be verified safely: ${guardDirectory}`,
|
|
1370
|
-
{ cause: errorArg },
|
|
1371
|
-
);
|
|
1784
|
+
if (cleanupErrors.length > 0) {
|
|
1785
|
+
throw new AggregateError(
|
|
1786
|
+
[errorArg, ...cleanupErrors],
|
|
1787
|
+
'Upgrade ownership guard publication failed and cleanup was incomplete.',
|
|
1788
|
+
{ cause: errorArg },
|
|
1789
|
+
);
|
|
1790
|
+
}
|
|
1791
|
+
const code = (errorArg as NodeJS.ErrnoException).code;
|
|
1792
|
+
if (code !== 'EEXIST' && code !== 'ENOTEMPTY') throw errorArg;
|
|
1372
1793
|
}
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
);
|
|
1794
|
+
|
|
1795
|
+
if (await this.reclaimOwnershipGuardDirectory(guardDirectory, targetHash) === 'retry') {
|
|
1796
|
+
continue;
|
|
1377
1797
|
}
|
|
1378
1798
|
if (Date.now() >= deadline) {
|
|
1379
1799
|
throw new Error('Timed out waiting for an upgrade ownership mutation guard.');
|
|
@@ -1419,6 +1839,9 @@ export class UpgradeCoordinator {
|
|
|
1419
1839
|
throw new Error(`The existing ${currentCliName} ownership cannot be verified safely.`);
|
|
1420
1840
|
}
|
|
1421
1841
|
if (current.state !== 'stale') return false;
|
|
1842
|
+
if (current.ownerMetadata) {
|
|
1843
|
+
await restoreOwnedDirectoryOwnerMetadata(directoryArg, current.ownerMetadata);
|
|
1844
|
+
}
|
|
1422
1845
|
await removeExactOwnedDirectory(directoryArg, {
|
|
1423
1846
|
identity: current.identity!,
|
|
1424
1847
|
...(current.owner
|
|
@@ -1439,41 +1862,35 @@ export class UpgradeCoordinator {
|
|
|
1439
1862
|
private async releaseOwnedDirectory(
|
|
1440
1863
|
directoryArg: string,
|
|
1441
1864
|
expectedOwnerArg: IUpgradeProcessOwner,
|
|
1865
|
+
expectedIdentityArg: { dev: number; ino: number },
|
|
1442
1866
|
shouldReleaseArg?: () => boolean,
|
|
1443
1867
|
): Promise<void> {
|
|
1444
1868
|
await this.withOwnershipMutationGuard(directoryArg, async () => {
|
|
1445
|
-
const stats = await
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
plugins.path.join(directoryArg, ownerFileName),
|
|
1450
|
-
));
|
|
1451
|
-
} catch (errorArg) {
|
|
1452
|
-
if ((errorArg as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
1453
|
-
throw new Error('Upgrade coordination ownership changed unexpectedly.', { cause: errorArg });
|
|
1454
|
-
}
|
|
1455
|
-
throw errorArg;
|
|
1869
|
+
const stats = await lstatIfPresent(directoryArg);
|
|
1870
|
+
if (!stats) return;
|
|
1871
|
+
if (!sameOwnedDirectoryIdentity(stats, expectedIdentityArg)) {
|
|
1872
|
+
throw new Error('Upgrade coordination ownership changed unexpectedly.');
|
|
1456
1873
|
}
|
|
1457
|
-
|
|
1874
|
+
const metadata = await inspectOwnedDirectoryOwnerMetadata(directoryArg, parseOwner);
|
|
1875
|
+
if (metadata && !exactPrimitiveRecordMatches(metadata.owner, expectedOwnerArg)) {
|
|
1458
1876
|
throw new Error('Upgrade coordination ownership changed unexpectedly.');
|
|
1459
1877
|
}
|
|
1878
|
+
if (shouldReleaseArg && !shouldReleaseArg()) {
|
|
1879
|
+
throw new Error('Upgrade coordination release is no longer authorized.');
|
|
1880
|
+
}
|
|
1881
|
+
if (metadata) await restoreOwnedDirectoryOwnerMetadata(directoryArg, metadata);
|
|
1460
1882
|
await removeExactOwnedDirectory(directoryArg, {
|
|
1461
|
-
identity:
|
|
1462
|
-
...(
|
|
1883
|
+
identity: expectedIdentityArg,
|
|
1884
|
+
...(metadata
|
|
1463
1885
|
? {
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1886
|
+
assertOwner: (valueArg: unknown) => {
|
|
1887
|
+
const removedOwner = parseOwner(valueArg);
|
|
1888
|
+
if (!exactPrimitiveRecordMatches(removedOwner, expectedOwnerArg)) {
|
|
1889
|
+
throw new Error('Upgrade coordination ownership changed during release.');
|
|
1467
1890
|
}
|
|
1468
1891
|
},
|
|
1469
1892
|
}
|
|
1470
1893
|
: {}),
|
|
1471
|
-
assertOwner: (valueArg) => {
|
|
1472
|
-
const removedOwner = parseOwner(valueArg);
|
|
1473
|
-
if (!exactPrimitiveRecordMatches(removedOwner, expectedOwnerArg)) {
|
|
1474
|
-
throw new Error('Upgrade coordination ownership changed during release.');
|
|
1475
|
-
}
|
|
1476
|
-
},
|
|
1477
1894
|
});
|
|
1478
1895
|
});
|
|
1479
1896
|
}
|
|
@@ -1481,10 +1898,13 @@ export class UpgradeCoordinator {
|
|
|
1481
1898
|
private async inspectTransactionMutationDirectory(
|
|
1482
1899
|
directoryArg: string,
|
|
1483
1900
|
expectedTokenHashArg?: string,
|
|
1901
|
+
attemptArg = 0,
|
|
1484
1902
|
): Promise<{
|
|
1485
1903
|
state: 'absent' | 'live' | 'recent-invalid' | 'stale' | 'indeterminate';
|
|
1486
1904
|
identity?: { dev: number; ino: number };
|
|
1487
1905
|
owner?: IUpgradeTransactionMutationOwner;
|
|
1906
|
+
ownerMetadata?: IOwnedDirectoryOwnerMetadata<IUpgradeTransactionMutationOwner>;
|
|
1907
|
+
inspectionError?: unknown;
|
|
1488
1908
|
}> {
|
|
1489
1909
|
const stats = await lstatIfPresent(directoryArg);
|
|
1490
1910
|
if (!stats) return { state: 'absent' };
|
|
@@ -1494,15 +1914,54 @@ export class UpgradeCoordinator {
|
|
|
1494
1914
|
|| stats.uid !== this.uid
|
|
1495
1915
|
|| (stats.mode & 0o077) !== 0
|
|
1496
1916
|
) throw new Error('The upgrade transaction mutation lock is unsafe.');
|
|
1497
|
-
let
|
|
1917
|
+
let ownerMetadata: IOwnedDirectoryOwnerMetadata<IUpgradeTransactionMutationOwner> | undefined;
|
|
1498
1918
|
try {
|
|
1499
|
-
|
|
1919
|
+
ownerMetadata = await inspectOwnedDirectoryOwnerMetadata(
|
|
1920
|
+
directoryArg,
|
|
1921
|
+
parseTransactionMutationOwner,
|
|
1922
|
+
);
|
|
1500
1923
|
} catch (errorArg) {
|
|
1924
|
+
const currentStats = await lstatIfPresent(directoryArg);
|
|
1925
|
+
if (!currentStats) return { state: 'absent' };
|
|
1926
|
+
if (!sameOwnedDirectoryIdentity(currentStats, { dev: stats.dev, ino: stats.ino })) {
|
|
1927
|
+
if (attemptArg < 2) {
|
|
1928
|
+
return await this.inspectTransactionMutationDirectory(
|
|
1929
|
+
directoryArg,
|
|
1930
|
+
expectedTokenHashArg,
|
|
1931
|
+
attemptArg + 1,
|
|
1932
|
+
);
|
|
1933
|
+
}
|
|
1934
|
+
return {
|
|
1935
|
+
state: 'indeterminate',
|
|
1936
|
+
identity: { dev: currentStats.dev, ino: currentStats.ino },
|
|
1937
|
+
inspectionError: errorArg,
|
|
1938
|
+
};
|
|
1939
|
+
}
|
|
1501
1940
|
const code = (errorArg as NodeJS.ErrnoException).code;
|
|
1502
|
-
if (code === '
|
|
1503
|
-
return {
|
|
1941
|
+
if (code === 'ENOENT') {
|
|
1942
|
+
return {
|
|
1943
|
+
state: Date.now() - currentStats.mtimeMs < initializationGraceMs
|
|
1944
|
+
? 'recent-invalid'
|
|
1945
|
+
: 'stale',
|
|
1946
|
+
identity: { dev: currentStats.dev, ino: currentStats.ino },
|
|
1947
|
+
};
|
|
1504
1948
|
}
|
|
1505
|
-
if (
|
|
1949
|
+
if (
|
|
1950
|
+
Date.now() - currentStats.mtimeMs < initializationGraceMs
|
|
1951
|
+
&& (code === undefined || code === 'HCON_INVALID_JSON')
|
|
1952
|
+
) {
|
|
1953
|
+
return {
|
|
1954
|
+
state: 'recent-invalid',
|
|
1955
|
+
identity: { dev: currentStats.dev, ino: currentStats.ino },
|
|
1956
|
+
};
|
|
1957
|
+
}
|
|
1958
|
+
return {
|
|
1959
|
+
state: 'indeterminate',
|
|
1960
|
+
identity: { dev: currentStats.dev, ino: currentStats.ino },
|
|
1961
|
+
inspectionError: errorArg,
|
|
1962
|
+
};
|
|
1963
|
+
}
|
|
1964
|
+
if (!ownerMetadata) {
|
|
1506
1965
|
return {
|
|
1507
1966
|
state: Date.now() - stats.mtimeMs < initializationGraceMs
|
|
1508
1967
|
? 'recent-invalid'
|
|
@@ -1510,12 +1969,7 @@ export class UpgradeCoordinator {
|
|
|
1510
1969
|
identity: { dev: stats.dev, ino: stats.ino },
|
|
1511
1970
|
};
|
|
1512
1971
|
}
|
|
1513
|
-
|
|
1514
|
-
try {
|
|
1515
|
-
owner = parseTransactionMutationOwner(rawOwner);
|
|
1516
|
-
} catch {
|
|
1517
|
-
return { state: 'indeterminate', identity: { dev: stats.dev, ino: stats.ino } };
|
|
1518
|
-
}
|
|
1972
|
+
const owner = ownerMetadata.owner;
|
|
1519
1973
|
if (
|
|
1520
1974
|
owner.uid !== this.uid
|
|
1521
1975
|
|| (expectedTokenHashArg !== undefined
|
|
@@ -1527,9 +1981,14 @@ export class UpgradeCoordinator {
|
|
|
1527
1981
|
state: ownerIdentity?.fingerprint === owner.fingerprint ? 'live' : 'stale',
|
|
1528
1982
|
identity: { dev: stats.dev, ino: stats.ino },
|
|
1529
1983
|
owner,
|
|
1984
|
+
ownerMetadata,
|
|
1985
|
+
};
|
|
1986
|
+
} catch (errorArg) {
|
|
1987
|
+
return {
|
|
1988
|
+
state: 'indeterminate',
|
|
1989
|
+
identity: { dev: stats.dev, ino: stats.ino },
|
|
1990
|
+
inspectionError: errorArg,
|
|
1530
1991
|
};
|
|
1531
|
-
} catch {
|
|
1532
|
-
return { state: 'indeterminate', identity: { dev: stats.dev, ino: stats.ino } };
|
|
1533
1992
|
}
|
|
1534
1993
|
}
|
|
1535
1994
|
|
|
@@ -1544,9 +2003,14 @@ export class UpgradeCoordinator {
|
|
|
1544
2003
|
);
|
|
1545
2004
|
if (current.state === 'absent') return false;
|
|
1546
2005
|
if (current.state === 'indeterminate') {
|
|
1547
|
-
throw new Error('The upgrade transaction mutation owner cannot be verified safely.'
|
|
2006
|
+
throw new Error('The upgrade transaction mutation owner cannot be verified safely.', {
|
|
2007
|
+
cause: current.inspectionError,
|
|
2008
|
+
});
|
|
1548
2009
|
}
|
|
1549
2010
|
if (current.state !== 'stale') return false;
|
|
2011
|
+
if (current.ownerMetadata) {
|
|
2012
|
+
await restoreOwnedDirectoryOwnerMetadata(directoryArg, current.ownerMetadata);
|
|
2013
|
+
}
|
|
1550
2014
|
await removeExactOwnedDirectory(directoryArg, {
|
|
1551
2015
|
identity: current.identity!,
|
|
1552
2016
|
...(current.owner
|
|
@@ -1572,6 +2036,7 @@ export class UpgradeCoordinator {
|
|
|
1572
2036
|
state: 'absent' | 'live' | 'recent-invalid' | 'stale' | 'indeterminate';
|
|
1573
2037
|
identity?: { dev: number; ino: number };
|
|
1574
2038
|
owner?: IUpgradeProcessOwner;
|
|
2039
|
+
ownerMetadata?: IOwnedDirectoryOwnerMetadata<IUpgradeProcessOwner>;
|
|
1575
2040
|
}> {
|
|
1576
2041
|
let stats: plugins.fs.Stats;
|
|
1577
2042
|
try {
|
|
@@ -1586,15 +2051,13 @@ export class UpgradeCoordinator {
|
|
|
1586
2051
|
if (stats.uid !== this.uid || (stats.mode & 0o077) !== 0) {
|
|
1587
2052
|
throw new Error(`Upgrade coordination owner path is not private: ${directoryArg}`);
|
|
1588
2053
|
}
|
|
1589
|
-
let
|
|
2054
|
+
let ownerMetadata: IOwnedDirectoryOwnerMetadata<IUpgradeProcessOwner> | undefined;
|
|
1590
2055
|
try {
|
|
1591
|
-
|
|
1592
|
-
} catch
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
}
|
|
1597
|
-
if (code !== 'ENOENT') throw errorArg;
|
|
2056
|
+
ownerMetadata = await inspectOwnedDirectoryOwnerMetadata(directoryArg, parseOwner);
|
|
2057
|
+
} catch {
|
|
2058
|
+
return { state: 'indeterminate', identity: { dev: stats.dev, ino: stats.ino } };
|
|
2059
|
+
}
|
|
2060
|
+
if (!ownerMetadata) {
|
|
1598
2061
|
return {
|
|
1599
2062
|
state: Date.now() - stats.mtimeMs < initializationGraceMs
|
|
1600
2063
|
? 'recent-invalid'
|
|
@@ -1602,17 +2065,13 @@ export class UpgradeCoordinator {
|
|
|
1602
2065
|
identity: { dev: stats.dev, ino: stats.ino },
|
|
1603
2066
|
};
|
|
1604
2067
|
}
|
|
1605
|
-
|
|
1606
|
-
try {
|
|
1607
|
-
owner = parseOwner(ownerValue);
|
|
1608
|
-
} catch {
|
|
1609
|
-
return { state: 'indeterminate', identity: { dev: stats.dev, ino: stats.ino } };
|
|
1610
|
-
}
|
|
2068
|
+
const owner = ownerMetadata.owner;
|
|
1611
2069
|
try {
|
|
1612
2070
|
return {
|
|
1613
2071
|
state: await ownerIsLive(owner) ? 'live' : 'stale',
|
|
1614
2072
|
identity: { dev: stats.dev, ino: stats.ino },
|
|
1615
2073
|
owner,
|
|
2074
|
+
ownerMetadata,
|
|
1616
2075
|
};
|
|
1617
2076
|
} catch {
|
|
1618
2077
|
return { state: 'indeterminate', identity: { dev: stats.dev, ino: stats.ino } };
|
|
@@ -1622,7 +2081,11 @@ export class UpgradeCoordinator {
|
|
|
1622
2081
|
private async createOwnedDirectory(
|
|
1623
2082
|
directoryArg: string,
|
|
1624
2083
|
ownerArg: IUpgradeProcessOwner,
|
|
1625
|
-
): Promise<{
|
|
2084
|
+
): Promise<{
|
|
2085
|
+
directory: string;
|
|
2086
|
+
owner: IUpgradeProcessOwner;
|
|
2087
|
+
identity: { dev: number; ino: number };
|
|
2088
|
+
} | undefined> {
|
|
1626
2089
|
return await this.withOwnershipMutationGuard(directoryArg, async () => {
|
|
1627
2090
|
try {
|
|
1628
2091
|
await plugins.fs.promises.mkdir(directoryArg, { mode: 0o700 });
|
|
@@ -1630,13 +2093,26 @@ export class UpgradeCoordinator {
|
|
|
1630
2093
|
if ((errorArg as NodeJS.ErrnoException).code === 'EEXIST') return undefined;
|
|
1631
2094
|
throw errorArg;
|
|
1632
2095
|
}
|
|
2096
|
+
const stats = await plugins.fs.promises.lstat(directoryArg);
|
|
2097
|
+
const identity = { dev: stats.dev, ino: stats.ino };
|
|
1633
2098
|
try {
|
|
1634
2099
|
await writePrivateJson(plugins.path.join(directoryArg, ownerFileName), ownerArg);
|
|
1635
2100
|
await syncDirectory(plugins.path.dirname(directoryArg));
|
|
1636
|
-
return {
|
|
2101
|
+
return {
|
|
2102
|
+
directory: directoryArg,
|
|
2103
|
+
owner: ownerArg,
|
|
2104
|
+
identity,
|
|
2105
|
+
};
|
|
1637
2106
|
} catch (errorArg) {
|
|
1638
|
-
|
|
1639
|
-
|
|
2107
|
+
try {
|
|
2108
|
+
await removeExactFailedOwnerPublication(directoryArg, identity);
|
|
2109
|
+
} catch (cleanupErrorArg) {
|
|
2110
|
+
throw new AggregateError(
|
|
2111
|
+
[errorArg, cleanupErrorArg],
|
|
2112
|
+
'Upgrade coordination owner publication failed and cleanup was incomplete.',
|
|
2113
|
+
{ cause: errorArg },
|
|
2114
|
+
);
|
|
2115
|
+
}
|
|
1640
2116
|
throw errorArg;
|
|
1641
2117
|
}
|
|
1642
2118
|
});
|
|
@@ -1662,20 +2138,13 @@ export class UpgradeCoordinator {
|
|
|
1662
2138
|
(directoryArg, ownerArg, shouldReleaseArg) => this.releaseOwnedDirectory(
|
|
1663
2139
|
directoryArg,
|
|
1664
2140
|
ownerArg,
|
|
2141
|
+
created.identity,
|
|
1665
2142
|
shouldReleaseArg,
|
|
1666
2143
|
),
|
|
1667
2144
|
);
|
|
1668
2145
|
const state = await this.inspectOwnedDirectory(this.upgradeLockDirectory);
|
|
1669
2146
|
if (state.state === 'live') {
|
|
1670
|
-
|
|
1671
|
-
try {
|
|
1672
|
-
currentOwner = parseOwner(await readPrivateJson(
|
|
1673
|
-
plugins.path.join(this.upgradeLockDirectory, ownerFileName),
|
|
1674
|
-
));
|
|
1675
|
-
} catch (errorArg) {
|
|
1676
|
-
if ((errorArg as NodeJS.ErrnoException).code === 'ENOENT') continue;
|
|
1677
|
-
throw errorArg;
|
|
1678
|
-
}
|
|
2147
|
+
const currentOwner = state.owner!;
|
|
1679
2148
|
if (
|
|
1680
2149
|
currentOwner.pid === owner.pid
|
|
1681
2150
|
&& currentOwner.fingerprint === owner.fingerprint
|
|
@@ -1686,6 +2155,7 @@ export class UpgradeCoordinator {
|
|
|
1686
2155
|
(directoryArg, ownerArg, shouldReleaseArg) => this.releaseOwnedDirectory(
|
|
1687
2156
|
directoryArg,
|
|
1688
2157
|
ownerArg,
|
|
2158
|
+
state.identity!,
|
|
1689
2159
|
shouldReleaseArg,
|
|
1690
2160
|
),
|
|
1691
2161
|
);
|
|
@@ -1763,13 +2233,7 @@ export class UpgradeCoordinator {
|
|
|
1763
2233
|
await this.reclaimStaleOwnedDirectory(this.upgradeLockDirectory);
|
|
1764
2234
|
continue;
|
|
1765
2235
|
}
|
|
1766
|
-
|
|
1767
|
-
return parseOwner(await readPrivateJson(
|
|
1768
|
-
plugins.path.join(this.upgradeLockDirectory, ownerFileName),
|
|
1769
|
-
));
|
|
1770
|
-
} catch (errorArg) {
|
|
1771
|
-
if ((errorArg as NodeJS.ErrnoException).code !== 'ENOENT') throw errorArg;
|
|
1772
|
-
}
|
|
2236
|
+
return state.owner!;
|
|
1773
2237
|
}
|
|
1774
2238
|
throw new Error(`The ${currentCliName} upgrade owner changed repeatedly during admission.`);
|
|
1775
2239
|
}
|
|
@@ -1806,6 +2270,7 @@ export class UpgradeCoordinator {
|
|
|
1806
2270
|
(directoryArg, ownerArg, shouldReleaseArg) => this.releaseOwnedDirectory(
|
|
1807
2271
|
directoryArg,
|
|
1808
2272
|
ownerArg,
|
|
2273
|
+
created.identity,
|
|
1809
2274
|
shouldReleaseArg,
|
|
1810
2275
|
),
|
|
1811
2276
|
);
|
|
@@ -1893,37 +2358,54 @@ export class UpgradeCoordinator {
|
|
|
1893
2358
|
createdAt: Date.now(),
|
|
1894
2359
|
};
|
|
1895
2360
|
while (true) {
|
|
1896
|
-
|
|
1897
|
-
|
|
2361
|
+
const acquisition = await this.withOwnershipMutationGuard(lockDirectory, async () => {
|
|
2362
|
+
try {
|
|
2363
|
+
await plugins.fs.promises.mkdir(lockDirectory, { mode: 0o700 });
|
|
2364
|
+
} catch (errorArg) {
|
|
2365
|
+
if ((errorArg as NodeJS.ErrnoException).code !== 'EEXIST') throw errorArg;
|
|
2366
|
+
const current = await this.inspectTransactionMutationDirectory(
|
|
2367
|
+
lockDirectory,
|
|
2368
|
+
owner.tokenHash,
|
|
2369
|
+
);
|
|
2370
|
+
return { state: 'contended' as const, current };
|
|
2371
|
+
}
|
|
2372
|
+
const lockStats = await plugins.fs.promises.lstat(lockDirectory);
|
|
2373
|
+
const lockIdentity = { dev: lockStats.dev, ino: lockStats.ino };
|
|
1898
2374
|
try {
|
|
1899
2375
|
await writePrivateJson(plugins.path.join(lockDirectory, ownerFileName), owner);
|
|
2376
|
+
return { state: 'acquired' as const };
|
|
1900
2377
|
} catch (errorArg) {
|
|
1901
|
-
|
|
2378
|
+
try {
|
|
2379
|
+
await removeExactFailedOwnerPublication(lockDirectory, lockIdentity);
|
|
2380
|
+
} catch (cleanupErrorArg) {
|
|
2381
|
+
throw new AggregateError(
|
|
2382
|
+
[errorArg, cleanupErrorArg],
|
|
2383
|
+
'Upgrade transaction mutation owner publication failed and cleanup was incomplete.',
|
|
2384
|
+
{ cause: errorArg },
|
|
2385
|
+
);
|
|
2386
|
+
}
|
|
1902
2387
|
throw errorArg;
|
|
1903
2388
|
}
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
2389
|
+
});
|
|
2390
|
+
if (acquisition.state === 'acquired') break;
|
|
2391
|
+
const current = acquisition.current;
|
|
2392
|
+
if (current.state === 'absent') continue;
|
|
2393
|
+
if (current.state === 'stale') {
|
|
2394
|
+
await this.reclaimStaleTransactionMutationDirectory(
|
|
1908
2395
|
lockDirectory,
|
|
1909
2396
|
owner.tokenHash,
|
|
1910
2397
|
);
|
|
1911
|
-
|
|
1912
|
-
if (current.state === 'stale') {
|
|
1913
|
-
await this.reclaimStaleTransactionMutationDirectory(
|
|
1914
|
-
lockDirectory,
|
|
1915
|
-
owner.tokenHash,
|
|
1916
|
-
);
|
|
1917
|
-
continue;
|
|
1918
|
-
}
|
|
1919
|
-
if (current.state === 'indeterminate') {
|
|
1920
|
-
throw new Error('The upgrade transaction mutation owner cannot be verified safely.');
|
|
1921
|
-
}
|
|
1922
|
-
if (Date.now() >= deadline) {
|
|
1923
|
-
throw new Error('Timed out waiting to update the upgrade transaction.');
|
|
1924
|
-
}
|
|
1925
|
-
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
2398
|
+
continue;
|
|
1926
2399
|
}
|
|
2400
|
+
if (current.state === 'indeterminate') {
|
|
2401
|
+
throw new Error('The upgrade transaction mutation owner cannot be verified safely.', {
|
|
2402
|
+
cause: current.inspectionError,
|
|
2403
|
+
});
|
|
2404
|
+
}
|
|
2405
|
+
if (Date.now() >= deadline) {
|
|
2406
|
+
throw new Error('Timed out waiting to update the upgrade transaction.');
|
|
2407
|
+
}
|
|
2408
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
1927
2409
|
}
|
|
1928
2410
|
let result: T | undefined;
|
|
1929
2411
|
let operationError: unknown;
|
|
@@ -1936,23 +2418,18 @@ export class UpgradeCoordinator {
|
|
|
1936
2418
|
try {
|
|
1937
2419
|
await this.withOwnershipMutationGuard(lockDirectory, async () => {
|
|
1938
2420
|
const stats = await plugins.fs.promises.lstat(lockDirectory);
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
if ((errorArg as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
1946
|
-
throw new Error(
|
|
1947
|
-
'The upgrade transaction mutation lock ownership changed unexpectedly.',
|
|
1948
|
-
{ cause: errorArg },
|
|
1949
|
-
);
|
|
1950
|
-
}
|
|
1951
|
-
throw errorArg;
|
|
2421
|
+
const metadata = await inspectOwnedDirectoryOwnerMetadata(
|
|
2422
|
+
lockDirectory,
|
|
2423
|
+
parseTransactionMutationOwner,
|
|
2424
|
+
);
|
|
2425
|
+
if (!metadata) {
|
|
2426
|
+
throw new Error('The upgrade transaction mutation lock ownership changed unexpectedly.');
|
|
1952
2427
|
}
|
|
2428
|
+
const currentOwner = metadata.owner;
|
|
1953
2429
|
if (!exactPrimitiveRecordMatches(currentOwner, owner)) {
|
|
1954
2430
|
throw new Error('The upgrade transaction mutation lock ownership changed unexpectedly.');
|
|
1955
2431
|
}
|
|
2432
|
+
await restoreOwnedDirectoryOwnerMetadata(lockDirectory, metadata);
|
|
1956
2433
|
await removeExactOwnedDirectory(lockDirectory, {
|
|
1957
2434
|
identity: { dev: stats.dev, ino: stats.ino },
|
|
1958
2435
|
assertOwner: (valueArg) => {
|