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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
//! Hot-reload support: replace the running bot binary without dropping the
//! IRC connection.
//!
//! The functions in this module are Unix-only. On other targets only the
//! `ENV_*` constants remain, and the `#[bot]` macro omits the `SIGHUP` listener
//! that drives a reload.
//!
//! # How it works
//!
//! On Unix, a TCP socket is just an open file descriptor. When a process
//! calls `exec()` the new process image inherits all file descriptors that do
//! **not** have the `FD_CLOEXEC` flag set.
//!
//! [`exec_reload`] exploits this:
//!
//! 1. Clears `FD_CLOEXEC` on the live TCP socket fd so the new binary
//! inherits it.
//! 2. Serialises the connection metadata (fd number, nick, server, channels,
//! keepalive settings, and — when recorded via [`record_flood_settings`] —
//! flood-control settings) into environment variables.
//! 3. Calls `exec()` to replace the current process image with the new
//! binary. The PID does not change, and the TCP connection is never closed.
//!
//! The new binary calls [`crate::connection::State::try_inherit_from_env`]
//! at startup. If the env vars are present it reconstructs a live `State`
//! from the inherited fd instead of opening a new TCP connection.
//!
//! # TLS connections
//!
//! A `raw_fd` of `None` means the connection cannot be handed over — the case
//! for TLS. A TLS session's keys, record sequence numbers, and any
//! partially-read record live in this process's memory, which `exec` discards.
//! The successor would inherit a socket it has no way to decrypt. The binary is
//! still replaced, just without the socket, so the new process connects afresh
//! and rejoins. Callers must warn the user that the connection will drop.
/// Replace the current process image with a new build of the same binary,
/// handing over the live IRC socket when it can be inherited.
///
/// Pass `raw_fd` as `None` to replace the binary without the socket. The
/// successor then opens a fresh connection and rejoins `channels`.
///
/// On success this function does not return: the process image is gone.
///
/// # Errors
///
/// Returns the error that stopped the reload. Because `exec()` only returns on
/// failure, a returned value always means the current process is still running
/// the old binary and still holds the connection.
/// Record the active flood-control settings (`burst`, `rate` in milliseconds)
/// so a subsequent [`exec_reload`] can carry them to the successor process.
///
/// Called once by [`crate::internal::run_bot`] at start-up. This indirection
/// exists because the `#[bot]` macro — pinned to a published version — does not
/// forward flood settings to `exec_reload` directly. Only the first value is
/// retained; later calls (e.g. on reconnect) are ignored.
/// Flood-control settings stashed by [`record_flood_settings`] for [`exec_reload`].
static FLOOD_FOR_RELOAD: OnceLock = new;
// ─── env var names ────────────────────────────────────────────────────────────
//
// exec_reload writes these into the environment of the successor process, and
// State::try_inherit_from_env reads them back.
/// Holds the file descriptor number of the inherited socket. When this variable
/// is absent, the successor opens a new connection.
pub const ENV_FD: &str = "IRCBOT_INHERIT_FD";
/// Holds the nick the predecessor was registered with.
pub const ENV_NICK: &str = "IRCBOT_NICK";
/// Holds the server address the predecessor was connected to.
pub const ENV_SERVER: &str = "IRCBOT_SERVER";
/// Holds the joined channels, separated by commas.
pub const ENV_CHANNELS: &str = "IRCBOT_CHANNELS";
/// Holds the keepalive interval, in milliseconds.
pub const ENV_KA_INTERVAL: &str = "IRCBOT_KEEPALIVE_INTERVAL_MS";
/// Holds the keepalive timeout, in milliseconds.
pub const ENV_KA_TIMEOUT: &str = "IRCBOT_KEEPALIVE_TIMEOUT_MS";
/// Holds the flood-control burst size. Absent when the predecessor never called
/// [`record_flood_settings`], and the successor then uses the default.
pub const ENV_FLOOD_BURST: &str = "IRCBOT_FLOOD_BURST";
/// Holds the flood-control rate, in milliseconds per message. Absent under the
/// same conditions as [`ENV_FLOOD_BURST`].
pub const ENV_FLOOD_RATE: &str = "IRCBOT_FLOOD_RATE_MS";