Skip to main content

yo_resp/dispatch/
server.rs

1//! The connection and server commands.
2//!
3//! None of these touch a key. They are here because a client library sends most
4//! of them before it sends anything else: a driver opens a socket, says `HELLO
5//! 3`, maybe `SELECT 4`, asks `COMMAND DOCS` or `COMMAND COUNT` to build its
6//! own routing table, and only then does any work. A server that answers `GET`
7//! perfectly and `HELLO` badly is a server no client library can talk to, which
8//! is why these land in the same milestone as the string commands rather than
9//! after them.
10//!
11//! The replies were read off a running Redis 8.8 in both protocols. The shapes
12//! are not obvious from the documentation: `HELLO` is a map on RESP3 and the
13//! same pairs flattened on RESP2, `CONFIG GET` is the same, `INFO` is a
14//! verbatim string on RESP3 and a bulk string on RESP2, and the flags in
15//! `COMMAND INFO` are simple strings inside an array rather than bulk strings.
16
17use super::args::{self, Args, is};
18use super::table::{self, Spec};
19use super::{DATABASES, Flow, Server, Session, auth, backup, cpu, debug, multi, notify, persist};
20use crate::proto::Proto;
21use crate::reply::Out;
22use core::fmt::Write;
23use std::time::{SystemTime, UNIX_EPOCH};
24use yo_common::num::parse_i64;
25use yo_common::{Code, Error, Result, glob};
26use yo_kv::Keyspace;
27use yo_kv::access::Policy;
28
29/// What we tell a client we are.
30///
31/// It is a lie and it is a deliberate one. Every client library in the world
32/// branches on this pair to decide which commands exist, and a driver that
33/// reads `yo` here falls back to its oldest code path or refuses to connect.
34/// Divergence D-12 in `divergences.toml` says so, and the honest answer is in
35/// the `yo_version` field of `INFO` next to this one.
36const REPORTED_SERVER: &str = "redis";
37/// The Redis version we answer 100 percent of, which is what `HELLO` reports.
38///
39/// [`super::backup`] writes it into the `redis-ver` aux field of the base file
40/// it produces, so a server told to load one reads the same version out of the
41/// file that a client reads off the connection.
42pub(super) const REPORTED_VERSION: &str = "8.8.0";
43
44/// The settings that are fixed for the life of the process.
45///
46/// `CONFIG SET` accepts a write to one of these that changes nothing and
47/// refuses everything else rather than pretending to have taken it. A client
48/// that sets `appendonly no` on a server that already has no append only file
49/// gets an `OK` and is telling the truth; one that sets `appendonly yes` gets
50/// told it cannot, which is better than an `OK` and no file.
51const SETTINGS: &[(&str, &str)] = &[
52    ("appendonly", "no"),
53    ("appendfsync", "everysec"),
54    // Where `BACKUP` writes, under `dir`. Fixed here where a real server takes
55    // it at startup, because nothing in this build reads it from a file.
56    ("backupdirname", backup::DIR_NAME),
57    ("databases", "16"),
58    ("io-threads", "1"),
59    ("proto-max-bulk-len", "536870912"),
60    ("save", ""),
61    ("timeout", "0"),
62];
63
64/// Which number on the size ladder a settings name refers to.
65#[derive(Debug, Clone, Copy, PartialEq, Eq)]
66enum Knob {
67    SetIntsetEntries,
68    SetListpackEntries,
69    SetListpackValue,
70    HashListpackEntries,
71    HashListpackValue,
72    MaxmemorySamples,
73    LfuLogFactor,
74    LfuDecayTime,
75}
76
77/// The settings that move the size ladder, which are the ones that really move.
78///
79/// These decide where a collection stops being a packed blob and becomes an
80/// element table, so they decide what `OBJECT ENCODING` answers, and a client
81/// that reads `OBJECT ENCODING` after setting one of these expects the two to
82/// agree. That is the whole reason they are writable when nothing else here is.
83///
84/// The `ziplist` spellings are the names these had before Redis renamed them
85/// and it still answers to both, so this does too. Two names, one number: a
86/// `CONFIG SET hash-max-ziplist-entries 4` shows up under the listpack name
87/// too, which was checked against 8.10.1 rather than assumed.
88///
89/// Moving one of these leaves every collection that already exists exactly as
90/// it is, and only decides what the next write builds. Redis does the same, and
91/// it is the reason `CONFIG SET set-max-listpack-entries 0` does not rewrite
92/// the keyspace.
93///
94/// The three eviction numbers are in here too, which stretches the name a
95/// little. They belong with these rather than with the immutable settings for
96/// the same reason: a client that sets one and then reads `OBJECT FREQ` or
97/// watches `evicted_keys` expects the two to agree. `maxmemory-samples` says how
98/// many keys a round of sampling looks at, and the two `lfu` numbers set what
99/// the counter under an LFU policy actually measures.
100const LADDER: &[(&str, Knob)] = &[
101    ("hash-max-listpack-entries", Knob::HashListpackEntries),
102    ("hash-max-listpack-value", Knob::HashListpackValue),
103    ("hash-max-ziplist-entries", Knob::HashListpackEntries),
104    ("hash-max-ziplist-value", Knob::HashListpackValue),
105    ("lfu-decay-time", Knob::LfuDecayTime),
106    ("lfu-log-factor", Knob::LfuLogFactor),
107    ("maxmemory-samples", Knob::MaxmemorySamples),
108    ("set-max-intset-entries", Knob::SetIntsetEntries),
109    ("set-max-listpack-entries", Knob::SetListpackEntries),
110    ("set-max-listpack-value", Knob::SetListpackValue),
111];
112
113/// The setting that decides which way the access field on every record is read.
114///
115/// It is on its own rather than in [`SETTINGS`] or [`LADDER`] because it is the
116/// only writable setting that is not a number, and rather than immutable because
117/// it really moves: a client that sets it and then reads `OBJECT FREQ` expects
118/// the two to agree, which is the same argument the size ladder makes.
119///
120/// Setting it changes nothing about the keys already stored. Whatever is in
121/// their access field stays there and means something different from the moment
122/// the policy changes, which is what the `OBJECT FREQ` error text warns about.
123const MAXMEMORY_POLICY: &str = "maxmemory-policy";
124
125/// How much the server is allowed to hold before it starts evicting.
126///
127/// Also on its own, and for the third different reason. It is not immutable,
128/// it is not on the size ladder and it is the only setting whose value is not a
129/// plain integer: a client writes `maxmemory 100mb` and means a hundred and
130/// four million bytes, so it needs a parser of its own.
131///
132/// Zero means no limit, which is the default and is what makes the check in
133/// front of every write one comparison. Setting it to a number smaller than
134/// what the server is already holding is allowed and is a real thing to do: the
135/// next write that would allocate evicts until it fits or is refused, which is
136/// what the `maxmemory-policy` decides between.
137const MAXMEMORY: &str = "maxmemory";
138
139/// How much the server is allowed to keep on the file before it starts evicting.
140///
141/// The other half of the eviction inversion `14` section 4.1 describes, and the
142/// only setting here that has no counterpart in Redis. `maxmemory` is a limit on
143/// memory, and the right answer to a memory limit on a system with a file under
144/// it is to move data to the file. Throwing data away is the right answer to a
145/// limit on the file, and this is that limit.
146///
147/// Minus one is no limit and is the default, so a server that never sets this
148/// grows until the disk is full and then refuses writes, which is what a
149/// database does. Zero is a real setting and it means the file may hold nothing,
150/// so migration cannot make room and eviction is all that is left, which is
151/// Redis exactly and is the documented setting for a drop in cache.
152const MAXSTORE: &str = "maxstore";
153
154/// Where the server writes, which `BACKUP LIST` answers paths under.
155///
156/// On its own for a fourth reason: it is readable and not writable, and it is
157/// not writable in a way of its own. Redis calls it a protected config, which
158/// means `CONFIG SET dir` is refused with a sentence about protection rather
159/// than about immutability unless the server was started with protected configs
160/// enabled. That distinction is copied, because the two messages are what an
161/// operator reads when a `CONFIG SET` does not take.
162const DIR: &str = "dir";
163
164/// What `SAVE` writes, under [`DIR`].
165///
166/// Protected in the same way and for a weaker version of the same reason: a
167/// server whose file name moved under a running backup script leaves a file
168/// nothing goes looking for. Redis protects it too, so `CONFIG SET dbfilename`
169/// is refused there as well without protected configs turned on.
170const DBFILENAME: &str = "dbfilename";
171
172/// The password every connection is asked for, empty when none is.
173///
174/// Writable, and the one setting here whose value is a secret. It reads back in
175/// the clear, which is what a real server does and is not an oversight of one:
176/// an operator who can send `CONFIG GET` on this server can already read
177/// everything in it.
178const REQUIREPASS: &str = "requirepass";
179
180/// How long a sealed backup is kept before it cleans itself up.
181///
182/// Seconds, and zero is the default and means it is kept until somebody says
183/// `BACKUP CLEANUP`. Writable, since a backup taken by a script that then died
184/// is exactly the thing this is for and setting it afterwards has to work.
185const SEALED_TTL: &str = "backup-sealed-ttl";
186
187/// Which classes of keyspace change are published, and on which two channels.
188///
189/// On its own for a fifth reason: it is the only setting whose value is neither
190/// a number nor one of a fixed list of words, but a set of characters that reads
191/// back in a different spelling from the one it was written in. `CONFIG SET
192/// notify-keyspace-events KEA` reads back as `AKE`. See the `notify` module for
193/// what each character means and why the order is what it is.
194const NOTIFY: &str = "notify-keyspace-events";
195
196/// Read a byte count the way `CONFIG SET maxmemory` reads one.
197///
198/// This is Redis's `memtoull`. Digits, then an optional unit that is not case
199/// sensitive: nothing or `b` is bytes, `k` is a thousand and `kb` is a kibibyte,
200/// and the same pairing again for `m` and `g`. The two spellings meaning
201/// different numbers is a trap and it is Redis's trap, so it is repeated here
202/// rather than tidied up.
203///
204/// A unit that overflows clamps rather than failing, which is upstream's
205/// `ULLONG_MAX` arm. There is no sign: a leading minus is refused before the
206/// digits are read, so `maxmemory -1` is not a very large number.
207///
208/// Public because `yodb serve` takes the same limits on the command line that
209/// `CONFIG SET` takes at runtime, and a server that accepts `100mb` from one and
210/// not the other, or reads it as a different number, is a server that gets
211/// misconfigured. One parser, one answer.
212#[must_use]
213pub fn parse_memory(value: &[u8]) -> Option<u64> {
214    let split = value
215        .iter()
216        .position(|b| !b.is_ascii_digit())
217        .unwrap_or(value.len());
218    let (digits, unit) = value.split_at(split);
219    if digits.is_empty() {
220        return None;
221    }
222    let mul: u64 = match unit {
223        [] => 1,
224        u if u.eq_ignore_ascii_case(b"b") => 1,
225        u if u.eq_ignore_ascii_case(b"k") => 1000,
226        u if u.eq_ignore_ascii_case(b"kb") => 1024,
227        u if u.eq_ignore_ascii_case(b"m") => 1000 * 1000,
228        u if u.eq_ignore_ascii_case(b"mb") => 1024 * 1024,
229        u if u.eq_ignore_ascii_case(b"g") => 1000 * 1000 * 1000,
230        u if u.eq_ignore_ascii_case(b"gb") => 1024 * 1024 * 1024,
231        _ => return None,
232    };
233    let mut n: u64 = 0;
234    for d in digits {
235        n = n.saturating_mul(10).saturating_add(u64::from(d - b'0'));
236    }
237    Some(n.saturating_mul(mul))
238}
239
240/// Every policy name, joined the way `CONFIG SET` lists them when it refuses one.
241///
242/// This is a formatter and not a string because the error path should not touch
243/// the allocator, and it walks [`Policy::ALL`] rather than spelling the ten names
244/// out again so the two cannot drift apart. The order is the order in Redis's own
245/// enum table, which is the whole reason `Policy::ALL` is written down.
246struct PolicyNames;
247
248impl core::fmt::Display for PolicyNames {
249    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
250        for (at, policy) in Policy::ALL.iter().enumerate() {
251            if at > 0 {
252                f.write_str(", ")?;
253            }
254            f.write_str(policy.name())?;
255        }
256        Ok(())
257    }
258}
259
260/// Run one connection or server command.
261pub(super) fn execute(
262    server: &Server,
263    session: &mut Session,
264    spec: &Spec,
265    args: Args<'_>,
266    out: &mut Out,
267) -> Result<Flow> {
268    match spec.name {
269        // The arity in the table is a minimum of one, and a real server then
270        // refuses a second argument as a wrong number of them.
271        "ping" => {
272            if args.len() > 2 {
273                return Err(args::wrong_arity("ping"));
274            }
275            // A RESP2 connection in subscribe mode is answered a two element
276            // array with `pong` in front, so that everything reaching a
277            // subscribed client on RESP2 has the same shape. The one place a
278            // command in this file cares what the connection has subscribed to.
279            if super::pubsub::ping(session, args, out) {
280                return Ok(Flow::Continue);
281            }
282            if args.len() == 2 {
283                out.bulk(args.get(1));
284            } else {
285                out.simple(b"PONG");
286            }
287        }
288        "echo" => out.bulk(args.get(1)),
289        "auth" => auth::execute(server, session, args, out)?,
290        "debug" => debug::execute(server, session, args, out)?,
291        "hello" => hello(server, session, args, out)?,
292        "select" => {
293            let n = args.int(1)?;
294            let ok = usize::try_from(n).is_ok_and(|n| n < DATABASES);
295            if !ok {
296                return Err(Error::new(Code::Invalid, "DB index is out of range"));
297            }
298            session.db = n as usize;
299            out.ok();
300        }
301        "reset" => {
302            // Everything a connection carries goes back to what it was when it
303            // was opened, and that includes the protocol: a connection that
304            // said `HELLO 3` is speaking RESP2 again after this.
305            //
306            // The transaction and the watches go first because letting go of a
307            // watch is a change to the server and not to the connection, so
308            // clearing the list here without saying so would leave rows on the
309            // server that nobody is watching. `RESET` inside `MULTI` answers
310            // `+RESET` and leaves no transaction, which is why it is one of the
311            // six commands a transaction does not queue.
312            // The subscriptions go with them, and for the same reason: a
313            // subscription is a row on the server naming this connection, so
314            // clearing the connection's list alone would leave the server
315            // delivering into a slot that is not listening any more.
316            // And the monitor with them, which is the one way out of monitor
317            // mode short of closing the socket. `RESET` still answers `+RESET`
318            // on a connection that was one, because the reply belongs to the
319            // client the connection has just gone back to being.
320            multi::release(server, session);
321            super::pubsub::release(server, session);
322            if session.monitoring() {
323                server.watch_no_more(session.row());
324            }
325            session.reset();
326            // Including the password, which is what `RESET` means by putting
327            // the connection back the way it was accepted: on a server with a
328            // password the client has to send `AUTH` again, and on a server
329            // without one it never had to.
330            session.admit(!server.guarded());
331            out.set_proto(Proto::Resp2);
332            out.simple(b"RESET");
333        }
334        // The reply goes out before the socket closes, which is why this is a
335        // flow answer and not something the body does to the connection.
336        "quit" => {
337            out.ok();
338            return Ok(Flow::Close);
339        }
340        // Every command on the server, from here on, on this connection. The
341        // reply is `OK` once and nothing after it, and a connection that sends
342        // it twice is answered nothing at all the second time, which is a real
343        // server's behaviour and not an oversight of one.
344        "monitor" => {
345            // A transaction replaying this has been promised a reply for every
346            // command it queued, and a connection that has turned into a feed
347            // cannot give one. A real server refuses it in the same words.
348            if session.running() {
349                return Err(Error::new(
350                    Code::Invalid,
351                    "MONITOR isn't allowed for DENY BLOCKING client",
352                ));
353            }
354            if server.watch_all(session.row()) {
355                out.ok();
356            }
357        }
358        "client" => return super::client::execute(server, session, spec, args, out),
359        "command" => command(args, out)?,
360        "config" => config(server, args, out)?,
361        "info" => info(server, args, out),
362        // A key that is past its deadline and has not been read since is still
363        // counted, which is what Redis does too: `DBSIZE` is the size of the
364        // dictionary and not a walk over it. Redis has an active expiry cycle
365        // that takes those keys out within a tick or so and we do not yet, so
366        // the two servers disagree for as long as a dead key sits unread. That
367        // gap closes with the maintenance slice rather than with a count here,
368        // because a count here would be O(N) on a command that is O(1)
369        // everywhere else.
370        "dbsize" => out.int(server.dbs[session.db].len() as i64),
371        "flushall" => {
372            flush_mode(args)?;
373            for db in &server.dbs {
374                db.clear();
375            }
376            server.search.lock().clear();
377            server.cursors.lock().wipe();
378            out.ok();
379        }
380        // The search indexes go too, and they go whichever database this is.
381        // An index that only ever followed keys on database zero is dropped by
382        // a `FLUSHDB` on database nine, which is measured against a real server
383        // rather than reasoned about: the module hangs its callback on the
384        // flush event without looking at which database flushed.
385        "flushdb" => {
386            flush_mode(args)?;
387            server.dbs[session.db].clear();
388            server.search.lock().clear();
389            server.cursors.lock().wipe();
390            out.ok();
391        }
392        // Two databases change places and no key moves. What is in the stripes
393        // is exchanged and the databases stay where they are, so this costs two
394        // pointer sized writes per stripe whatever is in either of them, which
395        // is what makes `SWAPDB` fast and dangerous at the same time.
396        //
397        // No connection is told. A client on database zero is still on database
398        // zero and is now looking at what used to be database one, which is the
399        // whole point of the command and is why Redis calls it dangerous. A
400        // client parked in `BLPOP` remembers the database index it blocked on
401        // and not the database, so it wakes up against the swapped in one, which
402        // is Redis's behaviour and falls out of the index being what is stored.
403        "swapdb" => {
404            let first = db_index(args.get(1), "invalid first DB index")?;
405            let second = db_index(args.get(2), "invalid second DB index")?;
406            server.striped(first).swap_with(server.striped(second));
407            out.ok();
408        }
409        "time" => time(out),
410        // The four commands about writing the dataset to a file and the one
411        // about who this server is, all in the `persist` module because a client
412        // asks them together.
413        "save" | "bgsave" | "bgrewriteaof" | "lastsave" | "role" => {
414            persist::execute(server, session, spec, args, out)?;
415        }
416        "backup" => backup::execute(server, args, out)?,
417        "shutdown" => return shutdown(server, args),
418        _ => return Err(args::unknown_command(args)),
419    }
420    Ok(Flow::Continue)
421}
422
423/// `TIME`, which is two bulk strings and not one integer.
424///
425/// Seconds first and then microseconds within that second, both written out as
426/// decimal text, which is a shape nobody would choose today and is the shape
427/// every client library parses.
428///
429/// It reads the wall clock rather than the coarse clock the keyspace uses. The
430/// coarse one is a cached millisecond that a background tick refreshes, which is
431/// the right trade for deciding whether a key has expired and the wrong one for
432/// a command whose entire job is to say what time it is. A client that calls
433/// `TIME` twice in a row and gets the same microsecond has been lied to.
434fn time(out: &mut Out) {
435    let now = SystemTime::now()
436        .duration_since(UNIX_EPOCH)
437        .unwrap_or_default();
438    out.array(2);
439    out.bulk(now.as_secs().to_string().as_bytes());
440    out.bulk(now.subsec_micros().to_string().as_bytes());
441}
442
443// ---------------------------------------------------------------- SHUTDOWN
444
445/// `SHUTDOWN [NOSAVE | SAVE] [NOW] [FORCE] [ABORT]`.
446///
447/// On success this writes nothing at all and the connection closes under the
448/// client, which is what a server that has stopped looks like from the outside
449/// and is what every client library already expects. There is no `OK`, because
450/// an `OK` would be a promise made by a process that is about to not exist.
451///
452/// `SAVE` writes the file [`persist`] writes, and it is the only word here that
453/// does anything. `NOSAVE` is the default rather than an instruction, which is
454/// the same answer `save` gets from `CONFIG GET`: this server has no save points
455/// and never will, because what durability there is belongs to the file
456/// underneath and is already on disk by the time a command returns. So there is
457/// nothing for `NOSAVE` to skip and the file `SAVE` asks for is an export
458/// somebody wants a copy of on the way down. `NOW` and `FORCE` are about not
459/// waiting for replicas and about going anyway when a save failed, and neither
460/// has anything to wait for or to fail here.
461///
462/// # Errors
463///
464/// [`Code::Invalid`] for a word that is not one of the five, for `SAVE` and
465/// `NOSAVE` in the same call, and for `ABORT` alongside any other flag, all of
466/// which is what 8.10.1 says. `ABORT` on its own gets Redis's message for a
467/// cancel with nothing to cancel, and here that is not a state that can be
468/// reached rather than one that happens to be empty: a shutdown is decided and
469/// done inside one turn of the loop, so there is never a window in which one is
470/// in progress and a second client could call it off.
471fn shutdown(server: &Server, args: Args<'_>) -> Result<Flow> {
472    let (mut save, mut nosave, mut abort, mut other) = (false, false, false, false);
473    for at in 1..args.len() {
474        let arg = args.get(at);
475        match () {
476            () if is(arg, b"save") => save = true,
477            () if is(arg, b"nosave") => nosave = true,
478            () if is(arg, b"abort") => abort = true,
479            () if is(arg, b"now") || is(arg, b"force") => other = true,
480            () => return Err(args::syntax()),
481        }
482    }
483    // Repeating one is fine and contradicting yourself is not, and `ABORT` says
484    // to do nothing so it cannot be combined with a word about how to do it.
485    if (save && nosave) || (abort && (save || nosave || other)) {
486        return Err(args::syntax());
487    }
488    if abort {
489        return Err(Error::new(Code::Invalid, "No shutdown in progress."));
490    }
491    if save {
492        persist::on_shutdown(server);
493    }
494    server.stop();
495    // Closing is what stops anything the client pipelined behind this from
496    // being answered by a server that is on its way out.
497    Ok(Flow::Close)
498}
499
500// ------------------------------------------------------------------- FLUSH
501
502/// Check the optional `ASYNC` or `SYNC` on `FLUSHALL` and `FLUSHDB`.
503///
504/// Both are accepted and neither changes anything. On a real server the choice
505/// is whether the freeing happens on the connection's thread or on the lazy
506/// free thread, and either way the keyspace is empty before the `OK` goes out.
507/// That is the whole of what a client can observe, and it is the same here,
508/// so taking the word and ignoring it is answering the question rather than
509/// pretending to.
510///
511/// # Errors
512///
513/// [`Code::Invalid`] for a third argument, or for a second that is neither
514/// word, which is what Redis says about both.
515fn flush_mode(args: Args<'_>) -> Result<()> {
516    if args.len() == 1 {
517        return Ok(());
518    }
519    if args.len() > 2 || !(is(args.get(1), b"async") || is(args.get(1), b"sync")) {
520        return Err(args::syntax());
521    }
522    Ok(())
523}
524
525/// One of `SWAPDB`'s two database indexes, with Redis's two different
526/// complaints about it.
527///
528/// A word that is not a number, or a number too big to be a database index on a
529/// server that stores the index in a C `int`, gets the caller's message, which
530/// says which of the two arguments was wrong. A number that is a plausible index
531/// and is not one of ours gets the same out of range message `SELECT` gives. The
532/// split looks arbitrary and it is Redis's, and the reason for it is that the
533/// first check happens while reading the argument and the second happens inside
534/// the swap, so only the first one knows which argument it was looking at.
535fn db_index(arg: &[u8], bad: &'static str) -> Result<usize> {
536    let n = parse_i64(arg)
537        .filter(|n| i32::try_from(*n).is_ok())
538        .ok_or_else(|| Error::new(Code::Invalid, bad))?;
539    usize::try_from(n)
540        .ok()
541        .filter(|n| *n < DATABASES)
542        .ok_or_else(|| Error::new(Code::Invalid, "DB index is out of range"))
543}
544
545// ------------------------------------------------------------------- HELLO
546
547/// `HELLO [protover [AUTH username password] [SETNAME name]]`.
548///
549/// The order of the three things that can go wrong here is the reference's and
550/// is worth writing down, because it is not the order they appear in. The
551/// protocol version is read and refused first, so `HELLO 9 AUTH default right`
552/// on a connection that has not authenticated is a `NOPROTO` and leaves the
553/// connection unauthenticated. The `AUTH` option is applied next, so a wrong
554/// password is a `WRONGPASS` and the protocol stays where it was. Only then does
555/// the connection have to be authenticated at all, which is what makes a bare
556/// `HELLO` on a server with a password a `NOAUTH` rather than a greeting.
557fn hello(server: &Server, session: &mut Session, args: Args<'_>, out: &mut Out) -> Result<()> {
558    // The version this call agreed on, if it named one. Held rather than applied
559    // where it is read, because the reply buffer must not change protocol until
560    // the password below has been asked for and answered.
561    let mut agreed = None;
562    if args.len() > 1 {
563        let v = parse_i64(args.get(1)).ok_or_else(|| {
564            Error::new(
565                Code::Invalid,
566                "Protocol version is not an integer or out of range",
567            )
568        })?;
569        let Some(proto) = Proto::from_version(v) else {
570            // `NOPROTO` rather than `ERR`, and it is the one error in this file
571            // written straight into the buffer: the prefix is part of what the
572            // client branches on, and it is the only place in the engine that
573            // needs this one.
574            out.error(b"NOPROTO unsupported protocol version");
575            return Ok(());
576        };
577        let mut i = 2;
578        while i < args.len() {
579            let o = args.get(i);
580            if is(o, b"AUTH") && i + 2 < args.len() {
581                if !auth::admits(server, args.get(i + 1), args.get(i + 2)) {
582                    out.error(b"WRONGPASS invalid username-password pair or user is disabled.");
583                    return Ok(());
584                }
585                session.admit(true);
586                i += 3;
587            } else if is(o, b"SETNAME") && i + 1 < args.len() {
588                session.set_name(args.get(i + 1));
589                i += 2;
590            } else {
591                return Err(yo_alloc::allow(|| {
592                    Error::fmt(
593                        Code::Invalid,
594                        format_args!(
595                            "Syntax error in HELLO option '{}'",
596                            String::from_utf8_lossy(o)
597                        ),
598                    )
599                }));
600            }
601        }
602        agreed = Some(proto);
603    }
604
605    if server.guarded() && !session.authenticated() {
606        // Its own sentence rather than the one every other command gets, because
607        // a client that speaks RESP3 has to send `HELLO` before it can send
608        // `AUTH` and would otherwise be told to do the thing it is doing.
609        out.error(auth::HELLO_NOAUTH.as_bytes());
610        return Ok(());
611    }
612    // The reply is written in the protocol that was just agreed, not the one the
613    // request arrived in.
614    if let Some(proto) = agreed {
615        out.set_proto(proto);
616    }
617
618    let proto = out.proto().version();
619    out.map(7);
620    out.bulk(b"server");
621    out.bulk(REPORTED_SERVER.as_bytes());
622    out.bulk(b"version");
623    out.bulk(REPORTED_VERSION.as_bytes());
624    out.bulk(b"proto");
625    out.int(proto);
626    out.bulk(b"id");
627    out.int(session.id as i64);
628    out.bulk(b"mode");
629    out.bulk(b"standalone");
630    out.bulk(b"role");
631    out.bulk(b"master");
632    out.bulk(b"modules");
633    out.array(0);
634    Ok(())
635}
636
637// ----------------------------------------------------------------- COMMAND
638
639/// `COMMAND [COUNT|LIST|INFO|DOCS|GETKEYS|HELP]`.
640fn command(args: Args<'_>, out: &mut Out) -> Result<()> {
641    if args.len() == 1 {
642        out.array(table::COMMANDS.len());
643        for spec in table::COMMANDS {
644            write_spec(out, spec);
645        }
646        return Ok(());
647    }
648    let sub = args.get(1);
649    if is(sub, b"COUNT") {
650        out.int(table::COMMANDS.len() as i64);
651    } else if is(sub, b"INFO") {
652        if args.len() == 2 {
653            out.array(table::COMMANDS.len());
654            for spec in table::COMMANDS {
655                write_spec(out, spec);
656            }
657        } else {
658            out.array(args.len() - 2);
659            for i in 2..args.len() {
660                match table::lookup(args.get(i)) {
661                    Some(spec) => write_spec(out, spec),
662                    // A name nobody has heard of is a null in the list rather
663                    // than an error, so one bad name in a batch does not cost
664                    // the client the other answers. It is the plain null and
665                    // not the array one, which on RESP2 is the difference
666                    // between `$-1` and `*-1` and is what a real server sends.
667                    None => out.nil(),
668                }
669            }
670        }
671    } else if is(sub, b"LIST") {
672        list(args, out)?;
673    } else if is(sub, b"DOCS") {
674        docs(args, out);
675    } else if is(sub, b"GETKEYS") {
676        getkeys(args, out)?;
677    } else if is(sub, b"HELP") {
678        help(out, COMMAND_HELP);
679    } else {
680        return Err(args::unknown_subcommand(sub, "COMMAND"));
681    }
682    Ok(())
683}
684
685/// `COMMAND LIST [FILTERBY MODULE m|ACLCAT c|PATTERN p]`.
686fn list(args: Args<'_>, out: &mut Out) -> Result<()> {
687    if args.len() == 2 {
688        out.array(table::COMMANDS.len());
689        for spec in table::COMMANDS {
690            out.bulk(spec.name.as_bytes());
691        }
692        return Ok(());
693    }
694    if args.len() != 5 || !is(args.get(2), b"FILTERBY") {
695        return Err(args::syntax());
696    }
697    let (how, what) = (args.get(3), args.get(4));
698    let keep = |spec: &Spec| {
699        if is(how, b"MODULE") {
700            // Nothing here came from a module, so every filter by one is empty.
701            false
702        } else if is(how, b"ACLCAT") {
703            spec.acl
704                .iter()
705                .any(|c| c.len() == what.len() + 1 && c.as_bytes()[1..].eq_ignore_ascii_case(what))
706        } else {
707            glob::matches(what, spec.name.as_bytes())
708        }
709    };
710    if !is(how, b"MODULE") && !is(how, b"ACLCAT") && !is(how, b"PATTERN") {
711        return Err(args::syntax());
712    }
713    out.array(table::COMMANDS.iter().filter(|s| keep(s)).count());
714    for spec in table::COMMANDS.iter().filter(|s| keep(s)) {
715        out.bulk(spec.name.as_bytes());
716    }
717    Ok(())
718}
719
720/// `COMMAND DOCS [name ...]`.
721///
722/// The arguments field a real server sends is left out. It describes the shape
723/// of every option of every command in a form nothing but `redis-cli`'s hinting
724/// reads, and getting it wrong would be worse than not sending it, since a
725/// client that finds the field trusts it.
726fn docs(args: Args<'_>, out: &mut Out) {
727    if args.len() == 2 {
728        out.map(table::COMMANDS.len());
729        for spec in table::COMMANDS {
730            write_docs(out, spec);
731        }
732        return;
733    }
734    let found = (2..args.len())
735        .filter(|&i| table::lookup(args.get(i)).is_some())
736        .count();
737    out.map(found);
738    for i in 2..args.len() {
739        if let Some(spec) = table::lookup(args.get(i)) {
740            write_docs(out, spec);
741        }
742    }
743}
744
745/// One command's documentation, as the name and then the map about it.
746fn write_docs(out: &mut Out, spec: &Spec) {
747    out.bulk(spec.name.as_bytes());
748    out.map(4);
749    out.bulk(b"summary");
750    out.bulk(spec.summary.as_bytes());
751    out.bulk(b"since");
752    out.bulk(spec.since.as_bytes());
753    out.bulk(b"group");
754    out.bulk(spec.group.as_bytes());
755    out.bulk(b"complexity");
756    out.bulk(spec.complexity.as_bytes());
757}
758
759/// `COMMAND GETKEYS <full command>`.
760///
761/// This is how a cluster aware client routes a command it does not have a rule
762/// for, so a wrong answer here is a client that sends a write to the wrong
763/// node. The generic path is the first, last and step triple from the table.
764fn getkeys(args: Args<'_>, out: &mut Out) -> Result<()> {
765    if args.len() < 3 {
766        return Err(args::wrong_arity_sub("command", "getkeys"));
767    }
768    let inner = args.get(2);
769    let spec = table::lookup(inner)
770        .ok_or_else(|| Error::new(Code::Unsupported, "Invalid command specified"))?;
771    let argc = args.len() - 2;
772    if !table::arity_ok(spec, argc) {
773        return Err(Error::new(
774            Code::Invalid,
775            "Invalid number of arguments specified for command",
776        ));
777    }
778    // Where the keys are is the same question `WATCH` asks about a command that
779    // has just run, so the answer is worked out in one place and the two
780    // callers differ only in how many arguments sit in front of the command.
781    let span = table::key_span(spec, args, 2).map_err(|why| match why {
782        table::NoKeys::Never => Error::new(Code::Invalid, "The command has no key arguments"),
783        table::NoKeys::BadCount => {
784            Error::new(Code::Invalid, "Invalid arguments specified for command")
785        }
786    })?;
787    out.array(span.count);
788    for i in 0..span.count {
789        out.bulk(args.get(span.first + i * span.step));
790    }
791    Ok(())
792}
793
794/// One command, in the ten field shape `COMMAND INFO` has had since 7.0.
795///
796/// The tips, the key specs and the subcommands are all empty. The triple above
797/// them says where the keys are for everything in this table except `MSETEX`,
798/// `TS.NRANGE` and `TS.NREVRANGE`, which is what `COMMAND GETKEYS` is for, and
799/// divergence D-13 says so.
800///
801/// Five of the ten fields are sets rather than arrays, which only shows on
802/// RESP3 and shows there on every command. A set is what the reference sends
803/// for all five, and it is the honest type for them: nothing in a flag list or
804/// an acl category list is ordered or repeated.
805fn write_spec(out: &mut Out, spec: &Spec) {
806    out.array(10);
807    out.bulk(spec.name.as_bytes());
808    out.int(i64::from(spec.arity));
809    out.set(spec.flags.len());
810    for f in spec.flags {
811        out.simple(f.as_bytes());
812    }
813    out.int(i64::from(spec.first_key));
814    out.int(i64::from(spec.last_key));
815    out.int(i64::from(spec.step));
816    out.set(spec.acl.len());
817    for a in spec.acl {
818        out.simple(a.as_bytes());
819    }
820    out.set(0);
821    out.set(0);
822    out.set(0);
823}
824
825// ------------------------------------------------------------------ CONFIG
826
827/// What a ladder setting is set to now.
828fn read_knob(db: &Keyspace, knob: Knob) -> usize {
829    match knob {
830        Knob::SetIntsetEntries => db.limits().max_intset_entries,
831        Knob::SetListpackEntries => db.limits().max_listpack_entries,
832        Knob::SetListpackValue => db.limits().max_listpack_value,
833        Knob::HashListpackEntries => db.hash_limits().max_listpack_entries,
834        Knob::HashListpackValue => db.hash_limits().max_listpack_value,
835        Knob::MaxmemorySamples => db.samples(),
836        Knob::LfuLogFactor => db.lfu().log_factor as usize,
837        Knob::LfuDecayTime => db.lfu().decay_minutes as usize,
838    }
839}
840
841/// Move one ladder setting on one database.
842fn write_knob(db: &mut Keyspace, knob: Knob, n: usize) {
843    let mut set = *db.limits();
844    let mut hash = *db.hash_limits();
845    let mut lfu = db.lfu();
846    match knob {
847        Knob::SetIntsetEntries => set.max_intset_entries = n,
848        Knob::SetListpackEntries => set.max_listpack_entries = n,
849        Knob::SetListpackValue => set.max_listpack_value = n,
850        Knob::HashListpackEntries => hash.max_listpack_entries = n,
851        Knob::HashListpackValue => hash.max_listpack_value = n,
852        Knob::MaxmemorySamples => db.set_samples(n),
853        // Saturating rather than wrapping, because these two are read as `u32`
854        // and a client is free to send a number that does not fit. Redis clamps
855        // `lfu-log-factor` and `lfu-decay-time` to the same width.
856        Knob::LfuLogFactor => lfu.log_factor = u32::try_from(n).unwrap_or(u32::MAX),
857        Knob::LfuDecayTime => lfu.decay_minutes = u32::try_from(n).unwrap_or(u32::MAX),
858    }
859    db.set_limits(set);
860    db.set_hash_limits(hash);
861    db.set_lfu(lfu);
862}
863
864/// The two things a real server says about a number it will not take.
865///
866/// Both name the setting the client typed and not the one it is an alias for,
867/// so `hash-max-ziplist-entries` comes back saying `hash-max-ziplist-entries`.
868/// A value past the range of an `i64` is the parse complaint and not the range
869/// one, which is upstream reading it before it checks it.
870fn bad_setting(name: &str, parsed: bool) -> Error {
871    if parsed {
872        Error::fmt(
873            Code::Invalid,
874            format_args!(
875                "CONFIG SET failed (possibly related to argument '{name}') - argument must be between 0 and 9223372036854775807 inclusive"
876            ),
877        )
878    } else {
879        Error::fmt(
880            Code::Invalid,
881            format_args!(
882                "CONFIG SET failed (possibly related to argument '{name}') - argument couldn't be parsed into an integer"
883            ),
884        )
885    }
886}
887
888/// `CONFIG GET|SET|RESETSTAT|REWRITE|HELP`.
889fn config(server: &Server, args: Args<'_>, out: &mut Out) -> Result<()> {
890    let sub = args.get(1);
891    if is(sub, b"GET") {
892        if args.len() < 3 {
893            return Err(args::wrong_arity_sub("config", "get"));
894        }
895        let wanted =
896            |name: &str| (2..args.len()).any(|i| glob::matches(args.get(i), name.as_bytes()));
897        // A setting that two patterns both ask for is sent once, which is what
898        // makes this a count of settings rather than a count of matches. The
899        // two spellings of a ladder setting are two settings by that rule, so
900        // `CONFIG GET hash-max-*` sends the listpack name and the ziplist name
901        // and the same number under both, which is what a real server does.
902        let fixed = SETTINGS.iter().filter(|(k, _)| wanted(k));
903        let ladder = LADDER.iter().filter(|(k, _)| wanted(k));
904        let policy = wanted(MAXMEMORY_POLICY);
905        let limit = wanted(MAXMEMORY);
906        let store = wanted(MAXSTORE);
907        let where_ = wanted(DIR);
908        let file = wanted(DBFILENAME);
909        let pass = wanted(REQUIREPASS);
910        let ttl = wanted(SEALED_TTL);
911        let events = wanted(NOTIFY);
912        out.map(
913            fixed.clone().count()
914                + ladder.clone().count()
915                + usize::from(policy)
916                + usize::from(limit)
917                + usize::from(store)
918                + usize::from(where_)
919                + usize::from(file)
920                + usize::from(pass)
921                + usize::from(ttl)
922                + usize::from(events),
923        );
924        for (k, v) in fixed {
925            out.bulk(k.as_bytes());
926            out.bulk(v.as_bytes());
927        }
928        for (k, knob) in ladder {
929            out.bulk(k.as_bytes());
930            out.bulk_int(read_knob(&server.settings(), *knob) as i64);
931        }
932        if policy {
933            out.bulk(MAXMEMORY_POLICY.as_bytes());
934            out.bulk(server.settings().policy().name().as_bytes());
935        }
936        if limit {
937            // Back as a plain number of bytes whatever the client typed to set
938            // it, which is what a real server does: `CONFIG SET maxmemory 1gb`
939            // reads back as 1073741824.
940            out.bulk(MAXMEMORY.as_bytes());
941            out.bulk_int(server.maxmemory() as i64);
942        }
943        if store {
944            // Minus one for no limit, and a plain number of bytes otherwise.
945            // Zero cannot mean no limit here the way it does for `maxmemory`,
946            // because zero is the setting that says the file holds nothing.
947            out.bulk(MAXSTORE.as_bytes());
948            out.bulk_int(server.maxstore().map_or(-1, |n| n as i64));
949        }
950        if where_ {
951            // Absolute, which is what a real server answers too: it resolves the
952            // directory at startup and reports the resolved one, so a client can
953            // tell where the files are without knowing where the process was
954            // launched from.
955            out.bulk(DIR.as_bytes());
956            yo_alloc::allow(|| out.bulk(server.dir().to_string_lossy().as_bytes()));
957        }
958        if file {
959            // The name on its own and not the path, which is how a real server
960            // answers it too: the two settings are joined by whoever reads them.
961            out.bulk(DBFILENAME.as_bytes());
962            out.bulk(persist::FILE.as_bytes());
963        }
964        if pass {
965            out.bulk(REQUIREPASS.as_bytes());
966            server.with_password(|p| out.bulk(p));
967        }
968        if ttl {
969            out.bulk(SEALED_TTL.as_bytes());
970            out.bulk_int(server.backup().ttl() as i64);
971        }
972        if events {
973            // The flags and not the string that set them, which is what a real
974            // server answers too and is why the parser has a formatter next to
975            // it rather than the text being kept.
976            out.bulk(NOTIFY.as_bytes());
977            let (buf, len) = notify::format(server.notify_flags());
978            out.bulk(&buf[..len]);
979        }
980    } else if is(sub, b"SET") {
981        // Too few is a wrong number of arguments and an odd number is a syntax
982        // error, which is not the same sentence and is not the same rule. A
983        // real server counts the pairs after it has decided there is at least
984        // one, so `CONFIG SET appendonly` is an arity error and `CONFIG SET
985        // appendonly no maxmemory` is a syntax one.
986        if args.len() < 4 {
987            return Err(args::wrong_arity_sub("config", "set"));
988        }
989        if !args.len().is_multiple_of(2) {
990            return Err(args::syntax());
991        }
992        // Every pair is checked before any of them is applied, because a real
993        // server takes the whole `CONFIG SET` or none of it. `CONFIG SET
994        // hash-max-listpack-entries 7 set-max-listpack-entries abc` leaves the
995        // hash setting where it was, which was checked rather than assumed.
996        let mut writes = [None; 16];
997        let mut count = 0;
998        let mut policy = None;
999        let mut limit = None;
1000        let mut store = None;
1001        let mut ttl = None;
1002        let mut events = None;
1003        let mut password = None;
1004        let mut i = 2;
1005        while i < args.len() {
1006            let (name, value) = (args.get(i), args.get(i + 1));
1007            i += 2;
1008            if is(name, MAXMEMORY.as_bytes()) {
1009                let Some(bytes) = parse_memory(value) else {
1010                    return Err(Error::fmt(
1011                        Code::Invalid,
1012                        format_args!(
1013                            "CONFIG SET failed (possibly related to argument '{MAXMEMORY}') - argument must be a memory value"
1014                        ),
1015                    ));
1016                };
1017                limit = Some(bytes);
1018                continue;
1019            }
1020            if is(name, MAXSTORE.as_bytes()) {
1021                // `-1` before the memory parser sees it, because that parser
1022                // refuses a sign and should keep refusing one: `maxmemory -1`
1023                // is not a very large number and never was.
1024                let parsed = if value == b"-1" {
1025                    Some(None)
1026                } else {
1027                    parse_memory(value).map(Some)
1028                };
1029                let Some(bytes) = parsed else {
1030                    return Err(Error::fmt(
1031                        Code::Invalid,
1032                        format_args!(
1033                            "CONFIG SET failed (possibly related to argument '{MAXSTORE}') - argument must be a memory value or -1"
1034                        ),
1035                    ));
1036                };
1037                store = Some(bytes);
1038                continue;
1039            }
1040            if is(name, MAXMEMORY_POLICY.as_bytes()) {
1041                // Named twice in one command, the last one wins, which is the
1042                // same rule the ladder settings follow and is what a real server
1043                // does with any setting repeated in a single `CONFIG SET`.
1044                let Some(p) = Policy::parse(value) else {
1045                    return Err(Error::fmt(
1046                        Code::Invalid,
1047                        format_args!(
1048                            "CONFIG SET failed (possibly related to argument '{MAXMEMORY_POLICY}') - argument(s) must be one of the following: {PolicyNames}"
1049                        ),
1050                    ));
1051                };
1052                policy = Some(p);
1053                continue;
1054            }
1055            // Refused whatever the value is, including the one they are already
1056            // set to, which is the one place a setting here does not take the
1057            // write that changes nothing. That is the reference's answer: a
1058            // protected config is refused before anybody looks at what was
1059            // asked for.
1060            if let Some(protected) = [DIR, DBFILENAME]
1061                .into_iter()
1062                .find(|p| is(name, p.as_bytes()))
1063            {
1064                return Err(Error::fmt(
1065                    Code::Unsupported,
1066                    format_args!(
1067                        "CONFIG SET failed (possibly related to argument '{protected}') - can't set protected config"
1068                    ),
1069                ));
1070            }
1071            if is(name, REQUIREPASS.as_bytes()) {
1072                // Anything at all is a password, including an empty one, which
1073                // is how a password is taken off again. There is nothing to
1074                // refuse here and a real server refuses nothing either.
1075                password = Some(value);
1076                continue;
1077            }
1078            if is(name, NOTIFY.as_bytes()) {
1079                // The only setting here whose error names what was wrong with
1080                // the value rather than what the value should have been, and it
1081                // quotes the accepted characters in the reference's order.
1082                let Some(flags) = notify::parse(value) else {
1083                    return Err(Error::fmt(
1084                        Code::Invalid,
1085                        format_args!(
1086                            "CONFIG SET failed (possibly related to argument '{NOTIFY}') - Invalid event class character. Use '{}'.",
1087                            notify::ACCEPTED
1088                        ),
1089                    ));
1090                };
1091                events = Some(flags);
1092                continue;
1093            }
1094            if is(name, SEALED_TTL.as_bytes()) {
1095                let Some(n) = parse_i64(value).filter(|&n| n >= 0) else {
1096                    return Err(bad_setting(SEALED_TTL, parse_i64(value).is_some()));
1097                };
1098                ttl = Some(n as u64);
1099                continue;
1100            }
1101            if let Some((k, knob)) = LADDER.iter().find(|(k, _)| is(name, k.as_bytes())) {
1102                let Some(n) = parse_i64(value).filter(|&n| n >= 0) else {
1103                    return Err(bad_setting(k, parse_i64(value).is_some()));
1104                };
1105                if count == writes.len() {
1106                    // Sixteen pairs is more than the ten names there are, so
1107                    // getting here means a name was given twice enough times to
1108                    // fill it, and the last one would have won anyway.
1109                    return Err(args::syntax());
1110                }
1111                writes[count] = Some((*knob, n as usize));
1112                count += 1;
1113                continue;
1114            }
1115            let Some((k, v)) = SETTINGS.iter().find(|(k, _)| is(name, k.as_bytes())) else {
1116                return Err(yo_alloc::allow(|| {
1117                    Error::fmt(
1118                        Code::Invalid,
1119                        format_args!(
1120                            "Unknown option or number of arguments for CONFIG SET - '{}'",
1121                            String::from_utf8_lossy(name)
1122                        ),
1123                    )
1124                }));
1125            };
1126            if value != v.as_bytes() {
1127                return Err(Error::fmt(
1128                    Code::Unsupported,
1129                    format_args!(
1130                        "CONFIG SET failed (possibly related to argument '{k}') - can't set immutable config"
1131                    ),
1132                ));
1133            }
1134        }
1135        // Every stripe of every database, because these are one server wide
1136        // number in Redis and the fact that a `Keyspace` carries its own copy is
1137        // ours and not the client's problem. A stripe that missed one would put
1138        // a key in a different shape from the same key on the stripe next to it,
1139        // which `OBJECT ENCODING` would then answer differently for depending on
1140        // where the key happened to land.
1141        // The whole database is held while its stripes are set rather than one
1142        // stripe at a time, for the same reason they all get the same number: a
1143        // client that read `OBJECT ENCODING` in the middle of a half done change
1144        // would be told two different things about two keys depending on nothing
1145        // it can see.
1146        for (knob, n) in writes.iter().flatten() {
1147            for at in 0..DATABASES {
1148                let db = server.striped(at);
1149                let mut held = db.hold_many(0..db.width());
1150                for i in 0..db.width() {
1151                    write_knob(held.stripe_mut(i), *knob, *n);
1152                }
1153            }
1154        }
1155        if let Some(p) = policy {
1156            for at in 0..DATABASES {
1157                let db = server.striped(at);
1158                let mut held = db.hold_many(0..db.width());
1159                for i in 0..db.width() {
1160                    held.stripe_mut(i).set_policy(p);
1161                }
1162            }
1163        }
1164        if let Some(seconds) = ttl {
1165            server.backup().set_ttl(seconds);
1166        }
1167        if let Some(flags) = events {
1168            server.set_notify_flags(flags);
1169        }
1170        if let Some(value) = password {
1171            // The connections that are already open are left where they are,
1172            // including the one that sent this. See the `auth` module for why
1173            // that is the reference's rule and not an accident of it.
1174            server.set_password(value);
1175        }
1176        // Last, so that a `CONFIG SET maxmemory 1mb maxmemory-policy allkeys-lru`
1177        // has the policy in place before the limit that will act on it. The two
1178        // in the other order would run the first eviction under whatever the
1179        // policy used to be, which for a fresh server is `noeviction` and would
1180        // refuse the next write instead of making room for it.
1181        if let Some(bytes) = store {
1182            server.set_maxstore(bytes);
1183        }
1184        if let Some(bytes) = limit {
1185            server.set_maxmemory(bytes);
1186        }
1187        out.ok();
1188    } else if is(sub, b"RESETSTAT") {
1189        server.reset_stats();
1190        out.ok();
1191    } else if is(sub, b"REWRITE") {
1192        return Err(Error::new(
1193            Code::Unsupported,
1194            "The server is running without a config file",
1195        ));
1196    } else if is(sub, b"HELP") {
1197        help(out, CONFIG_HELP);
1198    } else {
1199        return Err(args::unknown_subcommand(sub, "CONFIG"));
1200    }
1201    Ok(())
1202}
1203
1204// -------------------------------------------------------------------- INFO
1205
1206/// `INFO [section ...]`.
1207///
1208/// Every number in here is one this layer can actually answer. There is no
1209/// `rdb_last_save_time` because there is no save, and a field that is not there
1210/// is a client falling back rather than a client believing a zero.
1211///
1212/// The `CPU` section used to be missing for the same reason and is here now,
1213/// because nothing measured it and then something did. It is one `getrusage`
1214/// call in [`super::cpu`], and the reason it went in is that Redis's own
1215/// `unit/info-command` tests fail without it: a monitoring tool graphs
1216/// processor time against wall clock to decide whether a server is busy or
1217/// waiting, so an absent field there is a real hole and not a tidy omission.
1218fn info(server: &Server, args: Args<'_>, out: &mut Out) {
1219    // Redis keeps two lists: the sections a bare `INFO` hands back, and the ones
1220    // that have to be asked for by name or by `all`. `commandstats` is in the
1221    // second, along with `latencystats` and `errorstats`, because they grow with
1222    // the number of distinct commands a server has seen and a monitoring tool
1223    // polling `INFO` every second does not want them.
1224    //
1225    // `unit/info-command` is exactly this distinction written down: it asks for
1226    // `INFO default` and insists `rejected_calls` is not in the answer, then
1227    // asks for `INFO all` and insists that it is.
1228    let named = |section: &str| (1..args.len()).any(|i| is(args.get(i), section.as_bytes()));
1229    let everything = (1..args.len()).any(|i| {
1230        let a = args.get(i);
1231        is(a, b"all") || is(a, b"everything")
1232    });
1233    let by_default = args.len() == 1 || (1..args.len()).any(|i| is(args.get(i), b"default"));
1234    let want = |section: &str| by_default || everything || named(section);
1235    let extra = |section: &str| everything || named(section);
1236    // One string, built once and written once. It allocates, which is allowed
1237    // here and nowhere near the commands that count: `INFO` is a monitoring
1238    // call and it is not on the path M2 is measured on.
1239    let text = yo_alloc::allow(|| {
1240        let mut s = String::with_capacity(1024);
1241        if want("server") {
1242            let _ = write!(
1243                s,
1244                "# Server\r\nredis_version:{REPORTED_VERSION}\r\nyo_version:{}\r\n\
1245                 redis_mode:standalone\r\narch_bits:{}\r\nprocess_id:0\r\n\
1246                 run_id:0000000000000000000000000000000000000000\r\ntcp_port:0\r\n\
1247                 uptime_in_seconds:{}\r\nio_threads_active:0\r\n\r\n",
1248                env!("CARGO_PKG_VERSION"),
1249                usize::BITS,
1250                server.uptime_secs(),
1251            );
1252        }
1253        if want("clients") {
1254            let _ = write!(
1255                s,
1256                "# Clients\r\nconnected_clients:{}\r\nblocked_clients:{}\r\n\
1257                 pubsub_clients:{}\r\ncluster_connections:0\r\n\r\n",
1258                server.totals().clients,
1259                server.parked(),
1260                server.pubsub_counts().clients,
1261            );
1262        }
1263        if want("memory") {
1264            // Both the cap and the quarter of it, because the quarter is an
1265            // empirical number and somebody surprised by it should be able to
1266            // see what it was a quarter of without reading the source. The
1267            // reasoning is written out in `cap`.
1268            let cap = crate::cap::cap();
1269            let compact = server.compaction();
1270            // Read out of its stripe before the write, because an argument list
1271            // keeps every temporary in it alive until the whole call is over
1272            // and one of the other arguments walks that same stripe.
1273            let policy = server.settings().policy().name();
1274            let _ = write!(
1275                s,
1276                "# Memory\r\nused_memory:{}\r\nused_memory_dataset:{}\r\n\
1277                 used_memory_overhead:{}\r\nmem_arena_bytes:{}\r\n\
1278                 mem_arena_segments:{}\r\nmem_compact_walked:{}\r\n\
1279                 mem_compact_moved:{}\r\nmem_compact_bytes:{}\r\n\
1280                 mem_index_bytes:{}\r\n\
1281                 mem_client_buffers:{}\r\ntotal_system_memory:{}\r\n\
1282                 mem_cgroup_limit:{}\r\nmem_limit:{}\r\nmem_budget:{}\r\n\
1283                 maxmemory:{}\r\nmaxmemory_policy:{}\r\n\
1284                 maxstore:{}\r\nyo_store_bytes:{}\r\nyo_memory_regime:{}\r\n\r\n",
1285                server.memory_bytes(),
1286                server.dataset_bytes(),
1287                server.memory_bytes() - server.dataset_bytes(),
1288                server.arena_bytes(),
1289                server.segment_count(),
1290                compact.walked,
1291                compact.moved,
1292                compact.bytes,
1293                server.index_bytes(),
1294                server.conn_bytes(),
1295                cap.host.unwrap_or(0),
1296                cap.cgroup.unwrap_or(0),
1297                cap.limit().unwrap_or(0),
1298                cap.budget(),
1299                server.maxmemory(),
1300                policy,
1301                server.maxstore().map_or(-1, |n| n as i64),
1302                server.store_bytes(),
1303                server.regime(),
1304            );
1305        }
1306        if want("persistence") {
1307            persist::info(server, &mut s);
1308        }
1309        if want("stats") {
1310            // The cold counters live here and not in the memory section,
1311            // because they are totals since the server started and everything
1312            // in that section is a level right now. `yo_cold_faults` over the
1313            // point reads a run issued is the ratio G9 is a gate on, and it
1314            // cannot be worked out from outside the server.
1315            let cold = server.cold_stats();
1316            let totals = server.totals();
1317            let subs = server.pubsub_counts();
1318            let _ = write!(
1319                s,
1320                "# Stats\r\ntotal_connections_received:{}\r\n\
1321                 total_commands_processed:{}\r\nexpired_subkeys:{}\r\n\
1322                 expired_subkeys_active:{}\r\nexpired_keys:{}\r\n\
1323                 evicted_keys:{}\r\nkeyspace_hits:{}\r\nkeyspace_misses:{}\r\n\
1324                 yo_cold_demoted:{}\r\nyo_cold_promoted:{}\r\n\
1325                 yo_cold_faults:{}\r\nyo_cold_served:{}\r\nyo_cold_bytes_out:{}\r\n\
1326                 yo_cold_bytes_in:{}\r\npubsub_channels:{}\r\n\
1327                 pubsub_patterns:{}\r\npubsubshard_channels:{}\r\n\r\n",
1328                totals.connections,
1329                totals.commands,
1330                server.expired_fields(),
1331                server.expired_fields_active(),
1332                server.expired_keys(),
1333                server.evicted_keys(),
1334                server.keyspace_hits(),
1335                server.keyspace_misses(),
1336                cold.demoted,
1337                cold.promoted,
1338                cold.faults,
1339                cold.served,
1340                cold.bytes_out,
1341                cold.bytes_in,
1342                subs.channels,
1343                subs.patterns,
1344                subs.shard,
1345            );
1346        }
1347        if want("cpu") {
1348            // Two of Redis's six are not here. `used_cpu_sys_main_thread` and
1349            // `used_cpu_user_main_thread` need `RUSAGE_THREAD`, which is Linux
1350            // only, and reporting the process totals under a name that says
1351            // main thread would be right on a single threaded server and wrong
1352            // on the one this becomes.
1353            if let Some(u) = cpu::usage() {
1354                let _ = write!(
1355                    s,
1356                    "# CPU\r\nused_cpu_sys:{:.6}\r\nused_cpu_user:{:.6}\r\n\
1357                     used_cpu_sys_children:{:.6}\r\nused_cpu_user_children:{:.6}\r\n\r\n",
1358                    u.sys, u.user, u.sys_children, u.user_children,
1359                );
1360            }
1361        }
1362        if want("replication") {
1363            // Four fields out of Redis's dozen, and the eight that are missing
1364            // all describe the replication backlog, which is a thing that does
1365            // not exist here rather than a thing that is empty. The four that
1366            // are here are true of a server with no replica attached: it is the
1367            // master, nobody is following it, no failover is in progress and
1368            // nothing has been written to a stream that does not exist, which is
1369            // an offset of zero.
1370            s.push_str(
1371                "# Replication\r\nrole:master\r\nconnected_slaves:0\r\n\
1372                 master_failover_state:no-failover\r\nmaster_repl_offset:0\r\n\r\n",
1373            );
1374        }
1375        if extra("commandstats") {
1376            s.push_str("# Commandstats\r\n");
1377            for (name, row) in server.command_stats() {
1378                let _ = write!(
1379                    s,
1380                    "cmdstat_{name}:calls={},rejected_calls={},failed_calls={}\r\n",
1381                    row.calls, row.rejected, row.failed,
1382                );
1383            }
1384            s.push_str("\r\n");
1385        }
1386        if want("keyspace") {
1387            s.push_str("# Keyspace\r\n");
1388            for i in 0..DATABASES {
1389                let keys = server.dbs[i].len();
1390                if keys > 0 {
1391                    // `avg_ttl` is still a zero, and Redis reports a zero there
1392                    // too on a server that has never run its active expiry
1393                    // cycle, because the number is a running estimate that cycle
1394                    // produces rather than something anybody measures on demand.
1395                    let expires = server.dbs[i].expires();
1396                    let _ = write!(s, "db{i}:keys={keys},expires={expires},avg_ttl=0\r\n");
1397                }
1398            }
1399            s.push_str("\r\n");
1400        }
1401        s
1402    });
1403    out.verbatim(b"txt", text.as_bytes());
1404}
1405
1406// -------------------------------------------------------------------- help
1407
1408/// The `HELP` reply, which is an array of simple strings on both protocols.
1409pub(super) fn help(out: &mut Out, lines: &[&str]) {
1410    out.array(lines.len());
1411    for line in lines {
1412        out.simple(line.as_bytes());
1413    }
1414}
1415
1416/// What `COMMAND HELP` says.
1417const COMMAND_HELP: &[&str] = &[
1418    "COMMAND <subcommand> [<arg> [value] [opt] ...]. Subcommands are:",
1419    "(no subcommand)",
1420    "    Return details about all commands.",
1421    "COUNT",
1422    "    Return the total number of commands in this server.",
1423    "LIST [FILTERBY <MODULE <module-name>|ACLCAT <category>|PATTERN <pattern>>]",
1424    "    Return a list of all commands in this server.",
1425    "INFO [<command-name> ...]",
1426    "    Return details about multiple commands.",
1427    "DOCS [<command-name> ...]",
1428    "    Return documentation details about multiple commands.",
1429    "GETKEYS <full-command>",
1430    "    Return the keys from a full command.",
1431    "HELP",
1432    "    Print this help.",
1433];
1434
1435/// What `CONFIG HELP` says.
1436const CONFIG_HELP: &[&str] = &[
1437    "CONFIG <subcommand> [<arg> [value] [opt] ...]. Subcommands are:",
1438    "GET <pattern>",
1439    "    Return parameters matching the glob-like <pattern> and their values.",
1440    "SET <directive> <value>",
1441    "    Set the configuration <directive> to <value>.",
1442    "RESETSTAT",
1443    "    Reset statistics reported by the INFO command.",
1444    "REWRITE",
1445    "    Rewrite the configuration file.",
1446    "HELP",
1447    "    Print this help.",
1448];