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, backup, cpu};
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/// How long a sealed backup is kept before it cleans itself up.
165///
166/// Seconds, and zero is the default and means it is kept until somebody says
167/// `BACKUP CLEANUP`. Writable, since a backup taken by a script that then died
168/// is exactly the thing this is for and setting it afterwards has to work.
169const SEALED_TTL: &str = "backup-sealed-ttl";
170
171/// Read a byte count the way `CONFIG SET maxmemory` reads one.
172///
173/// This is Redis's `memtoull`. Digits, then an optional unit that is not case
174/// sensitive: nothing or `b` is bytes, `k` is a thousand and `kb` is a kibibyte,
175/// and the same pairing again for `m` and `g`. The two spellings meaning
176/// different numbers is a trap and it is Redis's trap, so it is repeated here
177/// rather than tidied up.
178///
179/// A unit that overflows clamps rather than failing, which is upstream's
180/// `ULLONG_MAX` arm. There is no sign: a leading minus is refused before the
181/// digits are read, so `maxmemory -1` is not a very large number.
182///
183/// Public because `yodb serve` takes the same limits on the command line that
184/// `CONFIG SET` takes at runtime, and a server that accepts `100mb` from one and
185/// not the other, or reads it as a different number, is a server that gets
186/// misconfigured. One parser, one answer.
187#[must_use]
188pub fn parse_memory(value: &[u8]) -> Option<u64> {
189    let split = value
190        .iter()
191        .position(|b| !b.is_ascii_digit())
192        .unwrap_or(value.len());
193    let (digits, unit) = value.split_at(split);
194    if digits.is_empty() {
195        return None;
196    }
197    let mul: u64 = match unit {
198        [] => 1,
199        u if u.eq_ignore_ascii_case(b"b") => 1,
200        u if u.eq_ignore_ascii_case(b"k") => 1000,
201        u if u.eq_ignore_ascii_case(b"kb") => 1024,
202        u if u.eq_ignore_ascii_case(b"m") => 1000 * 1000,
203        u if u.eq_ignore_ascii_case(b"mb") => 1024 * 1024,
204        u if u.eq_ignore_ascii_case(b"g") => 1000 * 1000 * 1000,
205        u if u.eq_ignore_ascii_case(b"gb") => 1024 * 1024 * 1024,
206        _ => return None,
207    };
208    let mut n: u64 = 0;
209    for d in digits {
210        n = n.saturating_mul(10).saturating_add(u64::from(d - b'0'));
211    }
212    Some(n.saturating_mul(mul))
213}
214
215/// Every policy name, joined the way `CONFIG SET` lists them when it refuses one.
216///
217/// This is a formatter and not a string because the error path should not touch
218/// the allocator, and it walks [`Policy::ALL`] rather than spelling the ten names
219/// out again so the two cannot drift apart. The order is the order in Redis's own
220/// enum table, which is the whole reason `Policy::ALL` is written down.
221struct PolicyNames;
222
223impl core::fmt::Display for PolicyNames {
224    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
225        for (at, policy) in Policy::ALL.iter().enumerate() {
226            if at > 0 {
227                f.write_str(", ")?;
228            }
229            f.write_str(policy.name())?;
230        }
231        Ok(())
232    }
233}
234
235/// Run one connection or server command.
236pub(super) fn execute(
237    server: &Server,
238    session: &mut Session,
239    spec: &Spec,
240    args: Args<'_>,
241    out: &mut Out,
242) -> Result<Flow> {
243    match spec.name {
244        // The arity in the table is a minimum of one, and a real server then
245        // refuses a second argument as a wrong number of them.
246        "ping" => {
247            if args.len() > 2 {
248                return Err(args::wrong_arity("ping"));
249            }
250            if args.len() == 2 {
251                out.bulk(args.get(1));
252            } else {
253                out.simple(b"PONG");
254            }
255        }
256        "echo" => out.bulk(args.get(1)),
257        "hello" => hello(session, args, out)?,
258        "select" => {
259            let n = args.int(1)?;
260            let ok = usize::try_from(n).is_ok_and(|n| n < DATABASES);
261            if !ok {
262                return Err(Error::new(Code::Invalid, "DB index is out of range"));
263            }
264            session.db = n as usize;
265            out.ok();
266        }
267        "reset" => {
268            // Everything a connection carries goes back to what it was when it
269            // was opened, and that includes the protocol: a connection that
270            // said `HELLO 3` is speaking RESP2 again after this.
271            session.reset();
272            out.set_proto(Proto::Resp2);
273            out.simple(b"RESET");
274        }
275        // The reply goes out before the socket closes, which is why this is a
276        // flow answer and not something the body does to the connection.
277        "quit" => {
278            out.ok();
279            return Ok(Flow::Close);
280        }
281        "command" => command(args, out)?,
282        "config" => config(server, args, out)?,
283        "info" => info(server, args, out),
284        // A key that is past its deadline and has not been read since is still
285        // counted, which is what Redis does too: `DBSIZE` is the size of the
286        // dictionary and not a walk over it. Redis has an active expiry cycle
287        // that takes those keys out within a tick or so and we do not yet, so
288        // the two servers disagree for as long as a dead key sits unread. That
289        // gap closes with the maintenance slice rather than with a count here,
290        // because a count here would be O(N) on a command that is O(1)
291        // everywhere else.
292        "dbsize" => out.int(server.dbs[session.db].len() as i64),
293        "flushall" => {
294            flush_mode(args)?;
295            for db in &server.dbs {
296                db.clear();
297            }
298            server.search.lock().clear();
299            server.cursors.lock().wipe();
300            out.ok();
301        }
302        // The search indexes go too, and they go whichever database this is.
303        // An index that only ever followed keys on database zero is dropped by
304        // a `FLUSHDB` on database nine, which is measured against a real server
305        // rather than reasoned about: the module hangs its callback on the
306        // flush event without looking at which database flushed.
307        "flushdb" => {
308            flush_mode(args)?;
309            server.dbs[session.db].clear();
310            server.search.lock().clear();
311            server.cursors.lock().wipe();
312            out.ok();
313        }
314        // Two databases change places and no key moves. What is in the stripes
315        // is exchanged and the databases stay where they are, so this costs two
316        // pointer sized writes per stripe whatever is in either of them, which
317        // is what makes `SWAPDB` fast and dangerous at the same time.
318        //
319        // No connection is told. A client on database zero is still on database
320        // zero and is now looking at what used to be database one, which is the
321        // whole point of the command and is why Redis calls it dangerous. A
322        // client parked in `BLPOP` remembers the database index it blocked on
323        // and not the database, so it wakes up against the swapped in one, which
324        // is Redis's behaviour and falls out of the index being what is stored.
325        "swapdb" => {
326            let first = db_index(args.get(1), "invalid first DB index")?;
327            let second = db_index(args.get(2), "invalid second DB index")?;
328            server.striped(first).swap_with(server.striped(second));
329            out.ok();
330        }
331        "time" => time(out),
332        "backup" => backup::execute(server, args, out)?,
333        "shutdown" => return shutdown(server, args),
334        _ => return Err(args::unknown_command(args)),
335    }
336    Ok(Flow::Continue)
337}
338
339/// `TIME`, which is two bulk strings and not one integer.
340///
341/// Seconds first and then microseconds within that second, both written out as
342/// decimal text, which is a shape nobody would choose today and is the shape
343/// every client library parses.
344///
345/// It reads the wall clock rather than the coarse clock the keyspace uses. The
346/// coarse one is a cached millisecond that a background tick refreshes, which is
347/// the right trade for deciding whether a key has expired and the wrong one for
348/// a command whose entire job is to say what time it is. A client that calls
349/// `TIME` twice in a row and gets the same microsecond has been lied to.
350fn time(out: &mut Out) {
351    let now = SystemTime::now()
352        .duration_since(UNIX_EPOCH)
353        .unwrap_or_default();
354    out.array(2);
355    out.bulk(now.as_secs().to_string().as_bytes());
356    out.bulk(now.subsec_micros().to_string().as_bytes());
357}
358
359// ---------------------------------------------------------------- SHUTDOWN
360
361/// `SHUTDOWN [NOSAVE | SAVE] [NOW] [FORCE] [ABORT]`.
362///
363/// On success this writes nothing at all and the connection closes under the
364/// client, which is what a server that has stopped looks like from the outside
365/// and is what every client library already expects. There is no `OK`, because
366/// an `OK` would be a promise made by a process that is about to not exist.
367///
368/// The flags are taken and none of them changes what happens, which is the same
369/// answer `SAVE` gets from `CONFIG GET`: this server has no save points and no
370/// snapshot to write, so saving and not saving are the same act. What durability
371/// there is belongs to the file underneath and is already on disk by the time a
372/// command returns, so there is nothing for `SAVE` to do and nothing for
373/// `NOSAVE` to skip. `NOW` and `FORCE` are about not waiting for replicas and
374/// about going anyway when a save failed, and neither has anything to wait for
375/// or to fail here.
376///
377/// # Errors
378///
379/// [`Code::Invalid`] for a word that is not one of the five, for `SAVE` and
380/// `NOSAVE` in the same call, and for `ABORT` alongside any other flag, all of
381/// which is what 8.10.1 says. `ABORT` on its own gets Redis's message for a
382/// cancel with nothing to cancel, and here that is not a state that can be
383/// reached rather than one that happens to be empty: a shutdown is decided and
384/// done inside one turn of the loop, so there is never a window in which one is
385/// in progress and a second client could call it off.
386fn shutdown(server: &Server, args: Args<'_>) -> Result<Flow> {
387    let (mut save, mut nosave, mut abort, mut other) = (false, false, false, false);
388    for at in 1..args.len() {
389        let arg = args.get(at);
390        match () {
391            () if is(arg, b"save") => save = true,
392            () if is(arg, b"nosave") => nosave = true,
393            () if is(arg, b"abort") => abort = true,
394            () if is(arg, b"now") || is(arg, b"force") => other = true,
395            () => return Err(args::syntax()),
396        }
397    }
398    // Repeating one is fine and contradicting yourself is not, and `ABORT` says
399    // to do nothing so it cannot be combined with a word about how to do it.
400    if (save && nosave) || (abort && (save || nosave || other)) {
401        return Err(args::syntax());
402    }
403    if abort {
404        return Err(Error::new(Code::Invalid, "No shutdown in progress."));
405    }
406    server.stop();
407    // Closing is what stops anything the client pipelined behind this from
408    // being answered by a server that is on its way out.
409    Ok(Flow::Close)
410}
411
412// ------------------------------------------------------------------- FLUSH
413
414/// Check the optional `ASYNC` or `SYNC` on `FLUSHALL` and `FLUSHDB`.
415///
416/// Both are accepted and neither changes anything. On a real server the choice
417/// is whether the freeing happens on the connection's thread or on the lazy
418/// free thread, and either way the keyspace is empty before the `OK` goes out.
419/// That is the whole of what a client can observe, and it is the same here,
420/// so taking the word and ignoring it is answering the question rather than
421/// pretending to.
422///
423/// # Errors
424///
425/// [`Code::Invalid`] for a third argument, or for a second that is neither
426/// word, which is what Redis says about both.
427fn flush_mode(args: Args<'_>) -> Result<()> {
428    if args.len() == 1 {
429        return Ok(());
430    }
431    if args.len() > 2 || !(is(args.get(1), b"async") || is(args.get(1), b"sync")) {
432        return Err(args::syntax());
433    }
434    Ok(())
435}
436
437/// One of `SWAPDB`'s two database indexes, with Redis's two different
438/// complaints about it.
439///
440/// A word that is not a number, or a number too big to be a database index on a
441/// server that stores the index in a C `int`, gets the caller's message, which
442/// says which of the two arguments was wrong. A number that is a plausible index
443/// and is not one of ours gets the same out of range message `SELECT` gives. The
444/// split looks arbitrary and it is Redis's, and the reason for it is that the
445/// first check happens while reading the argument and the second happens inside
446/// the swap, so only the first one knows which argument it was looking at.
447fn db_index(arg: &[u8], bad: &'static str) -> Result<usize> {
448    let n = parse_i64(arg)
449        .filter(|n| i32::try_from(*n).is_ok())
450        .ok_or_else(|| Error::new(Code::Invalid, bad))?;
451    usize::try_from(n)
452        .ok()
453        .filter(|n| *n < DATABASES)
454        .ok_or_else(|| Error::new(Code::Invalid, "DB index is out of range"))
455}
456
457// ------------------------------------------------------------------- HELLO
458
459/// `HELLO [protover [AUTH username password] [SETNAME name]]`.
460fn hello(session: &mut Session, args: Args<'_>, out: &mut Out) -> Result<()> {
461    if args.len() > 1 {
462        let v = parse_i64(args.get(1)).ok_or_else(|| {
463            Error::new(
464                Code::Invalid,
465                "Protocol version is not an integer or out of range",
466            )
467        })?;
468        let Some(proto) = Proto::from_version(v) else {
469            // `NOPROTO` rather than `ERR`, and it is the one error in this file
470            // written straight into the buffer: the prefix is part of what the
471            // client branches on, and it is the only place in the engine that
472            // needs this one.
473            out.error(b"NOPROTO unsupported protocol version");
474            return Ok(());
475        };
476        let mut i = 2;
477        while i < args.len() {
478            let o = args.get(i);
479            if is(o, b"AUTH") && i + 2 < args.len() {
480                // No password is configured, so the default user is `nopass`
481                // and any password for it is the right one, which is how a
482                // real server with no `requirepass` behaves. Any other user
483                // does not exist.
484                if !is(args.get(i + 1), b"default") {
485                    out.error(b"WRONGPASS invalid username-password pair or user is disabled.");
486                    return Ok(());
487                }
488                i += 3;
489            } else if is(o, b"SETNAME") && i + 1 < args.len() {
490                session.set_name(args.get(i + 1));
491                i += 2;
492            } else {
493                return Err(yo_alloc::allow(|| {
494                    Error::fmt(
495                        Code::Invalid,
496                        format_args!(
497                            "Syntax error in HELLO option '{}'",
498                            String::from_utf8_lossy(o)
499                        ),
500                    )
501                }));
502            }
503        }
504        // The reply is written in the protocol that was just agreed, not the
505        // one the request arrived in.
506        out.set_proto(proto);
507    }
508
509    let proto = out.proto().version();
510    out.map(7);
511    out.bulk(b"server");
512    out.bulk(REPORTED_SERVER.as_bytes());
513    out.bulk(b"version");
514    out.bulk(REPORTED_VERSION.as_bytes());
515    out.bulk(b"proto");
516    out.int(proto);
517    out.bulk(b"id");
518    out.int(session.id as i64);
519    out.bulk(b"mode");
520    out.bulk(b"standalone");
521    out.bulk(b"role");
522    out.bulk(b"master");
523    out.bulk(b"modules");
524    out.array(0);
525    Ok(())
526}
527
528// ----------------------------------------------------------------- COMMAND
529
530/// `COMMAND [COUNT|LIST|INFO|DOCS|GETKEYS|HELP]`.
531fn command(args: Args<'_>, out: &mut Out) -> Result<()> {
532    if args.len() == 1 {
533        out.array(table::COMMANDS.len());
534        for spec in table::COMMANDS {
535            write_spec(out, spec);
536        }
537        return Ok(());
538    }
539    let sub = args.get(1);
540    if is(sub, b"COUNT") {
541        out.int(table::COMMANDS.len() as i64);
542    } else if is(sub, b"INFO") {
543        if args.len() == 2 {
544            out.array(table::COMMANDS.len());
545            for spec in table::COMMANDS {
546                write_spec(out, spec);
547            }
548        } else {
549            out.array(args.len() - 2);
550            for i in 2..args.len() {
551                match table::lookup(args.get(i)) {
552                    Some(spec) => write_spec(out, spec),
553                    // A name nobody has heard of is a null in the list rather
554                    // than an error, so one bad name in a batch does not cost
555                    // the client the other answers. It is the plain null and
556                    // not the array one, which on RESP2 is the difference
557                    // between `$-1` and `*-1` and is what a real server sends.
558                    None => out.nil(),
559                }
560            }
561        }
562    } else if is(sub, b"LIST") {
563        list(args, out)?;
564    } else if is(sub, b"DOCS") {
565        docs(args, out);
566    } else if is(sub, b"GETKEYS") {
567        getkeys(args, out)?;
568    } else if is(sub, b"HELP") {
569        help(out, COMMAND_HELP);
570    } else {
571        return Err(args::unknown_subcommand(sub, "COMMAND"));
572    }
573    Ok(())
574}
575
576/// `COMMAND LIST [FILTERBY MODULE m|ACLCAT c|PATTERN p]`.
577fn list(args: Args<'_>, out: &mut Out) -> Result<()> {
578    if args.len() == 2 {
579        out.array(table::COMMANDS.len());
580        for spec in table::COMMANDS {
581            out.bulk(spec.name.as_bytes());
582        }
583        return Ok(());
584    }
585    if args.len() != 5 || !is(args.get(2), b"FILTERBY") {
586        return Err(args::syntax());
587    }
588    let (how, what) = (args.get(3), args.get(4));
589    let keep = |spec: &Spec| {
590        if is(how, b"MODULE") {
591            // Nothing here came from a module, so every filter by one is empty.
592            false
593        } else if is(how, b"ACLCAT") {
594            spec.acl
595                .iter()
596                .any(|c| c.len() == what.len() + 1 && c.as_bytes()[1..].eq_ignore_ascii_case(what))
597        } else {
598            glob::matches(what, spec.name.as_bytes())
599        }
600    };
601    if !is(how, b"MODULE") && !is(how, b"ACLCAT") && !is(how, b"PATTERN") {
602        return Err(args::syntax());
603    }
604    out.array(table::COMMANDS.iter().filter(|s| keep(s)).count());
605    for spec in table::COMMANDS.iter().filter(|s| keep(s)) {
606        out.bulk(spec.name.as_bytes());
607    }
608    Ok(())
609}
610
611/// `COMMAND DOCS [name ...]`.
612///
613/// The arguments field a real server sends is left out. It describes the shape
614/// of every option of every command in a form nothing but `redis-cli`'s hinting
615/// reads, and getting it wrong would be worse than not sending it, since a
616/// client that finds the field trusts it.
617fn docs(args: Args<'_>, out: &mut Out) {
618    if args.len() == 2 {
619        out.map(table::COMMANDS.len());
620        for spec in table::COMMANDS {
621            write_docs(out, spec);
622        }
623        return;
624    }
625    let found = (2..args.len())
626        .filter(|&i| table::lookup(args.get(i)).is_some())
627        .count();
628    out.map(found);
629    for i in 2..args.len() {
630        if let Some(spec) = table::lookup(args.get(i)) {
631            write_docs(out, spec);
632        }
633    }
634}
635
636/// One command's documentation, as the name and then the map about it.
637fn write_docs(out: &mut Out, spec: &Spec) {
638    out.bulk(spec.name.as_bytes());
639    out.map(4);
640    out.bulk(b"summary");
641    out.bulk(spec.summary.as_bytes());
642    out.bulk(b"since");
643    out.bulk(spec.since.as_bytes());
644    out.bulk(b"group");
645    out.bulk(spec.group.as_bytes());
646    out.bulk(b"complexity");
647    out.bulk(spec.complexity.as_bytes());
648}
649
650/// `COMMAND GETKEYS <full command>`.
651///
652/// This is how a cluster aware client routes a command it does not have a rule
653/// for, so a wrong answer here is a client that sends a write to the wrong
654/// node. The generic path is the first, last and step triple from the table.
655fn getkeys(args: Args<'_>, out: &mut Out) -> Result<()> {
656    if args.len() < 3 {
657        return Err(args::wrong_arity_sub("command", "getkeys"));
658    }
659    let inner = args.get(2);
660    let spec = table::lookup(inner)
661        .ok_or_else(|| Error::new(Code::Unsupported, "Invalid command specified"))?;
662    let argc = args.len() - 2;
663    if !table::arity_ok(spec, argc) {
664        return Err(Error::new(
665            Code::Invalid,
666            "Invalid number of arguments specified for command",
667        ));
668    }
669    // A few commands keep their keys somewhere the triple cannot describe,
670    // behind a count of how many there are. That is why a real server marks them
671    // `movablekeys` and why a client has to ask this question about them at all.
672    // `MSETEX` counts pairs and the rest count single keys, so what differs
673    // between them is the step and where the count sits: the script family has
674    // the body in front of it and the others have nothing.
675    //
676    // The script family is also the only one where none is a real answer. A
677    // script with no keys is an ordinary thing to write and `EVAL body 0`
678    // answers an empty list rather than complaining, where `MSETEX 0` is a
679    // command that would do nothing and is refused. The last flag says the same
680    // thing about a count that makes no sense at all: a real server reads the
681    // script family through a key spec that finds no keys and answers nothing,
682    // and refuses the others, so `EVAL body -1` and `EVAL body abc` are both an
683    // empty list here rather than an error.
684    if let Some((at, step, least, lenient)) = match spec.name {
685        "msetex" => Some((3, 2, 1, false)),
686        "ts.nrange" | "ts.nrevrange" => Some((3, 1, 1, false)),
687        "eval" | "eval_ro" | "evalsha" | "evalsha_ro" | "fcall" | "fcall_ro" => {
688            Some((4, 1, 0, true))
689        }
690        _ => None,
691    } {
692        let found = parse_i64(args.get(at))
693            .filter(|&n| n >= least)
694            .and_then(|n| usize::try_from(n).ok())
695            .filter(|&n| at + 1 + step * n <= args.len());
696        let n = match found {
697            Some(n) => n,
698            None if lenient => 0,
699            None => {
700                return Err(Error::new(
701                    Code::Invalid,
702                    "Invalid arguments specified for command",
703                ));
704            }
705        };
706        out.array(n);
707        for i in 0..n {
708            out.bulk(args.get(at + 1 + step * i));
709        }
710        return Ok(());
711    }
712    if spec.first_key == 0 {
713        return Err(Error::new(
714            Code::Invalid,
715            "The command has no key arguments",
716        ));
717    }
718    let last = if spec.last_key < 0 {
719        (argc as i64) + i64::from(spec.last_key)
720    } else {
721        i64::from(spec.last_key)
722    };
723    let step = i64::from(spec.step).max(1);
724    let first = i64::from(spec.first_key);
725    let count = if last < first {
726        0
727    } else {
728        ((last - first) / step + 1) as usize
729    };
730    out.array(count);
731    for i in 0..count {
732        out.bulk(args.get(2 + (first + (i as i64) * step) as usize));
733    }
734    Ok(())
735}
736
737/// One command, in the ten field shape `COMMAND INFO` has had since 7.0.
738///
739/// The tips, the key specs and the subcommands are all empty. The triple above
740/// them says where the keys are for everything in this table except `MSETEX`,
741/// `TS.NRANGE` and `TS.NREVRANGE`, which is what `COMMAND GETKEYS` is for, and
742/// divergence D-13 says so.
743fn write_spec(out: &mut Out, spec: &Spec) {
744    out.array(10);
745    out.bulk(spec.name.as_bytes());
746    out.int(i64::from(spec.arity));
747    out.array(spec.flags.len());
748    for f in spec.flags {
749        out.simple(f.as_bytes());
750    }
751    out.int(i64::from(spec.first_key));
752    out.int(i64::from(spec.last_key));
753    out.int(i64::from(spec.step));
754    out.array(spec.acl.len());
755    for a in spec.acl {
756        out.simple(a.as_bytes());
757    }
758    out.array(0);
759    out.array(0);
760    out.array(0);
761}
762
763// ------------------------------------------------------------------ CONFIG
764
765/// What a ladder setting is set to now.
766fn read_knob(db: &Keyspace, knob: Knob) -> usize {
767    match knob {
768        Knob::SetIntsetEntries => db.limits().max_intset_entries,
769        Knob::SetListpackEntries => db.limits().max_listpack_entries,
770        Knob::SetListpackValue => db.limits().max_listpack_value,
771        Knob::HashListpackEntries => db.hash_limits().max_listpack_entries,
772        Knob::HashListpackValue => db.hash_limits().max_listpack_value,
773        Knob::MaxmemorySamples => db.samples(),
774        Knob::LfuLogFactor => db.lfu().log_factor as usize,
775        Knob::LfuDecayTime => db.lfu().decay_minutes as usize,
776    }
777}
778
779/// Move one ladder setting on one database.
780fn write_knob(db: &mut Keyspace, knob: Knob, n: usize) {
781    let mut set = *db.limits();
782    let mut hash = *db.hash_limits();
783    let mut lfu = db.lfu();
784    match knob {
785        Knob::SetIntsetEntries => set.max_intset_entries = n,
786        Knob::SetListpackEntries => set.max_listpack_entries = n,
787        Knob::SetListpackValue => set.max_listpack_value = n,
788        Knob::HashListpackEntries => hash.max_listpack_entries = n,
789        Knob::HashListpackValue => hash.max_listpack_value = n,
790        Knob::MaxmemorySamples => db.set_samples(n),
791        // Saturating rather than wrapping, because these two are read as `u32`
792        // and a client is free to send a number that does not fit. Redis clamps
793        // `lfu-log-factor` and `lfu-decay-time` to the same width.
794        Knob::LfuLogFactor => lfu.log_factor = u32::try_from(n).unwrap_or(u32::MAX),
795        Knob::LfuDecayTime => lfu.decay_minutes = u32::try_from(n).unwrap_or(u32::MAX),
796    }
797    db.set_limits(set);
798    db.set_hash_limits(hash);
799    db.set_lfu(lfu);
800}
801
802/// The two things a real server says about a number it will not take.
803///
804/// Both name the setting the client typed and not the one it is an alias for,
805/// so `hash-max-ziplist-entries` comes back saying `hash-max-ziplist-entries`.
806/// A value past the range of an `i64` is the parse complaint and not the range
807/// one, which is upstream reading it before it checks it.
808fn bad_setting(name: &str, parsed: bool) -> Error {
809    if parsed {
810        Error::fmt(
811            Code::Invalid,
812            format_args!(
813                "CONFIG SET failed (possibly related to argument '{name}') - argument must be between 0 and 9223372036854775807 inclusive"
814            ),
815        )
816    } else {
817        Error::fmt(
818            Code::Invalid,
819            format_args!(
820                "CONFIG SET failed (possibly related to argument '{name}') - argument couldn't be parsed into an integer"
821            ),
822        )
823    }
824}
825
826/// `CONFIG GET|SET|RESETSTAT|REWRITE|HELP`.
827fn config(server: &Server, args: Args<'_>, out: &mut Out) -> Result<()> {
828    let sub = args.get(1);
829    if is(sub, b"GET") {
830        if args.len() < 3 {
831            return Err(args::wrong_arity_sub("config", "get"));
832        }
833        let wanted =
834            |name: &str| (2..args.len()).any(|i| glob::matches(args.get(i), name.as_bytes()));
835        // A setting that two patterns both ask for is sent once, which is what
836        // makes this a count of settings rather than a count of matches. The
837        // two spellings of a ladder setting are two settings by that rule, so
838        // `CONFIG GET hash-max-*` sends the listpack name and the ziplist name
839        // and the same number under both, which is what a real server does.
840        let fixed = SETTINGS.iter().filter(|(k, _)| wanted(k));
841        let ladder = LADDER.iter().filter(|(k, _)| wanted(k));
842        let policy = wanted(MAXMEMORY_POLICY);
843        let limit = wanted(MAXMEMORY);
844        let store = wanted(MAXSTORE);
845        let where_ = wanted(DIR);
846        let ttl = wanted(SEALED_TTL);
847        out.map(
848            fixed.clone().count()
849                + ladder.clone().count()
850                + usize::from(policy)
851                + usize::from(limit)
852                + usize::from(store)
853                + usize::from(where_)
854                + usize::from(ttl),
855        );
856        for (k, v) in fixed {
857            out.bulk(k.as_bytes());
858            out.bulk(v.as_bytes());
859        }
860        for (k, knob) in ladder {
861            out.bulk(k.as_bytes());
862            out.bulk_int(read_knob(&server.settings(), *knob) as i64);
863        }
864        if policy {
865            out.bulk(MAXMEMORY_POLICY.as_bytes());
866            out.bulk(server.settings().policy().name().as_bytes());
867        }
868        if limit {
869            // Back as a plain number of bytes whatever the client typed to set
870            // it, which is what a real server does: `CONFIG SET maxmemory 1gb`
871            // reads back as 1073741824.
872            out.bulk(MAXMEMORY.as_bytes());
873            out.bulk_int(server.maxmemory() as i64);
874        }
875        if store {
876            // Minus one for no limit, and a plain number of bytes otherwise.
877            // Zero cannot mean no limit here the way it does for `maxmemory`,
878            // because zero is the setting that says the file holds nothing.
879            out.bulk(MAXSTORE.as_bytes());
880            out.bulk_int(server.maxstore().map_or(-1, |n| n as i64));
881        }
882        if where_ {
883            // Absolute, which is what a real server answers too: it resolves the
884            // directory at startup and reports the resolved one, so a client can
885            // tell where the files are without knowing where the process was
886            // launched from.
887            out.bulk(DIR.as_bytes());
888            yo_alloc::allow(|| out.bulk(server.dir().to_string_lossy().as_bytes()));
889        }
890        if ttl {
891            out.bulk(SEALED_TTL.as_bytes());
892            out.bulk_int(server.backup().ttl() as i64);
893        }
894    } else if is(sub, b"SET") {
895        // Too few is a wrong number of arguments and an odd number is a syntax
896        // error, which is not the same sentence and is not the same rule. A
897        // real server counts the pairs after it has decided there is at least
898        // one, so `CONFIG SET appendonly` is an arity error and `CONFIG SET
899        // appendonly no maxmemory` is a syntax one.
900        if args.len() < 4 {
901            return Err(args::wrong_arity_sub("config", "set"));
902        }
903        if !args.len().is_multiple_of(2) {
904            return Err(args::syntax());
905        }
906        // Every pair is checked before any of them is applied, because a real
907        // server takes the whole `CONFIG SET` or none of it. `CONFIG SET
908        // hash-max-listpack-entries 7 set-max-listpack-entries abc` leaves the
909        // hash setting where it was, which was checked rather than assumed.
910        let mut writes = [None; 16];
911        let mut count = 0;
912        let mut policy = None;
913        let mut limit = None;
914        let mut store = None;
915        let mut ttl = None;
916        let mut i = 2;
917        while i < args.len() {
918            let (name, value) = (args.get(i), args.get(i + 1));
919            i += 2;
920            if is(name, MAXMEMORY.as_bytes()) {
921                let Some(bytes) = parse_memory(value) else {
922                    return Err(Error::fmt(
923                        Code::Invalid,
924                        format_args!(
925                            "CONFIG SET failed (possibly related to argument '{MAXMEMORY}') - argument must be a memory value"
926                        ),
927                    ));
928                };
929                limit = Some(bytes);
930                continue;
931            }
932            if is(name, MAXSTORE.as_bytes()) {
933                // `-1` before the memory parser sees it, because that parser
934                // refuses a sign and should keep refusing one: `maxmemory -1`
935                // is not a very large number and never was.
936                let parsed = if value == b"-1" {
937                    Some(None)
938                } else {
939                    parse_memory(value).map(Some)
940                };
941                let Some(bytes) = parsed else {
942                    return Err(Error::fmt(
943                        Code::Invalid,
944                        format_args!(
945                            "CONFIG SET failed (possibly related to argument '{MAXSTORE}') - argument must be a memory value or -1"
946                        ),
947                    ));
948                };
949                store = Some(bytes);
950                continue;
951            }
952            if is(name, MAXMEMORY_POLICY.as_bytes()) {
953                // Named twice in one command, the last one wins, which is the
954                // same rule the ladder settings follow and is what a real server
955                // does with any setting repeated in a single `CONFIG SET`.
956                let Some(p) = Policy::parse(value) else {
957                    return Err(Error::fmt(
958                        Code::Invalid,
959                        format_args!(
960                            "CONFIG SET failed (possibly related to argument '{MAXMEMORY_POLICY}') - argument(s) must be one of the following: {PolicyNames}"
961                        ),
962                    ));
963                };
964                policy = Some(p);
965                continue;
966            }
967            if is(name, DIR.as_bytes()) {
968                // Refused whatever the value is, including the one it is already
969                // set to, which is the one place a setting here does not take
970                // the write that changes nothing. That is the reference's
971                // answer: a protected config is refused before anybody looks at
972                // what was asked for.
973                return Err(Error::fmt(
974                    Code::Unsupported,
975                    format_args!(
976                        "CONFIG SET failed (possibly related to argument '{DIR}') - can't set protected config"
977                    ),
978                ));
979            }
980            if is(name, SEALED_TTL.as_bytes()) {
981                let Some(n) = parse_i64(value).filter(|&n| n >= 0) else {
982                    return Err(bad_setting(SEALED_TTL, parse_i64(value).is_some()));
983                };
984                ttl = Some(n as u64);
985                continue;
986            }
987            if let Some((k, knob)) = LADDER.iter().find(|(k, _)| is(name, k.as_bytes())) {
988                let Some(n) = parse_i64(value).filter(|&n| n >= 0) else {
989                    return Err(bad_setting(k, parse_i64(value).is_some()));
990                };
991                if count == writes.len() {
992                    // Sixteen pairs is more than the ten names there are, so
993                    // getting here means a name was given twice enough times to
994                    // fill it, and the last one would have won anyway.
995                    return Err(args::syntax());
996                }
997                writes[count] = Some((*knob, n as usize));
998                count += 1;
999                continue;
1000            }
1001            let Some((k, v)) = SETTINGS.iter().find(|(k, _)| is(name, k.as_bytes())) else {
1002                return Err(yo_alloc::allow(|| {
1003                    Error::fmt(
1004                        Code::Invalid,
1005                        format_args!(
1006                            "Unknown option or number of arguments for CONFIG SET - '{}'",
1007                            String::from_utf8_lossy(name)
1008                        ),
1009                    )
1010                }));
1011            };
1012            if value != v.as_bytes() {
1013                return Err(Error::fmt(
1014                    Code::Unsupported,
1015                    format_args!(
1016                        "CONFIG SET failed (possibly related to argument '{k}') - can't set immutable config"
1017                    ),
1018                ));
1019            }
1020        }
1021        // Every stripe of every database, because these are one server wide
1022        // number in Redis and the fact that a `Keyspace` carries its own copy is
1023        // ours and not the client's problem. A stripe that missed one would put
1024        // a key in a different shape from the same key on the stripe next to it,
1025        // which `OBJECT ENCODING` would then answer differently for depending on
1026        // where the key happened to land.
1027        // The whole database is held while its stripes are set rather than one
1028        // stripe at a time, for the same reason they all get the same number: a
1029        // client that read `OBJECT ENCODING` in the middle of a half done change
1030        // would be told two different things about two keys depending on nothing
1031        // it can see.
1032        for (knob, n) in writes.iter().flatten() {
1033            for at in 0..DATABASES {
1034                let db = server.striped(at);
1035                let mut held = db.hold_many(0..db.width());
1036                for i in 0..db.width() {
1037                    write_knob(held.stripe_mut(i), *knob, *n);
1038                }
1039            }
1040        }
1041        if let Some(p) = policy {
1042            for at in 0..DATABASES {
1043                let db = server.striped(at);
1044                let mut held = db.hold_many(0..db.width());
1045                for i in 0..db.width() {
1046                    held.stripe_mut(i).set_policy(p);
1047                }
1048            }
1049        }
1050        if let Some(seconds) = ttl {
1051            server.backup().set_ttl(seconds);
1052        }
1053        // Last, so that a `CONFIG SET maxmemory 1mb maxmemory-policy allkeys-lru`
1054        // has the policy in place before the limit that will act on it. The two
1055        // in the other order would run the first eviction under whatever the
1056        // policy used to be, which for a fresh server is `noeviction` and would
1057        // refuse the next write instead of making room for it.
1058        if let Some(bytes) = store {
1059            server.set_maxstore(bytes);
1060        }
1061        if let Some(bytes) = limit {
1062            server.set_maxmemory(bytes);
1063        }
1064        out.ok();
1065    } else if is(sub, b"RESETSTAT") {
1066        server.reset_stats();
1067        out.ok();
1068    } else if is(sub, b"REWRITE") {
1069        return Err(Error::new(
1070            Code::Unsupported,
1071            "The server is running without a config file",
1072        ));
1073    } else if is(sub, b"HELP") {
1074        help(out, CONFIG_HELP);
1075    } else {
1076        return Err(args::unknown_subcommand(sub, "CONFIG"));
1077    }
1078    Ok(())
1079}
1080
1081// -------------------------------------------------------------------- INFO
1082
1083/// `INFO [section ...]`.
1084///
1085/// Every number in here is one this layer can actually answer. There is no
1086/// `rdb_last_save_time` because there is no save, and a field that is not there
1087/// is a client falling back rather than a client believing a zero.
1088///
1089/// The `CPU` section used to be missing for the same reason and is here now,
1090/// because nothing measured it and then something did. It is one `getrusage`
1091/// call in [`super::cpu`], and the reason it went in is that Redis's own
1092/// `unit/info-command` tests fail without it: a monitoring tool graphs
1093/// processor time against wall clock to decide whether a server is busy or
1094/// waiting, so an absent field there is a real hole and not a tidy omission.
1095fn info(server: &Server, args: Args<'_>, out: &mut Out) {
1096    // Redis keeps two lists: the sections a bare `INFO` hands back, and the ones
1097    // that have to be asked for by name or by `all`. `commandstats` is in the
1098    // second, along with `latencystats` and `errorstats`, because they grow with
1099    // the number of distinct commands a server has seen and a monitoring tool
1100    // polling `INFO` every second does not want them.
1101    //
1102    // `unit/info-command` is exactly this distinction written down: it asks for
1103    // `INFO default` and insists `rejected_calls` is not in the answer, then
1104    // asks for `INFO all` and insists that it is.
1105    let named = |section: &str| (1..args.len()).any(|i| is(args.get(i), section.as_bytes()));
1106    let everything = (1..args.len()).any(|i| {
1107        let a = args.get(i);
1108        is(a, b"all") || is(a, b"everything")
1109    });
1110    let by_default = args.len() == 1 || (1..args.len()).any(|i| is(args.get(i), b"default"));
1111    let want = |section: &str| by_default || everything || named(section);
1112    let extra = |section: &str| everything || named(section);
1113    // One string, built once and written once. It allocates, which is allowed
1114    // here and nowhere near the commands that count: `INFO` is a monitoring
1115    // call and it is not on the path M2 is measured on.
1116    let text = yo_alloc::allow(|| {
1117        let mut s = String::with_capacity(1024);
1118        if want("server") {
1119            let _ = write!(
1120                s,
1121                "# Server\r\nredis_version:{REPORTED_VERSION}\r\nyo_version:{}\r\n\
1122                 redis_mode:standalone\r\narch_bits:{}\r\nprocess_id:0\r\n\
1123                 run_id:0000000000000000000000000000000000000000\r\ntcp_port:0\r\n\
1124                 uptime_in_seconds:{}\r\nio_threads_active:0\r\n\r\n",
1125                env!("CARGO_PKG_VERSION"),
1126                usize::BITS,
1127                server.uptime_secs(),
1128            );
1129        }
1130        if want("clients") {
1131            let _ = write!(
1132                s,
1133                "# Clients\r\nconnected_clients:{}\r\nblocked_clients:{}\r\n\
1134                 cluster_connections:0\r\n\r\n",
1135                server.totals().clients,
1136                server.parked(),
1137            );
1138        }
1139        if want("memory") {
1140            // Both the cap and the quarter of it, because the quarter is an
1141            // empirical number and somebody surprised by it should be able to
1142            // see what it was a quarter of without reading the source. The
1143            // reasoning is written out in `cap`.
1144            let cap = crate::cap::cap();
1145            let compact = server.compaction();
1146            // Read out of its stripe before the write, because an argument list
1147            // keeps every temporary in it alive until the whole call is over
1148            // and one of the other arguments walks that same stripe.
1149            let policy = server.settings().policy().name();
1150            let _ = write!(
1151                s,
1152                "# Memory\r\nused_memory:{}\r\nused_memory_dataset:{}\r\n\
1153                 used_memory_overhead:{}\r\nmem_arena_bytes:{}\r\n\
1154                 mem_arena_segments:{}\r\nmem_compact_walked:{}\r\n\
1155                 mem_compact_moved:{}\r\nmem_compact_bytes:{}\r\n\
1156                 mem_index_bytes:{}\r\n\
1157                 mem_client_buffers:{}\r\ntotal_system_memory:{}\r\n\
1158                 mem_cgroup_limit:{}\r\nmem_limit:{}\r\nmem_budget:{}\r\n\
1159                 maxmemory:{}\r\nmaxmemory_policy:{}\r\n\
1160                 maxstore:{}\r\nyo_store_bytes:{}\r\nyo_memory_regime:{}\r\n\r\n",
1161                server.memory_bytes(),
1162                server.dataset_bytes(),
1163                server.memory_bytes() - server.dataset_bytes(),
1164                server.arena_bytes(),
1165                server.segment_count(),
1166                compact.walked,
1167                compact.moved,
1168                compact.bytes,
1169                server.index_bytes(),
1170                server.conn_bytes(),
1171                cap.host.unwrap_or(0),
1172                cap.cgroup.unwrap_or(0),
1173                cap.limit().unwrap_or(0),
1174                cap.budget(),
1175                server.maxmemory(),
1176                policy,
1177                server.maxstore().map_or(-1, |n| n as i64),
1178                server.store_bytes(),
1179                server.regime(),
1180            );
1181        }
1182        if want("stats") {
1183            // The cold counters live here and not in the memory section,
1184            // because they are totals since the server started and everything
1185            // in that section is a level right now. `yo_cold_faults` over the
1186            // point reads a run issued is the ratio G9 is a gate on, and it
1187            // cannot be worked out from outside the server.
1188            let cold = server.cold_stats();
1189            let totals = server.totals();
1190            let _ = write!(
1191                s,
1192                "# Stats\r\ntotal_connections_received:{}\r\n\
1193                 total_commands_processed:{}\r\nexpired_keys:{}\r\n\
1194                 evicted_keys:{}\r\nyo_cold_demoted:{}\r\nyo_cold_promoted:{}\r\n\
1195                 yo_cold_faults:{}\r\nyo_cold_served:{}\r\nyo_cold_bytes_out:{}\r\n\
1196                 yo_cold_bytes_in:{}\r\n\r\n",
1197                totals.connections,
1198                totals.commands,
1199                server.expired_keys(),
1200                server.evicted_keys(),
1201                cold.demoted,
1202                cold.promoted,
1203                cold.faults,
1204                cold.served,
1205                cold.bytes_out,
1206                cold.bytes_in,
1207            );
1208        }
1209        if want("cpu") {
1210            // Two of Redis's six are not here. `used_cpu_sys_main_thread` and
1211            // `used_cpu_user_main_thread` need `RUSAGE_THREAD`, which is Linux
1212            // only, and reporting the process totals under a name that says
1213            // main thread would be right on a single threaded server and wrong
1214            // on the one this becomes.
1215            if let Some(u) = cpu::usage() {
1216                let _ = write!(
1217                    s,
1218                    "# CPU\r\nused_cpu_sys:{:.6}\r\nused_cpu_user:{:.6}\r\n\
1219                     used_cpu_sys_children:{:.6}\r\nused_cpu_user_children:{:.6}\r\n\r\n",
1220                    u.sys, u.user, u.sys_children, u.user_children,
1221                );
1222            }
1223        }
1224        if want("replication") {
1225            // Four fields out of Redis's dozen, and the eight that are missing
1226            // all describe the replication backlog, which is a thing that does
1227            // not exist here rather than a thing that is empty. The four that
1228            // are here are true of a server with no replica attached: it is the
1229            // master, nobody is following it, no failover is in progress and
1230            // nothing has been written to a stream that does not exist, which is
1231            // an offset of zero.
1232            s.push_str(
1233                "# Replication\r\nrole:master\r\nconnected_slaves:0\r\n\
1234                 master_failover_state:no-failover\r\nmaster_repl_offset:0\r\n\r\n",
1235            );
1236        }
1237        if extra("commandstats") {
1238            s.push_str("# Commandstats\r\n");
1239            for (name, row) in server.command_stats() {
1240                let _ = write!(
1241                    s,
1242                    "cmdstat_{name}:calls={},rejected_calls={},failed_calls={}\r\n",
1243                    row.calls, row.rejected, row.failed,
1244                );
1245            }
1246            s.push_str("\r\n");
1247        }
1248        if want("keyspace") {
1249            s.push_str("# Keyspace\r\n");
1250            for i in 0..DATABASES {
1251                let keys = server.dbs[i].len();
1252                if keys > 0 {
1253                    // `avg_ttl` is still a zero, and Redis reports a zero there
1254                    // too on a server that has never run its active expiry
1255                    // cycle, because the number is a running estimate that cycle
1256                    // produces rather than something anybody measures on demand.
1257                    let expires = server.dbs[i].expires();
1258                    let _ = write!(s, "db{i}:keys={keys},expires={expires},avg_ttl=0\r\n");
1259                }
1260            }
1261            s.push_str("\r\n");
1262        }
1263        s
1264    });
1265    out.verbatim(b"txt", text.as_bytes());
1266}
1267
1268// -------------------------------------------------------------------- help
1269
1270/// The `HELP` reply, which is an array of simple strings on both protocols.
1271pub(super) fn help(out: &mut Out, lines: &[&str]) {
1272    out.array(lines.len());
1273    for line in lines {
1274        out.simple(line.as_bytes());
1275    }
1276}
1277
1278/// What `COMMAND HELP` says.
1279const COMMAND_HELP: &[&str] = &[
1280    "COMMAND <subcommand> [<arg> [value] [opt] ...]. Subcommands are:",
1281    "(no subcommand)",
1282    "    Return details about all commands.",
1283    "COUNT",
1284    "    Return the total number of commands in this server.",
1285    "LIST [FILTERBY <MODULE <module-name>|ACLCAT <category>|PATTERN <pattern>>]",
1286    "    Return a list of all commands in this server.",
1287    "INFO [<command-name> ...]",
1288    "    Return details about multiple commands.",
1289    "DOCS [<command-name> ...]",
1290    "    Return documentation details about multiple commands.",
1291    "GETKEYS <full-command>",
1292    "    Return the keys from a full command.",
1293    "HELP",
1294    "    Print this help.",
1295];
1296
1297/// What `CONFIG HELP` says.
1298const CONFIG_HELP: &[&str] = &[
1299    "CONFIG <subcommand> [<arg> [value] [opt] ...]. Subcommands are:",
1300    "GET <pattern>",
1301    "    Return parameters matching the glob-like <pattern> and their values.",
1302    "SET <directive> <value>",
1303    "    Set the configuration <directive> to <value>.",
1304    "RESETSTAT",
1305    "    Reset statistics reported by the INFO command.",
1306    "REWRITE",
1307    "    Rewrite the configuration file.",
1308    "HELP",
1309    "    Print this help.",
1310];