Skip to content

Instantly share code, notes, and snippets.

@trvswgnr
Last active October 22, 2024 09:32
Show Gist options
  • Save trvswgnr/6d0dda4595dda708627cd9076841cbd3 to your computer and use it in GitHub Desktop.
Save trvswgnr/6d0dda4595dda708627cd9076841cbd3 to your computer and use it in GitHub Desktop.
typescript linux exit signals
/**
* Unix/POSIX Signal implementation with architecture-specific handling.
* @module Signal
*/
type Prettify<T> = {
[K in keyof T]: T[K];
} & {};
type IsNever<T> = [T] extends [never] ? true : false;
const validSignalNumbers = [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
25, 26, 27, 28, 29, 30, 31,
] as const;
export type SignalNumber = (typeof validSignalNumbers)[number];
export const isValidSignalNumber = (value: unknown): value is SignalNumber =>
typeof value === "number" && validSignalNumbers.includes(value as SignalNumber);
export const isValidSignalValue = (
value: unknown,
): value is {
alpha_sparc: SignalNumber;
x86_arm: SignalNumber;
mips: SignalNumber;
} =>
typeof value === "object" &&
value !== null &&
"alpha_sparc" in value &&
isValidSignalNumber(value.alpha_sparc) &&
"x86_arm" in value &&
isValidSignalNumber(value.x86_arm) &&
"mips" in value &&
isValidSignalNumber(value.mips);
const hasValidSignalValue = <X extends { value: unknown }>(
value: X,
): value is X & { value: SignalNumber } => isValidSignalValue(value.value);
/** Supported CPU architecture types */
export const ArchType = {
/** ARM 32-bit */
ARM: "arm",
/** ARM 64-bit */
ARM64: "arm64",
/** x86 32-bit */
X86: "ia32",
/** LoongArch 64-bit */
LOONG64: "loong64",
/** MIPS */
MIPS: "mips",
/** MIPS little-endian */
MIPSEL: "mipsel",
/** PowerPC */
PPC: "ppc",
/** PowerPC 64-bit */
PPC64: "ppc64",
/** RISC-V 64-bit */
RISCV64: "riscv64",
/** S390 */
S390: "s390",
/** S390x */
S390X: "s390x",
/** x86 64-bit */
X64: "x64",
} as const;
export type ArchType = (typeof ArchType)[keyof typeof ArchType];
const validSignalNames = [
"SIGHUP",
"SIGINT",
"SIGQUIT",
"SIGILL",
"SIGABRT",
"SIGFPE",
"SIGKILL",
"SIGSEGV",
"SIGPIPE",
"SIGALRM",
"SIGTERM",
"SIGUSR1",
"SIGUSR2",
"SIGCHLD",
"SIGCONT",
"SIGSTOP",
"SIGTSTP",
"SIGTTIN",
"SIGTTOU",
"SIGBUS",
"SIGPOLL",
"SIGPROF",
"SIGSYS",
"SIGTRAP",
"SIGURG",
"SIGVTALRM",
"SIGXCPU",
"SIGXFSZ",
"SIGIOT",
"SIGEMT",
"SIGSTKFLT",
"SIGIO",
"SIGCLD",
"SIGPWR",
"SIGINFO",
"SIGLOST",
"SIGWINCH",
"SIGUNUSED",
] as const;
export type SignalName = (typeof validSignalNames)[number];
export const isValidSignalName = (value: unknown): value is SignalName =>
typeof value === "string" && validSignalNames.includes(value as SignalName);
export const hasValidSignalName = <X extends { name: unknown }>(
value: X,
): value is X & { name: SignalName } => isValidSignalName(value.name);
export const ActionType = {
/** Terminate the process */
Term: "Term",
/** Generate core dump */
Core: "Core",
/** Ignore the signal */
Ign: "Ign",
/** Continue if stopped */
Cont: "Cont",
/** Stop the process */
Stop: "Stop",
} as const;
export type ActionType = (typeof ActionType)[keyof typeof ActionType];
export const isValidActionType = (value: unknown): value is ActionType =>
typeof value === "string" && Object.values(ActionType).includes(value as ActionType);
const hasValidActionType = <X extends { action: unknown }>(
value: X,
): value is X & { action: ActionType } => isValidActionType(value.action);
type ArchValue = SignalNumber | null;
function archTupleToObject<
const A extends ArchValue,
const B extends ArchValue,
const C extends ArchValue,
>(value: readonly [A, B, C]) {
return {
alpha_sparc: value[0],
x86_arm: value[1],
mips: value[2],
} as const;
}
type ArchValues<A extends ArchValue, B extends ArchValue, C extends ArchValue> = {
/** Signal number for Alpha and SPARC architectures */
readonly alpha_sparc: A;
/** Signal number for x86 and ARM architectures */
readonly x86_arm: B;
/** Signal number for MIPS architecture */
readonly mips: C;
};
export type Signal<S extends SignalName, V, A extends ActionType> = {
readonly name: S;
readonly value: V;
readonly action: A;
};
const createSignal = <
const S extends SignalName,
const A extends ArchValue,
const B extends ArchValue,
const C extends ArchValue,
const Ac extends ActionType,
>(
name: S,
value: readonly [A, B, C],
action: Ac,
) =>
({
name,
value: archTupleToObject(value),
action,
} as const);
export namespace Signal {
/** Hangup detected on controlling terminal or death of controlling process */
export const SIGHUP = createSignal("SIGHUP", [1, 1, 1], ActionType.Term);
export type SIGHUP = typeof SIGHUP;
/** Interrupt from keyboard */
export const SIGINT = createSignal("SIGINT", [2, 2, 2], ActionType.Term);
export type SIGINT = typeof SIGINT;
/** Quit from keyboard */
export const SIGQUIT = createSignal("SIGQUIT", [3, 3, 3], ActionType.Core);
export type SIGQUIT = typeof SIGQUIT;
/** Illegal Instruction */
export const SIGILL = createSignal("SIGILL", [4, 4, 4], ActionType.Core);
export type SIGILL = typeof SIGILL;
/** Abort signal from abort(3) */
export const SIGABRT = createSignal("SIGABRT", [6, 6, 6], ActionType.Core);
export type SIGABRT = typeof SIGABRT;
/** Floating-point exception */
export const SIGFPE = createSignal("SIGFPE", [8, 8, 8], ActionType.Core);
export type SIGFPE = typeof SIGFPE;
/** Kill signal, cannot be caught, blocked or ignored */
export const SIGKILL = createSignal("SIGKILL", [9, 9, 9], ActionType.Term);
export type SIGKILL = typeof SIGKILL;
/** Invalid memory reference */
export const SIGSEGV = createSignal("SIGSEGV", [11, 11, 11], ActionType.Core);
export type SIGSEGV = typeof SIGSEGV;
/** Broken pipe: write to pipe with no readers; see pipe(7) */
export const SIGPIPE = createSignal("SIGPIPE", [13, 13, 13], ActionType.Term);
export type SIGPIPE = typeof SIGPIPE;
/** Timer signal from alarm(2) */
export const SIGALRM = createSignal("SIGALRM", [14, 14, 14], ActionType.Term);
export type SIGALRM = typeof SIGALRM;
/** Termination signal */
export const SIGTERM = createSignal("SIGTERM", [15, 15, 15], ActionType.Term);
export type SIGTERM = typeof SIGTERM;
/** User-defined signal 1 */
export const SIGUSR1 = createSignal("SIGUSR1", [30, 10, 16], ActionType.Term);
export type SIGUSR1 = typeof SIGUSR1;
/** User-defined signal 2 */
export const SIGUSR2 = createSignal("SIGUSR2", [31, 12, 17], ActionType.Term);
export type SIGUSR2 = typeof SIGUSR2;
/** Child stopped or terminated */
export const SIGCHLD = createSignal("SIGCHLD", [20, 17, 18], ActionType.Ign);
export type SIGCHLD = typeof SIGCHLD;
/** Continue if stopped */
export const SIGCONT = createSignal("SIGCONT", [19, 18, 25], ActionType.Cont);
export type SIGCONT = typeof SIGCONT;
/** Stop process, cannot be caught, blocked or ignored */
export const SIGSTOP = createSignal("SIGSTOP", [17, 19, 23], ActionType.Stop);
export type SIGSTOP = typeof SIGSTOP;
/** Stop typed at terminal */
export const SIGTSTP = createSignal("SIGTSTP", [18, 20, 24], ActionType.Stop);
export type SIGTSTP = typeof SIGTSTP;
/** Terminal input for background process */
export const SIGTTIN = createSignal("SIGTTIN", [21, 21, 26], ActionType.Stop);
export type SIGTTIN = typeof SIGTTIN;
/** Terminal output for background process */
export const SIGTTOU = createSignal("SIGTTOU", [22, 22, 27], ActionType.Stop);
export type SIGTTOU = typeof SIGTTOU;
/** Bus error (bad memory access) */
export const SIGBUS = createSignal("SIGBUS", [10, 7, 10], ActionType.Core);
export type SIGBUS = typeof SIGBUS;
/** Pollable event (Sys V). Synonym for SIGIO */
export const SIGPOLL = createSignal("SIGPOLL", [29, 29, 22], ActionType.Term);
export type SIGPOLL = typeof SIGPOLL;
/** Profiling timer expired */
export const SIGPROF = createSignal("SIGPROF", [27, 27, 29], ActionType.Term);
export type SIGPROF = typeof SIGPROF;
/** Bad system call (SVr4); see also seccomp(2) */
export const SIGSYS = createSignal("SIGSYS", [12, 31, 12], ActionType.Core);
export type SIGSYS = typeof SIGSYS;
/** Trace/breakpoint trap */
export const SIGTRAP = createSignal("SIGTRAP", [5, 5, 5], ActionType.Core);
export type SIGTRAP = typeof SIGTRAP;
/** Urgent condition on socket (4.2BSD) */
export const SIGURG = createSignal("SIGURG", [16, 23, 21], ActionType.Ign);
export type SIGURG = typeof SIGURG;
/** Virtual alarm clock (4.2BSD) */
export const SIGVTALRM = createSignal("SIGVTALRM", [26, 26, 28], ActionType.Term);
export type SIGVTALRM = typeof SIGVTALRM;
/** CPU time limit exceeded (4.2BSD); see setrlimit(2) */
export const SIGXCPU = createSignal("SIGXCPU", [24, 24, 30], ActionType.Core);
export type SIGXCPU = typeof SIGXCPU;
/** File size limit exceeded (4.2BSD); see setrlimit(2) */
export const SIGXFSZ = createSignal("SIGXFSZ", [25, 25, 31], ActionType.Core);
export type SIGXFSZ = typeof SIGXFSZ;
/** IOT trap. A synonym for SIGABRT */
export const SIGIOT = createSignal("SIGIOT", [6, 6, 6], ActionType.Core);
export type SIGIOT = typeof SIGIOT;
/** Emulator trap */
export const SIGEMT = createSignal("SIGEMT", [7, null, 7], ActionType.Term);
export type SIGEMT = typeof SIGEMT;
/** Stack fault on coprocessor (unused) */
export const SIGSTKFLT = createSignal("SIGSTKFLT", [null, 16, null], ActionType.Term);
export type SIGSTKFLT = typeof SIGSTKFLT;
/** I/O now possible (4.2BSD) */
export const SIGIO = createSignal("SIGIO", [23, 29, 22], ActionType.Term);
export type SIGIO = typeof SIGIO;
/** A synonym for SIGCHLD */
export const SIGCLD = createSignal("SIGCLD", [null, null, 18], ActionType.Ign);
export type SIGCLD = typeof SIGCLD;
/** Power failure (System V) */
export const SIGPWR = createSignal("SIGPWR", [29, 30, 19], ActionType.Term);
export type SIGPWR = typeof SIGPWR;
/** A synonym for SIGPWR */
export const SIGINFO = createSignal("SIGINFO", [29, null, null], ActionType.Term);
export type SIGINFO = typeof SIGINFO;
/** File lock lost (unused) */
export const SIGLOST = createSignal("SIGLOST", [null, null, null], ActionType.Term);
export type SIGLOST = typeof SIGLOST;
/** Window resize signal (4.3BSD, Sun) */
export const SIGWINCH = createSignal("SIGWINCH", [28, 28, 20], ActionType.Ign);
export type SIGWINCH = typeof SIGWINCH;
/** Synonymous with SIGSYS */
export const SIGUNUSED = createSignal("SIGUNUSED", [31, null, null], ActionType.Core);
export type SIGUNUSED = typeof SIGUNUSED;
}
type SignalNS = typeof Signal;
type AnySignal = SignalNS[keyof SignalNS];
type _SignalFromValue<S extends number | ArchValues<ArchValue, ArchValue, ArchValue>> =
S extends number
? Extract<AnySignal, { value: readonly [S, S, S] }>
: Extract<AnySignal, { value: S }>;
type SignalFromValue<S extends number | ArchValues<ArchValue, ArchValue, ArchValue>> =
IsNever<_SignalFromValue<S>> extends true
? S extends SignalNumber
? FilterSignalsByNumber<S>
: never
: _SignalFromValue<S>;
type SignalFromName<N extends keyof SignalNS> = Extract<AnySignal, { signal: N }>;
export function fromValue<
const S extends SignalNumber | ArchValues<ArchValue, ArchValue, ArchValue>,
>(value: S): SignalFromValue<S extends SignalNumber ? ArchValues<S, S, S> : S> {
const archValue = toArch(value);
for (const signal of Object.values(Signal)) {
if (compareArch(signal.value, archValue)) {
return signal as any;
}
}
throw new Error("Invalid signal value");
}
export function fromName<const S extends keyof SignalNS>(name: S): SignalFromName<S> {
return Signal[name] as unknown as SignalFromName<S>;
}
function toArch<S extends number | ArchValues<ArchValue, ArchValue, ArchValue>>(
v: S,
): ArchValues<ArchValue, ArchValue, ArchValue> {
if (isValidSignalNumber(v)) {
return archTupleToObject([v, v, v]);
}
if (typeof v === "object") {
return v;
}
throw new Error("Invalid signal value");
}
function compareArch(
a: ArchValues<ArchValue, ArchValue, ArchValue>,
b: ArchValues<ArchValue, ArchValue, ArchValue>,
): boolean {
const { alpha_sparc: a1, x86_arm: a2, mips: a3 } = a;
const { alpha_sparc: b1, x86_arm: b2, mips: b3 } = b;
return a1 === b1 && a2 === b2 && a3 === b3;
}
export function isSignal(x: unknown): x is Signal<SignalName, number, ActionType> {
return (
typeof x === "object" &&
x !== null &&
"name" in x &&
hasValidSignalName(x) &&
"value" in x &&
hasValidSignalValue(x) &&
"action" in x &&
hasValidActionType(x) &&
isValidSignal(x)
);
}
function isValidSignal<
const X extends {
name: SignalName;
value: SignalNumber;
action: ActionType;
},
>(x: X): x is X & Signal<X["name"], X["value"], X["action"]> {
for (const signal of Object.values(Signal)) {
if (objectsAreEqual(signal, x)) {
return true;
}
}
return false;
}
function objectsAreEqual<
A extends Record<PropertyKey, unknown>,
B extends Record<PropertyKey, unknown>,
>(a: A, b: B): boolean {
const aEntries = Object.entries(a);
const bEntries = Object.entries(b);
return (
aEntries.length === bEntries.length &&
aEntries.every(([key, value]) => b[key] === value)
);
}
type ArchField<A extends ArchType> = A extends "arm" | "arm64" | "ia32" | "x64"
? "x86_arm"
: A extends "mips" | "mipsel"
? "mips"
: A extends "ppc" | "ppc64" | "s390" | "s390x" | "loong64" | "riscv64"
? "alpha_sparc"
: never;
type _SignalForArch<N extends number, A extends ArchType> = Extract<
AnySignal,
{ value: { [K in ArchField<A>]: N } }
>;
type SignalForArch<N extends number, A extends ArchType> = IsNever<
_SignalForArch<N, A>
> extends true
? N extends SignalNumber
? FilterSignalsByNumber<N>
: never
: _SignalForArch<N, A>;
type FilterSignalsByNumber<N extends SignalNumber> = Extract<
AnySignal,
{ value: { alpha_sparc: N } } | { value: { x86_arm: N } } | { value: { mips: N } }
>;
export function getSignalForArchitecture<
const N extends number,
const A extends ArchType,
>(signalNumber: N, arch: A): Prettify<SignalForArch<N, A>> {
const valueField = ["arm", "arm64", "ia32", "x64"].includes(arch)
? "x86_arm"
: ["mips", "mipsel"].includes(arch)
? "mips"
: ["ppc", "ppc64", "s390", "s390x", "loong64", "riscv64"].includes(arch)
? "alpha_sparc"
: null;
if (valueField === null) {
throw new Error(`Unsupported architecture: ${arch}`);
}
for (const signal of Object.values(Signal)) {
if (signal.value[valueField] === signalNumber) {
return signal as SignalForArch<N, A>;
}
}
throw new Error(`No signal found for number ${signalNumber} on architecture ${arch}`);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment