@push.rocks/smartdaemon 3.0.0 → 3.2.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/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.systemdunit.d.ts +58 -2
- package/dist_ts/classes.systemdunit.js +87 -6
- package/dist_ts/smartdaemon.plugins.d.ts +2 -1
- package/dist_ts/smartdaemon.plugins.js +3 -2
- package/dist_ts/systemd.managerversion.d.ts +2 -0
- package/dist_ts/systemd.managerversion.js +3 -1
- package/dist_ts_filelock/filelock.classes.filelockerror.d.ts +24 -0
- package/dist_ts_filelock/filelock.classes.filelockerror.js +9 -0
- package/dist_ts_filelock/filelock.flock.d.ts +14 -0
- package/dist_ts_filelock/filelock.flock.js +38 -0
- package/dist_ts_filelock/index.d.ts +2 -0
- package/dist_ts_filelock/index.js +11 -0
- package/dist_ts_systemdruntime/systemdruntime.libc.d.ts +22 -3
- package/dist_ts_systemdruntime/systemdruntime.libc.js +22 -10
- package/package.json +11 -8
- package/readme.md +109 -2
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.systemdunit.ts +120 -4
- package/ts/smartdaemon.plugins.ts +2 -1
- package/ts/systemd.managerversion.ts +3 -0
- package/ts_filelock/filelock.classes.filelockerror.ts +37 -0
- package/ts_filelock/filelock.flock.ts +48 -0
- package/ts_filelock/index.ts +12 -0
- package/ts_systemdruntime/systemdruntime.libc.ts +37 -16
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import {
|
|
2
|
+
assertLibcNode,
|
|
3
|
+
libcConstants,
|
|
4
|
+
systemdLibc,
|
|
5
|
+
type ISystemdLibc,
|
|
6
|
+
type TLibcUnsupportedError,
|
|
7
|
+
} from '../ts_systemdruntime/systemdruntime.libc.js';
|
|
8
|
+
import { isSystemdDescriptor } from '../ts_systemdruntime/systemdruntime.validation.js';
|
|
9
|
+
import { FileLockError } from './filelock.classes.filelockerror.js';
|
|
10
|
+
|
|
11
|
+
const fileLockUnsupported: TLibcUnsupportedError = (messageArg, causeArg) =>
|
|
12
|
+
new FileLockError('runtime_unsupported', messageArg, causeArg === undefined ? {} : { cause: causeArg });
|
|
13
|
+
|
|
14
|
+
/** Internal: refuses a Node.js older than 26; the subpath runs it on import. */
|
|
15
|
+
export const assertFileLockNode = (versionArg: string = process.versions.node): void =>
|
|
16
|
+
assertLibcNode('file-lock', fileLockUnsupported, versionArg);
|
|
17
|
+
|
|
18
|
+
const fileLockLibc = (descriptorArg: number): ISystemdLibc => {
|
|
19
|
+
if (!isSystemdDescriptor(descriptorArg)) {
|
|
20
|
+
throw new FileLockError('descriptor_invalid', `${String(descriptorArg)} is not a descriptor number.`);
|
|
21
|
+
}
|
|
22
|
+
return systemdLibc(fileLockUnsupported);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Takes the exclusive `flock(2)` lock on the open file description behind `descriptorArg`
|
|
27
|
+
* (`LOCK_EX | LOCK_NB`), without waiting. Returns true when the lock is granted, also when this
|
|
28
|
+
* open file description already holds it, and false only when another open file description holds
|
|
29
|
+
* a conflicting lock (`EWOULDBLOCK`). Throws `FileLockError` for every other failure.
|
|
30
|
+
*/
|
|
31
|
+
export const tryLockFile = (descriptorArg: number): boolean => {
|
|
32
|
+
const libc = fileLockLibc(descriptorArg);
|
|
33
|
+
if (libc.functions.flock(descriptorArg, libcConstants.LOCK_EX | libcConstants.LOCK_NB) === 0) return true;
|
|
34
|
+
const errno = libc.errno();
|
|
35
|
+
if (errno === libcConstants.EWOULDBLOCK) return false;
|
|
36
|
+
throw new FileLockError('lock_failed', `Descriptor ${descriptorArg} cannot be locked.`, { errno });
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Releases the `flock(2)` lock of the open file description behind `descriptorArg` (`LOCK_UN`).
|
|
41
|
+
* Releasing a description that holds no lock succeeds. Throws `FileLockError` on failure.
|
|
42
|
+
*/
|
|
43
|
+
export const unlockFile = (descriptorArg: number): void => {
|
|
44
|
+
const libc = fileLockLibc(descriptorArg);
|
|
45
|
+
if (libc.functions.flock(descriptorArg, libcConstants.LOCK_UN) !== 0) {
|
|
46
|
+
throw new FileLockError('unlock_failed', `Descriptor ${descriptorArg} cannot be unlocked.`, { errno: libc.errno() });
|
|
47
|
+
}
|
|
48
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@push.rocks/smartdaemon/file-lock`: the exclusive, advisory `flock(2)` lock on a descriptor the
|
|
3
|
+
* caller opened, through glibc called with `node:ffi`. There is no blocking lock: a caller that must
|
|
4
|
+
* wait polls `tryLockFile` on a timer. Requires Node.js 26 or later: importing it on an older
|
|
5
|
+
* Node.js throws `FileLockError` `runtime_unsupported`.
|
|
6
|
+
*/
|
|
7
|
+
import { assertFileLockNode } from './filelock.flock.js';
|
|
8
|
+
|
|
9
|
+
export * from './filelock.classes.filelockerror.js';
|
|
10
|
+
export { tryLockFile, unlockFile } from './filelock.flock.js';
|
|
11
|
+
|
|
12
|
+
assertFileLockNode();
|
|
@@ -4,17 +4,33 @@ import { SystemdRuntimeError } from './classes.systemdruntimeerror.js';
|
|
|
4
4
|
/** The first Node.js major with `node:ffi`, which this module calls libc through. */
|
|
5
5
|
export const systemdRuntimeNodeMajor = 26;
|
|
6
6
|
|
|
7
|
-
/**
|
|
8
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Internal: builds the `runtime_unsupported` error of a subpath that calls libc through this
|
|
9
|
+
* loader (`systemd-runtime`, `file-lock`), so each subpath refuses with its own error class.
|
|
10
|
+
*/
|
|
11
|
+
export type TLibcUnsupportedError = (messageArg: string, causeArg?: unknown) => Error;
|
|
12
|
+
|
|
13
|
+
const systemdRuntimeUnsupported: TLibcUnsupportedError = (messageArg, causeArg) =>
|
|
14
|
+
new SystemdRuntimeError('runtime_unsupported', messageArg, causeArg === undefined ? {} : { cause: causeArg });
|
|
15
|
+
|
|
16
|
+
/** Internal: refuses a Node.js older than `systemdRuntimeNodeMajor`; each libc subpath runs it on import. */
|
|
17
|
+
export const assertLibcNode = (
|
|
18
|
+
subpathArg: string,
|
|
19
|
+
unsupportedArg: TLibcUnsupportedError,
|
|
20
|
+
versionArg: string = process.versions.node,
|
|
21
|
+
): void => {
|
|
9
22
|
const major = Number(/^(\d+)\./.exec(versionArg)?.[1]);
|
|
10
23
|
if (!(major >= systemdRuntimeNodeMajor)) {
|
|
11
|
-
throw
|
|
12
|
-
|
|
13
|
-
`@push.rocks/smartdaemon/systemd-runtime requires Node.js ${systemdRuntimeNodeMajor} or later; this is Node.js ${versionArg}.`,
|
|
24
|
+
throw unsupportedArg(
|
|
25
|
+
`@push.rocks/smartdaemon/${subpathArg} requires Node.js ${systemdRuntimeNodeMajor} or later; this is Node.js ${versionArg}.`,
|
|
14
26
|
);
|
|
15
27
|
}
|
|
16
28
|
};
|
|
17
29
|
|
|
30
|
+
/** Internal: refuses a Node.js older than `systemdRuntimeNodeMajor`; the subpath runs it on import. */
|
|
31
|
+
export const assertSystemdRuntimeNode = (versionArg: string = process.versions.node): void =>
|
|
32
|
+
assertLibcNode('systemd-runtime', systemdRuntimeUnsupported, versionArg);
|
|
33
|
+
|
|
18
34
|
/**
|
|
19
35
|
* The generic Linux values both qualified targets (x86_64 and aarch64, glibc) use. The structure
|
|
20
36
|
* layouts below are the LP64 glibc ones, identical on both:
|
|
@@ -27,6 +43,8 @@ export const assertSystemdRuntimeNode = (versionArg: string = process.versions.n
|
|
|
27
43
|
* `CMSG_LEN(n)` is 16 + n and `CMSG_SPACE(n)` 16 + n rounded up to 8.
|
|
28
44
|
* - `timespec` (16 bytes): `tv_sec` at 0, `tv_nsec` at 8.
|
|
29
45
|
* - `rlimit` (16 bytes): `rlim_cur` (u64) at 0, `rlim_max` (u64) at 8; `RLIM_INFINITY` is all ones.
|
|
46
|
+
*
|
|
47
|
+
* `EWOULDBLOCK` is `EAGAIN` (11) on both.
|
|
30
48
|
*/
|
|
31
49
|
export const libcConstants = {
|
|
32
50
|
AF_UNIX: 1,
|
|
@@ -40,7 +58,11 @@ export const libcConstants = {
|
|
|
40
58
|
FD_CLOEXEC: 1,
|
|
41
59
|
CLOCK_MONOTONIC: 1,
|
|
42
60
|
EINTR: 4,
|
|
61
|
+
EWOULDBLOCK: 11,
|
|
43
62
|
RLIMIT_NOFILE: 7,
|
|
63
|
+
LOCK_EX: 2,
|
|
64
|
+
LOCK_NB: 4,
|
|
65
|
+
LOCK_UN: 8,
|
|
44
66
|
} as const;
|
|
45
67
|
|
|
46
68
|
/** `sun_path` is 108 bytes; an address of up to 107 bytes keeps a path's terminating NUL. */
|
|
@@ -57,6 +79,7 @@ const libcFunctions = {
|
|
|
57
79
|
fcntl: { arguments: ['int32', 'int32', 'int32'], return: 'int32' },
|
|
58
80
|
clock_gettime: { arguments: ['int32', 'pointer'], return: 'int32' },
|
|
59
81
|
getrlimit: { arguments: ['int32', 'pointer'], return: 'int32' },
|
|
82
|
+
flock: { arguments: ['int32', 'int32'], return: 'int32' },
|
|
60
83
|
__errno_location: { arguments: [], return: 'pointer' },
|
|
61
84
|
} as const satisfies ffi.FunctionDefinitions;
|
|
62
85
|
|
|
@@ -73,29 +96,27 @@ export interface ISystemdLibc {
|
|
|
73
96
|
let loaded: ISystemdLibc | null = null;
|
|
74
97
|
|
|
75
98
|
/**
|
|
76
|
-
* glibc, opened through `node:ffi` on first use and kept for the process lifetime
|
|
77
|
-
* `
|
|
99
|
+
* glibc, opened through `node:ffi` on first use and kept for the process lifetime: the one libc
|
|
100
|
+
* loader of the package, shared by the `systemd-runtime` and `file-lock` subpaths. A refusal is
|
|
101
|
+
* built by `unsupportedArg`, the calling subpath's `runtime_unsupported` error. Loading `node:ffi`
|
|
102
|
+
* emits Node's ExperimentalWarning once.
|
|
78
103
|
*/
|
|
79
|
-
export const systemdLibc = (): ISystemdLibc => {
|
|
104
|
+
export const systemdLibc = (unsupportedArg: TLibcUnsupportedError = systemdRuntimeUnsupported): ISystemdLibc => {
|
|
80
105
|
if (loaded !== null) return loaded;
|
|
81
106
|
if (process.platform !== 'linux' || (process.arch !== 'x64' && process.arch !== 'arm64')) {
|
|
82
|
-
throw
|
|
83
|
-
'runtime_unsupported',
|
|
84
|
-
`The systemd runtime supports Linux on x64 and arm64; this is ${process.platform} on ${process.arch}.`,
|
|
85
|
-
);
|
|
107
|
+
throw unsupportedArg(`glibc calls through node:ffi support Linux on x64 and arm64; this is ${process.platform} on ${process.arch}.`);
|
|
86
108
|
}
|
|
87
109
|
const ffiModule: typeof ffi | undefined = process.getBuiltinModule('node:ffi');
|
|
88
110
|
if (ffiModule === undefined) {
|
|
89
|
-
throw
|
|
111
|
+
throw unsupportedArg('node:ffi is not available (disabled by --no-experimental-ffi?).');
|
|
90
112
|
}
|
|
91
113
|
let functions: ffi.WrappedFunctionsFromDefinitions<typeof libcFunctions>;
|
|
92
114
|
try {
|
|
93
115
|
functions = ffiModule.dlopen('libc.so.6', libcFunctions).functions;
|
|
94
116
|
} catch (error) {
|
|
95
|
-
throw
|
|
96
|
-
'runtime_unsupported',
|
|
117
|
+
throw unsupportedArg(
|
|
97
118
|
'glibc (libc.so.6) could not be opened through node:ffi; under the permission model pass --allow-ffi.',
|
|
98
|
-
|
|
119
|
+
error,
|
|
99
120
|
);
|
|
100
121
|
}
|
|
101
122
|
loaded = Object.freeze({
|