@push.rocks/smartdaemon 3.1.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.
@@ -3,7 +3,7 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@push.rocks/smartdaemon',
6
- version: '3.1.0',
6
+ version: '3.2.0',
7
7
  description: 'Start scripts as long running daemons and manage them.'
8
8
  };
9
9
  //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvMDBfY29tbWl0aW5mb19kYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHO0lBQ3hCLElBQUksRUFBRSx5QkFBeUI7SUFDL0IsT0FBTyxFQUFFLE9BQU87SUFDaEIsV0FBVyxFQUFFLHdEQUF3RDtDQUN0RSxDQUFBIn0=
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Why a file lock operation refused or failed:
3
+ *
4
+ * - `runtime_unsupported`: the lock cannot be called: Node.js older than 26, a platform other than
5
+ * Linux on x64 or arm64, a C library other than glibc (`libc.so.6`), `node:ffi` disabled
6
+ * (`--no-experimental-ffi`) or denied by the permission model (no `--allow-ffi`).
7
+ * - `descriptor_invalid`: a descriptor number that cannot exist (not an integer from 0 to
8
+ * `INT_MAX`); refused before any call.
9
+ * - `lock_failed`: `flock(LOCK_EX | LOCK_NB)` failed for a reason other than a conflicting lock
10
+ * (`errno` names why: `EBADF` for a descriptor that is not open, for example).
11
+ * - `unlock_failed`: `flock(LOCK_UN)` failed (`errno` names why).
12
+ */
13
+ export type TFileLockErrorCode = 'runtime_unsupported' | 'descriptor_invalid' | 'lock_failed' | 'unlock_failed';
14
+ export interface IFileLockErrorOptions {
15
+ /** The C library's `errno` after the failed call, when a call failed. */
16
+ readonly errno?: number;
17
+ readonly cause?: unknown;
18
+ }
19
+ export declare class FileLockError extends Error {
20
+ readonly code: TFileLockErrorCode;
21
+ /** The C library's `errno` after the failed call, or null when no call failed. */
22
+ readonly errno: number | null;
23
+ constructor(code: TFileLockErrorCode, messageArg: string, optionsArg?: IFileLockErrorOptions);
24
+ }
@@ -0,0 +1,9 @@
1
+ export class FileLockError extends Error {
2
+ constructor(code, messageArg, optionsArg = {}) {
3
+ super(`${messageArg}${optionsArg.errno === undefined ? '' : ` (errno ${optionsArg.errno})`}`, optionsArg.cause === undefined ? undefined : { cause: optionsArg.cause });
4
+ this.code = code;
5
+ this.name = 'FileLockError';
6
+ this.errno = optionsArg.errno ?? null;
7
+ }
8
+ }
9
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZmlsZWxvY2suY2xhc3Nlcy5maWxlbG9ja2Vycm9yLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vdHNfZmlsZWxvY2svZmlsZWxvY2suY2xhc3Nlcy5maWxlbG9ja2Vycm9yLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQW9CQSxNQUFNLE9BQU8sYUFBYyxTQUFRLEtBQUs7SUFJdEMsWUFDa0IsSUFBd0IsRUFDeEMsVUFBa0IsRUFDbEIsVUFBVSxHQUEwQixFQUFFO1FBRXRDLEtBQUssQ0FDSCxHQUFHLFVBQVUsR0FBRyxVQUFVLENBQUMsS0FBSyxLQUFLLFNBQVMsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQyxXQUFXLFVBQVUsQ0FBQyxLQUFLLEdBQUcsRUFBRSxFQUN0RixVQUFVLENBQUMsS0FBSyxLQUFLLFNBQVMsQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQyxFQUFFLEtBQUssRUFBRSxVQUFVLENBQUMsS0FBSyxFQUFFLENBQ3pFLENBQUM7b0JBUGMsSUFBSTtRQVFwQixJQUFJLENBQUMsSUFBSSxHQUFHLGVBQWUsQ0FBQztRQUM1QixJQUFJLENBQUMsS0FBSyxHQUFHLFVBQVUsQ0FBQyxLQUFLLElBQUksSUFBSSxDQUFDO0lBQ3hDLENBQUM7Q0FDRiJ9
@@ -0,0 +1,14 @@
1
+ /** Internal: refuses a Node.js older than 26; the subpath runs it on import. */
2
+ export declare const assertFileLockNode: (versionArg?: string) => void;
3
+ /**
4
+ * Takes the exclusive `flock(2)` lock on the open file description behind `descriptorArg`
5
+ * (`LOCK_EX | LOCK_NB`), without waiting. Returns true when the lock is granted, also when this
6
+ * open file description already holds it, and false only when another open file description holds
7
+ * a conflicting lock (`EWOULDBLOCK`). Throws `FileLockError` for every other failure.
8
+ */
9
+ export declare const tryLockFile: (descriptorArg: number) => boolean;
10
+ /**
11
+ * Releases the `flock(2)` lock of the open file description behind `descriptorArg` (`LOCK_UN`).
12
+ * Releasing a description that holds no lock succeeds. Throws `FileLockError` on failure.
13
+ */
14
+ export declare const unlockFile: (descriptorArg: number) => void;
@@ -0,0 +1,38 @@
1
+ import { assertLibcNode, libcConstants, systemdLibc, } from '../dist_ts_systemdruntime/systemdruntime.libc.js';
2
+ import { isSystemdDescriptor } from '../dist_ts_systemdruntime/systemdruntime.validation.js';
3
+ import { FileLockError } from './filelock.classes.filelockerror.js';
4
+ const fileLockUnsupported = (messageArg, causeArg) => new FileLockError('runtime_unsupported', messageArg, causeArg === undefined ? {} : { cause: causeArg });
5
+ /** Internal: refuses a Node.js older than 26; the subpath runs it on import. */
6
+ export const assertFileLockNode = (versionArg = process.versions.node) => assertLibcNode('file-lock', fileLockUnsupported, versionArg);
7
+ const fileLockLibc = (descriptorArg) => {
8
+ if (!isSystemdDescriptor(descriptorArg)) {
9
+ throw new FileLockError('descriptor_invalid', `${String(descriptorArg)} is not a descriptor number.`);
10
+ }
11
+ return systemdLibc(fileLockUnsupported);
12
+ };
13
+ /**
14
+ * Takes the exclusive `flock(2)` lock on the open file description behind `descriptorArg`
15
+ * (`LOCK_EX | LOCK_NB`), without waiting. Returns true when the lock is granted, also when this
16
+ * open file description already holds it, and false only when another open file description holds
17
+ * a conflicting lock (`EWOULDBLOCK`). Throws `FileLockError` for every other failure.
18
+ */
19
+ export const tryLockFile = (descriptorArg) => {
20
+ const libc = fileLockLibc(descriptorArg);
21
+ if (libc.functions.flock(descriptorArg, libcConstants.LOCK_EX | libcConstants.LOCK_NB) === 0)
22
+ return true;
23
+ const errno = libc.errno();
24
+ if (errno === libcConstants.EWOULDBLOCK)
25
+ return false;
26
+ throw new FileLockError('lock_failed', `Descriptor ${descriptorArg} cannot be locked.`, { errno });
27
+ };
28
+ /**
29
+ * Releases the `flock(2)` lock of the open file description behind `descriptorArg` (`LOCK_UN`).
30
+ * Releasing a description that holds no lock succeeds. Throws `FileLockError` on failure.
31
+ */
32
+ export const unlockFile = (descriptorArg) => {
33
+ const libc = fileLockLibc(descriptorArg);
34
+ if (libc.functions.flock(descriptorArg, libcConstants.LOCK_UN) !== 0) {
35
+ throw new FileLockError('unlock_failed', `Descriptor ${descriptorArg} cannot be unlocked.`, { errno: libc.errno() });
36
+ }
37
+ };
38
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZmlsZWxvY2suZmxvY2suanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90c19maWxlbG9jay9maWxlbG9jay5mbG9jay50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQ0wsY0FBYyxFQUNkLGFBQWEsRUFDYixXQUFXLEdBR1osTUFBTSw2Q0FBNkMsQ0FBQztBQUNyRCxPQUFPLEVBQUUsbUJBQW1CLEVBQUUsTUFBTSxtREFBbUQsQ0FBQztBQUN4RixPQUFPLEVBQUUsYUFBYSxFQUFFLE1BQU0scUNBQXFDLENBQUM7QUFFcEUsTUFBTSxtQkFBbUIsR0FBMEIsQ0FBQyxVQUFVLEVBQUUsUUFBUSxFQUFFLEVBQUUsQ0FDMUUsSUFBSSxhQUFhLENBQUMscUJBQXFCLEVBQUUsVUFBVSxFQUFFLFFBQVEsS0FBSyxTQUFTLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUMsRUFBRSxLQUFLLEVBQUUsUUFBUSxFQUFFLENBQUMsQ0FBQztBQUUxRyxnRkFBZ0Y7QUFDaEYsTUFBTSxDQUFDLE1BQU0sa0JBQWtCLEdBQUcsQ0FBQyxVQUFVLEdBQVcsT0FBTyxDQUFDLFFBQVEsQ0FBQyxJQUFJLEVBQVEsRUFBRSxDQUNyRixjQUFjLENBQUMsV0FBVyxFQUFFLG1CQUFtQixFQUFFLFVBQVUsQ0FBQyxDQUFDO0FBRS9ELE1BQU0sWUFBWSxHQUFHLENBQUMsYUFBcUIsRUFBZ0IsRUFBRTtJQUMzRCxJQUFJLENBQUMsbUJBQW1CLENBQUMsYUFBYSxDQUFDLEVBQUUsQ0FBQztRQUN4QyxNQUFNLElBQUksYUFBYSxDQUFDLG9CQUFvQixFQUFFLEdBQUcsTUFBTSxDQUFDLGFBQWEsQ0FBQyw4QkFBOEIsQ0FBQyxDQUFDO0lBQ3hHLENBQUM7SUFDRCxPQUFPLFdBQVcsQ0FBQyxtQkFBbUIsQ0FBQyxDQUFDO0FBQzFDLENBQUMsQ0FBQztBQUVGOzs7OztHQUtHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sV0FBVyxHQUFHLENBQUMsYUFBcUIsRUFBVyxFQUFFO0lBQzVELE1BQU0sSUFBSSxHQUFHLFlBQVksQ0FBQyxhQUFhLENBQUMsQ0FBQztJQUN6QyxJQUFJLElBQUksQ0FBQyxTQUFTLENBQUMsS0FBSyxDQUFDLGFBQWEsRUFBRSxhQUFhLENBQUMsT0FBTyxHQUFHLGFBQWEsQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDO1FBQUUsT0FBTyxJQUFJLENBQUM7SUFDMUcsTUFBTSxLQUFLLEdBQUcsSUFBSSxDQUFDLEtBQUssRUFBRSxDQUFDO0lBQzNCLElBQUksS0FBSyxLQUFLLGFBQWEsQ0FBQyxXQUFXO1FBQUUsT0FBTyxLQUFLLENBQUM7SUFDdEQsTUFBTSxJQUFJLGFBQWEsQ0FBQyxhQUFhLEVBQUUsY0FBYyxhQUFhLG9CQUFvQixFQUFFLEVBQUUsS0FBSyxFQUFFLENBQUMsQ0FBQztBQUNyRyxDQUFDLENBQUM7QUFFRjs7O0dBR0c7QUFDSCxNQUFNLENBQUMsTUFBTSxVQUFVLEdBQUcsQ0FBQyxhQUFxQixFQUFRLEVBQUU7SUFDeEQsTUFBTSxJQUFJLEdBQUcsWUFBWSxDQUFDLGFBQWEsQ0FBQyxDQUFDO0lBQ3pDLElBQUksSUFBSSxDQUFDLFNBQVMsQ0FBQyxLQUFLLENBQUMsYUFBYSxFQUFFLGFBQWEsQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDLEVBQUUsQ0FBQztRQUNyRSxNQUFNLElBQUksYUFBYSxDQUFDLGVBQWUsRUFBRSxjQUFjLGFBQWEsc0JBQXNCLEVBQUUsRUFBRSxLQUFLLEVBQUUsSUFBSSxDQUFDLEtBQUssRUFBRSxFQUFFLENBQUMsQ0FBQztJQUN2SCxDQUFDO0FBQ0gsQ0FBQyxDQUFDIn0=
@@ -0,0 +1,2 @@
1
+ export * from './filelock.classes.filelockerror.js';
2
+ export { tryLockFile, unlockFile } from './filelock.flock.js';
@@ -0,0 +1,11 @@
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
+ export * from './filelock.classes.filelockerror.js';
9
+ export { tryLockFile, unlockFile } from './filelock.flock.js';
10
+ assertFileLockNode();
11
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90c19maWxlbG9jay9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7R0FLRztBQUNILE9BQU8sRUFBRSxrQkFBa0IsRUFBRSxNQUFNLHFCQUFxQixDQUFDO0FBRXpELGNBQWMscUNBQXFDLENBQUM7QUFDcEQsT0FBTyxFQUFFLFdBQVcsRUFBRSxVQUFVLEVBQUUsTUFBTSxxQkFBcUIsQ0FBQztBQUU5RCxrQkFBa0IsRUFBRSxDQUFDIn0=
@@ -1,6 +1,13 @@
1
1
  import type * as ffi from 'node:ffi';
2
2
  /** The first Node.js major with `node:ffi`, which this module calls libc through. */
3
3
  export declare const systemdRuntimeNodeMajor = 26;
4
+ /**
5
+ * Internal: builds the `runtime_unsupported` error of a subpath that calls libc through this
6
+ * loader (`systemd-runtime`, `file-lock`), so each subpath refuses with its own error class.
7
+ */
8
+ export type TLibcUnsupportedError = (messageArg: string, causeArg?: unknown) => Error;
9
+ /** Internal: refuses a Node.js older than `systemdRuntimeNodeMajor`; each libc subpath runs it on import. */
10
+ export declare const assertLibcNode: (subpathArg: string, unsupportedArg: TLibcUnsupportedError, versionArg?: string) => void;
4
11
  /** Internal: refuses a Node.js older than `systemdRuntimeNodeMajor`; the subpath runs it on import. */
5
12
  export declare const assertSystemdRuntimeNode: (versionArg?: string) => void;
6
13
  /**
@@ -15,6 +22,8 @@ export declare const assertSystemdRuntimeNode: (versionArg?: string) => void;
15
22
  * `CMSG_LEN(n)` is 16 + n and `CMSG_SPACE(n)` 16 + n rounded up to 8.
16
23
  * - `timespec` (16 bytes): `tv_sec` at 0, `tv_nsec` at 8.
17
24
  * - `rlimit` (16 bytes): `rlim_cur` (u64) at 0, `rlim_max` (u64) at 8; `RLIM_INFINITY` is all ones.
25
+ *
26
+ * `EWOULDBLOCK` is `EAGAIN` (11) on both.
18
27
  */
19
28
  export declare const libcConstants: {
20
29
  readonly AF_UNIX: 1;
@@ -28,7 +37,11 @@ export declare const libcConstants: {
28
37
  readonly FD_CLOEXEC: 1;
29
38
  readonly CLOCK_MONOTONIC: 1;
30
39
  readonly EINTR: 4;
40
+ readonly EWOULDBLOCK: 11;
31
41
  readonly RLIMIT_NOFILE: 7;
42
+ readonly LOCK_EX: 2;
43
+ readonly LOCK_NB: 4;
44
+ readonly LOCK_UN: 8;
32
45
  };
33
46
  /** `sun_path` is 108 bytes; an address of up to 107 bytes keeps a path's terminating NUL. */
34
47
  export declare const socketAddressMaximumBytes = 107;
@@ -59,6 +72,10 @@ declare const libcFunctions: {
59
72
  readonly arguments: readonly ["int32", "pointer"];
60
73
  readonly return: 'int32';
61
74
  };
75
+ readonly flock: {
76
+ readonly arguments: readonly ["int32", "int32"];
77
+ readonly return: 'int32';
78
+ };
62
79
  readonly __errno_location: {
63
80
  readonly arguments: readonly [];
64
81
  readonly return: 'pointer';
@@ -74,10 +91,12 @@ export interface ISystemdLibc {
74
91
  errno(): number;
75
92
  }
76
93
  /**
77
- * glibc, opened through `node:ffi` on first use and kept for the process lifetime. Loading
78
- * `node:ffi` emits Node's ExperimentalWarning once.
94
+ * glibc, opened through `node:ffi` on first use and kept for the process lifetime: the one libc
95
+ * loader of the package, shared by the `systemd-runtime` and `file-lock` subpaths. A refusal is
96
+ * built by `unsupportedArg`, the calling subpath's `runtime_unsupported` error. Loading `node:ffi`
97
+ * emits Node's ExperimentalWarning once.
79
98
  */
80
- export declare const systemdLibc: () => ISystemdLibc;
99
+ export declare const systemdLibc: (unsupportedArg?: TLibcUnsupportedError) => ISystemdLibc;
81
100
  /** The `msg_namelen` of an `AF_UNIX` address: a path keeps its NUL; an abstract name has none. */
82
101
  export declare const socketAddressLength: (addressArg: string) => number;
83
102
  /** Writes a `sockaddr_un` at `offsetArg`; an abstract name's leading `@` becomes its NUL. */
@@ -1,13 +1,16 @@
1
1
  import { SystemdRuntimeError } from './classes.systemdruntimeerror.js';
2
2
  /** The first Node.js major with `node:ffi`, which this module calls libc through. */
3
3
  export const systemdRuntimeNodeMajor = 26;
4
- /** Internal: refuses a Node.js older than `systemdRuntimeNodeMajor`; the subpath runs it on import. */
5
- export const assertSystemdRuntimeNode = (versionArg = process.versions.node) => {
4
+ const systemdRuntimeUnsupported = (messageArg, causeArg) => new SystemdRuntimeError('runtime_unsupported', messageArg, causeArg === undefined ? {} : { cause: causeArg });
5
+ /** Internal: refuses a Node.js older than `systemdRuntimeNodeMajor`; each libc subpath runs it on import. */
6
+ export const assertLibcNode = (subpathArg, unsupportedArg, versionArg = process.versions.node) => {
6
7
  const major = Number(/^(\d+)\./.exec(versionArg)?.[1]);
7
8
  if (!(major >= systemdRuntimeNodeMajor)) {
8
- throw new SystemdRuntimeError('runtime_unsupported', `@push.rocks/smartdaemon/systemd-runtime requires Node.js ${systemdRuntimeNodeMajor} or later; this is Node.js ${versionArg}.`);
9
+ throw unsupportedArg(`@push.rocks/smartdaemon/${subpathArg} requires Node.js ${systemdRuntimeNodeMajor} or later; this is Node.js ${versionArg}.`);
9
10
  }
10
11
  };
12
+ /** Internal: refuses a Node.js older than `systemdRuntimeNodeMajor`; the subpath runs it on import. */
13
+ export const assertSystemdRuntimeNode = (versionArg = process.versions.node) => assertLibcNode('systemd-runtime', systemdRuntimeUnsupported, versionArg);
11
14
  /**
12
15
  * The generic Linux values both qualified targets (x86_64 and aarch64, glibc) use. The structure
13
16
  * layouts below are the LP64 glibc ones, identical on both:
@@ -20,6 +23,8 @@ export const assertSystemdRuntimeNode = (versionArg = process.versions.node) =>
20
23
  * `CMSG_LEN(n)` is 16 + n and `CMSG_SPACE(n)` 16 + n rounded up to 8.
21
24
  * - `timespec` (16 bytes): `tv_sec` at 0, `tv_nsec` at 8.
22
25
  * - `rlimit` (16 bytes): `rlim_cur` (u64) at 0, `rlim_max` (u64) at 8; `RLIM_INFINITY` is all ones.
26
+ *
27
+ * `EWOULDBLOCK` is `EAGAIN` (11) on both.
23
28
  */
24
29
  export const libcConstants = {
25
30
  AF_UNIX: 1,
@@ -33,7 +38,11 @@ export const libcConstants = {
33
38
  FD_CLOEXEC: 1,
34
39
  CLOCK_MONOTONIC: 1,
35
40
  EINTR: 4,
41
+ EWOULDBLOCK: 11,
36
42
  RLIMIT_NOFILE: 7,
43
+ LOCK_EX: 2,
44
+ LOCK_NB: 4,
45
+ LOCK_UN: 8,
37
46
  };
38
47
  /** `sun_path` is 108 bytes; an address of up to 107 bytes keeps a path's terminating NUL. */
39
48
  export const socketAddressMaximumBytes = 107;
@@ -47,29 +56,32 @@ const libcFunctions = {
47
56
  fcntl: { arguments: ['int32', 'int32', 'int32'], return: 'int32' },
48
57
  clock_gettime: { arguments: ['int32', 'pointer'], return: 'int32' },
49
58
  getrlimit: { arguments: ['int32', 'pointer'], return: 'int32' },
59
+ flock: { arguments: ['int32', 'int32'], return: 'int32' },
50
60
  __errno_location: { arguments: [], return: 'pointer' },
51
61
  };
52
62
  let loaded = null;
53
63
  /**
54
- * glibc, opened through `node:ffi` on first use and kept for the process lifetime. Loading
55
- * `node:ffi` emits Node's ExperimentalWarning once.
64
+ * glibc, opened through `node:ffi` on first use and kept for the process lifetime: the one libc
65
+ * loader of the package, shared by the `systemd-runtime` and `file-lock` subpaths. A refusal is
66
+ * built by `unsupportedArg`, the calling subpath's `runtime_unsupported` error. Loading `node:ffi`
67
+ * emits Node's ExperimentalWarning once.
56
68
  */
57
- export const systemdLibc = () => {
69
+ export const systemdLibc = (unsupportedArg = systemdRuntimeUnsupported) => {
58
70
  if (loaded !== null)
59
71
  return loaded;
60
72
  if (process.platform !== 'linux' || (process.arch !== 'x64' && process.arch !== 'arm64')) {
61
- throw new SystemdRuntimeError('runtime_unsupported', `The systemd runtime supports Linux on x64 and arm64; this is ${process.platform} on ${process.arch}.`);
73
+ throw unsupportedArg(`glibc calls through node:ffi support Linux on x64 and arm64; this is ${process.platform} on ${process.arch}.`);
62
74
  }
63
75
  const ffiModule = process.getBuiltinModule('node:ffi');
64
76
  if (ffiModule === undefined) {
65
- throw new SystemdRuntimeError('runtime_unsupported', 'node:ffi is not available (disabled by --no-experimental-ffi?).');
77
+ throw unsupportedArg('node:ffi is not available (disabled by --no-experimental-ffi?).');
66
78
  }
67
79
  let functions;
68
80
  try {
69
81
  functions = ffiModule.dlopen('libc.so.6', libcFunctions).functions;
70
82
  }
71
83
  catch (error) {
72
- throw new SystemdRuntimeError('runtime_unsupported', 'glibc (libc.so.6) could not be opened through node:ffi; under the permission model pass --allow-ffi.', { cause: error });
84
+ throw unsupportedArg('glibc (libc.so.6) could not be opened through node:ffi; under the permission model pass --allow-ffi.', error);
73
85
  }
74
86
  loaded = Object.freeze({
75
87
  functions,
@@ -89,4 +101,4 @@ export const writeSocketAddress = (bufferArg, offsetArg, addressArg) => {
89
101
  };
90
102
  /** `CMSG_SPACE` for `countArg` descriptors. */
91
103
  export const controlSpace = (countArg) => 16 + ((4 * countArg + 7) & ~7);
92
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3lzdGVtZHJ1bnRpbWUubGliYy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzX3N5c3RlbWRydW50aW1lL3N5c3RlbWRydW50aW1lLmxpYmMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQ0EsT0FBTyxFQUFFLG1CQUFtQixFQUFFLE1BQU0sa0NBQWtDLENBQUM7QUFFdkUscUZBQXFGO0FBQ3JGLE1BQU0sQ0FBQyxNQUFNLHVCQUF1QixHQUFHLEVBQUUsQ0FBQztBQUUxQyx1R0FBdUc7QUFDdkcsTUFBTSxDQUFDLE1BQU0sd0JBQXdCLEdBQUcsQ0FBQyxVQUFVLEdBQVcsT0FBTyxDQUFDLFFBQVEsQ0FBQyxJQUFJLEVBQVEsRUFBRTtJQUMzRixNQUFNLEtBQUssR0FBRyxNQUFNLENBQUMsVUFBVSxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFDdkQsSUFBSSxDQUFDLENBQUMsS0FBSyxJQUFJLHVCQUF1QixDQUFDLEVBQUUsQ0FBQztRQUN4QyxNQUFNLElBQUksbUJBQW1CLENBQzNCLHFCQUFxQixFQUNyQiw0REFBNEQsdUJBQXVCLDhCQUE4QixVQUFVLEdBQUcsQ0FDL0gsQ0FBQztJQUNKLENBQUM7QUFDSCxDQUFDLENBQUM7QUFFRjs7Ozs7Ozs7Ozs7O0dBWUc7QUFDSCxNQUFNLENBQUMsTUFBTSxhQUFhLEdBQUc7SUFDM0IsT0FBTyxFQUFFLENBQUM7SUFDVixVQUFVLEVBQUUsQ0FBQztJQUNiLFlBQVksRUFBRSxTQUFTO0lBQ3ZCLFVBQVUsRUFBRSxDQUFDO0lBQ2IsVUFBVSxFQUFFLENBQUM7SUFDYixZQUFZLEVBQUUsTUFBTTtJQUNwQixPQUFPLEVBQUUsQ0FBQztJQUNWLE9BQU8sRUFBRSxDQUFDO0lBQ1YsVUFBVSxFQUFFLENBQUM7SUFDYixlQUFlLEVBQUUsQ0FBQztJQUNsQixLQUFLLEVBQUUsQ0FBQztJQUNSLGFBQWEsRUFBRSxDQUFDO0NBQ1IsQ0FBQztBQUVY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
104
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3lzdGVtZHJ1bnRpbWUubGliYy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzX3N5c3RlbWRydW50aW1lL3N5c3RlbWRydW50aW1lLmxpYmMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQ0EsT0FBTyxFQUFFLG1CQUFtQixFQUFFLE1BQU0sa0NBQWtDLENBQUM7QUFFdkUscUZBQXFGO0FBQ3JGLE1BQU0sQ0FBQyxNQUFNLHVCQUF1QixHQUFHLEVBQUUsQ0FBQztBQVExQyxNQUFNLHlCQUF5QixHQUEwQixDQUFDLFVBQVUsRUFBRSxRQUFRLEVBQUUsRUFBRSxDQUNoRixJQUFJLG1CQUFtQixDQUFDLHFCQUFxQixFQUFFLFVBQVUsRUFBRSxRQUFRLEtBQUssU0FBUyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDLEVBQUUsS0FBSyxFQUFFLFFBQVEsRUFBRSxDQUFDLENBQUM7QUFFaEgsNkdBQTZHO0FBQzdHLE1BQU0sQ0FBQyxNQUFNLGNBQWMsR0FBRyxDQUM1QixVQUFrQixFQUNsQixjQUFxQyxFQUNyQyxVQUFVLEdBQVcsT0FBTyxDQUFDLFFBQVEsQ0FBQyxJQUFJLEVBQ3BDLEVBQUU7SUFDUixNQUFNLEtBQUssR0FBRyxNQUFNLENBQUMsVUFBVSxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFDdkQsSUFBSSxDQUFDLENBQUMsS0FBSyxJQUFJLHVCQUF1QixDQUFDLEVBQUUsQ0FBQztRQUN4QyxNQUFNLGNBQWMsQ0FDbEIsMkJBQTJCLFVBQVUscUJBQXFCLHVCQUF1Qiw4QkFBOEIsVUFBVSxHQUFHLENBQzdILENBQUM7SUFDSixDQUFDO0FBQ0gsQ0FBQyxDQUFDO0FBRUYsdUdBQXVHO0FBQ3ZHLE1BQU0sQ0FBQyxNQUFNLHdCQUF3QixHQUFHLENBQUMsVUFBVSxHQUFXLE9BQU8sQ0FBQyxRQUFRLENBQUMsSUFBSSxFQUFRLEVBQUUsQ0FDM0YsY0FBYyxDQUFD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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@push.rocks/smartdaemon",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "private": false,
5
5
  "description": "Start scripts as long running daemons and manage them.",
6
6
  "main": "dist_ts/index.js",
@@ -14,6 +14,10 @@
14
14
  "types": "./dist_ts_systemdruntime/index.d.ts",
15
15
  "default": "./dist_ts_systemdruntime/index.js"
16
16
  },
17
+ "./file-lock": {
18
+ "types": "./dist_ts_filelock/index.d.ts",
19
+ "default": "./dist_ts_filelock/index.js"
20
+ },
17
21
  "./package.json": "./package.json"
18
22
  },
19
23
  "type": "module",
@@ -37,8 +41,8 @@
37
41
  },
38
42
  "homepage": "https://code.foss.global/push.rocks/smartdaemon",
39
43
  "devDependencies": {
40
- "@git.zone/tsrepo": "^1.1.1",
41
- "@types/node": "^26.6.5"
44
+ "@git.zone/tsrepo": "^2.1.0",
45
+ "@types/node": "^26.6.6"
42
46
  },
43
47
  "dependencies": {
44
48
  "@push.rocks/lik": "^6.4.4",
@@ -50,6 +54,7 @@
50
54
  "files": [
51
55
  "ts/**/*",
52
56
  "ts_systemdruntime/**/*",
57
+ "ts_filelock/**/*",
53
58
  "ts_web/**/*",
54
59
  "dist/**/*",
55
60
  "dist_*/**/*",
package/readme.md CHANGED
@@ -1155,6 +1155,70 @@ verifies the flag on every passed descriptor, as `sd_listen_fds(3)` does.
1155
1155
  caller's user manager (`systemd-run --user`), and skips with the reason without Node.js 26 or a
1156
1156
  user manager.
1157
1157
 
1158
+ ### An exclusive file lock on an open descriptor (`/file-lock`, Node.js 26)
1159
+
1160
+ Node.js has no file lock. The `@push.rocks/smartdaemon/file-lock` subpath takes the kernel's
1161
+ `flock(2)` lock on a descriptor the caller opened, through the same glibc loader as
1162
+ `/systemd-runtime`, and nothing more: no path, no open, no class, no wait, no shared lock.
1163
+
1164
+ ```typescript
1165
+ import * as fs from 'node:fs';
1166
+ import { FileLockError, tryLockFile, unlockFile } from '@push.rocks/smartdaemon/file-lock';
1167
+
1168
+ // The caller opens (and checks) the file itself; the lock belongs to this open file description.
1169
+ const descriptor = fs.openSync('/var/lib/example/.owner.lock', 'r+');
1170
+ if (!tryLockFile(descriptor)) throw new Error('another process owns /var/lib/example');
1171
+ try {
1172
+ // ... work only the owner may do ...
1173
+ } finally {
1174
+ unlockFile(descriptor); // or close the descriptor, or exit
1175
+ fs.closeSync(descriptor);
1176
+ }
1177
+ ```
1178
+
1179
+ | Call | Does |
1180
+ | --- | --- |
1181
+ | `tryLockFile(fd)` | `flock(fd, LOCK_EX \| LOCK_NB)`: true when granted (also when this description already holds it), false only when another holds a conflicting lock (`EWOULDBLOCK`) |
1182
+ | `unlockFile(fd)` | `flock(fd, LOCK_UN)`; releasing a description that holds no lock succeeds |
1183
+
1184
+ Both are synchronous and either complete or throw `FileLockError` (`code`, and `errno` when the
1185
+ libc call failed): `lock_failed` and `unlock_failed` carry the errno (`EBADF` for a descriptor that
1186
+ is not open), and a number that cannot be a descriptor (not an integer from 0 to `INT_MAX`) is
1187
+ `descriptor_invalid` before any call.
1188
+
1189
+ What the lock is:
1190
+
1191
+ - **Advisory and exclusive.** It excludes only other `flock(2)` locks; a process that does not ask
1192
+ is not stopped from reading or writing the file. `/proc/locks` shows it as `FLOCK ADVISORY WRITE`.
1193
+ - **Held by the open file description**, not by the process or the descriptor number. A `dup` of
1194
+ the descriptor shares it, and so does a descriptor a child inherits (through `stdio`, for
1195
+ example): the child keeps the lock after the parent closed its copy or died. A second `open` of
1196
+ the same file, even in the same process, is a new description and conflicts.
1197
+ - **Released** by `unlockFile`, or when the last descriptor of the description is closed, which a
1198
+ process exit (`SIGKILL` included) does.
1199
+ - **Not an `fcntl` lock.** On a local Linux filesystem `fcntl(2)` record locks (`F_SETLK`,
1200
+ `F_OFD_SETLK`) and `flock(2)` locks do not conflict with each other, so a peer that uses `fcntl` is
1201
+ not excluded.
1202
+ - Any other program taking `flock(2)` on the same file, util-linux `flock(1)` for example, excludes
1203
+ and is excluded by it.
1204
+
1205
+ It requires **Node.js 26 or later** on Linux x64 or arm64 with glibc, like `/systemd-runtime`:
1206
+ importing it on an older Node.js throws `FileLockError` `runtime_unsupported`, and the first call
1207
+ throws the same code on another platform or with `node:ffi` unavailable or denied (pass
1208
+ `--allow-ffi` under the permission model).
1209
+
1210
+ There is **no blocking lock**, on purpose. `node:ffi` has no off-thread call mode, so a blocking
1211
+ `flock(fd, LOCK_EX)` on the main thread stops the event loop for the whole wait: signal handlers,
1212
+ timers and `sd_notify` messages all stall. Moving it into a worker does not help: a worker blocked
1213
+ in `flock` cannot be terminated, and `process.exit()` waits until the lock is granted, which is
1214
+ never while another process holds it. A caller that must wait polls `tryLockFile` on a timer:
1215
+
1216
+ ```typescript
1217
+ const lockWhenFree = async (fd: number, intervalMs = 100): Promise<void> => {
1218
+ while (!tryLockFile(fd)) await new Promise((resolve) => setTimeout(resolve, intervalMs));
1219
+ };
1220
+ ```
1221
+
1158
1222
  ### Starting a program in its own transient scope
1159
1223
 
1160
1224
  A service that launches a long-lived program keeps that program in its own
@@ -1411,8 +1475,8 @@ console.log(template); // View generated systemd unit file
1411
1475
  - **systemd 240 or later** for installing a definition with an `append` output (`standardOutput` or
1412
1476
  `standardError`), refused the same way
1413
1477
  - **Node.js**: Version 14.x or higher
1414
- - **Node.js 26 or later** for the `@push.rocks/smartdaemon/systemd-runtime` subpath (`node:ffi`), on Linux
1415
- x64 or arm64 with glibc
1478
+ - **Node.js 26 or later** for the `@push.rocks/smartdaemon/systemd-runtime` and
1479
+ `@push.rocks/smartdaemon/file-lock` subpaths (`node:ffi`), on Linux x64 or arm64 with glibc
1416
1480
  - **Permissions**: Either root access or properly configured sudo
1417
1481
 
1418
1482
  ## ⚙️ Generated Systemd Service Files
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@push.rocks/smartdaemon',
6
- version: '3.1.0',
6
+ version: '3.2.0',
7
7
  description: 'Start scripts as long running daemons and manage them.'
8
8
  }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Why a file lock operation refused or failed:
3
+ *
4
+ * - `runtime_unsupported`: the lock cannot be called: Node.js older than 26, a platform other than
5
+ * Linux on x64 or arm64, a C library other than glibc (`libc.so.6`), `node:ffi` disabled
6
+ * (`--no-experimental-ffi`) or denied by the permission model (no `--allow-ffi`).
7
+ * - `descriptor_invalid`: a descriptor number that cannot exist (not an integer from 0 to
8
+ * `INT_MAX`); refused before any call.
9
+ * - `lock_failed`: `flock(LOCK_EX | LOCK_NB)` failed for a reason other than a conflicting lock
10
+ * (`errno` names why: `EBADF` for a descriptor that is not open, for example).
11
+ * - `unlock_failed`: `flock(LOCK_UN)` failed (`errno` names why).
12
+ */
13
+ export type TFileLockErrorCode = 'runtime_unsupported' | 'descriptor_invalid' | 'lock_failed' | 'unlock_failed';
14
+
15
+ export interface IFileLockErrorOptions {
16
+ /** The C library's `errno` after the failed call, when a call failed. */
17
+ readonly errno?: number;
18
+ readonly cause?: unknown;
19
+ }
20
+
21
+ export class FileLockError extends Error {
22
+ /** The C library's `errno` after the failed call, or null when no call failed. */
23
+ public readonly errno: number | null;
24
+
25
+ constructor(
26
+ public readonly code: TFileLockErrorCode,
27
+ messageArg: string,
28
+ optionsArg: IFileLockErrorOptions = {},
29
+ ) {
30
+ super(
31
+ `${messageArg}${optionsArg.errno === undefined ? '' : ` (errno ${optionsArg.errno})`}`,
32
+ optionsArg.cause === undefined ? undefined : { cause: optionsArg.cause },
33
+ );
34
+ this.name = 'FileLockError';
35
+ this.errno = optionsArg.errno ?? null;
36
+ }
37
+ }
@@ -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
- /** Internal: refuses a Node.js older than `systemdRuntimeNodeMajor`; the subpath runs it on import. */
8
- export const assertSystemdRuntimeNode = (versionArg: string = process.versions.node): void => {
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 new SystemdRuntimeError(
12
- 'runtime_unsupported',
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. Loading
77
- * `node:ffi` emits Node's ExperimentalWarning once.
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 new SystemdRuntimeError(
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 new SystemdRuntimeError('runtime_unsupported', 'node:ffi is not available (disabled by --no-experimental-ffi?).');
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 new SystemdRuntimeError(
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
- { cause: error },
119
+ error,
99
120
  );
100
121
  }
101
122
  loaded = Object.freeze({