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::keyspec::{self, Begin, Find, KeySpec};
19use super::table::{self, Spec};
20use super::{
21    DATABASES, Flow, Server, Session, acl, auth, backup, cpu, debug, multi, notify, persist,
22};
23use crate::proto::Proto;
24use crate::reply::Out;
25use core::fmt::Write;
26use std::time::{SystemTime, UNIX_EPOCH};
27use yo_common::num::parse_i64;
28use yo_common::{Code, Error, Result, glob};
29use yo_kv::Keyspace;
30use yo_kv::access::Policy;
31
32/// What we tell a client we are.
33///
34/// It is a lie and it is a deliberate one. Every client library in the world
35/// branches on this pair to decide which commands exist, and a driver that
36/// reads `yo` here falls back to its oldest code path or refuses to connect.
37/// Divergence D-12 in `divergences.toml` says so, and the honest answer is in
38/// the `yo_version` field of `INFO` next to this one.
39const REPORTED_SERVER: &str = "redis";
40/// The Redis version we answer 100 percent of, which is what `HELLO` reports.
41///
42/// [`super::backup`] writes it into the `redis-ver` aux field of the base file
43/// it produces, so a server told to load one reads the same version out of the
44/// file that a client reads off the connection.
45pub(super) const REPORTED_VERSION: &str = "8.8.0";
46
47/// The settings that are fixed for the life of the process.
48///
49/// `CONFIG SET` accepts a write to one of these that changes nothing and
50/// refuses everything else rather than pretending to have taken it. A client
51/// that sets `appendonly no` on a server that already has no append only file
52/// gets an `OK` and is telling the truth; one that sets `appendonly yes` gets
53/// told it cannot, which is better than an `OK` and no file.
54const SETTINGS: &[(&str, &str)] = &[
55    ("appendonly", "no"),
56    ("appendfsync", "everysec"),
57    // Where `BACKUP` writes, under `dir`. Fixed here where a real server takes
58    // it at startup, because nothing in this build reads it from a file.
59    ("backupdirname", backup::DIR_NAME),
60    ("databases", "16"),
61    ("io-threads", "1"),
62    ("proto-max-bulk-len", "536870912"),
63    // How much of the command stream a master keeps for a replica that comes
64    // back, which is a compiled in size here and happens to be the size Redis
65    // ships with. Fixed rather than writable because the backlog is one buffer
66    // that is allocated once and resizing it under a replica that is reading
67    // out of it is a change of its own. There is a test below that this number
68    // and `repl::BACKLOG_BYTES` are the same number.
69    ("repl-backlog-size", "1048576"),
70    ("save", ""),
71    ("timeout", "0"),
72];
73
74/// Which number on the size ladder a settings name refers to.
75#[derive(Debug, Clone, Copy, PartialEq, Eq)]
76enum Knob {
77    SetIntsetEntries,
78    SetListpackEntries,
79    SetListpackValue,
80    HashListpackEntries,
81    HashListpackValue,
82    MaxmemorySamples,
83    LfuLogFactor,
84    LfuDecayTime,
85}
86
87/// The settings that move the size ladder, which are the ones that really move.
88///
89/// These decide where a collection stops being a packed blob and becomes an
90/// element table, so they decide what `OBJECT ENCODING` answers, and a client
91/// that reads `OBJECT ENCODING` after setting one of these expects the two to
92/// agree. That is the whole reason they are writable when nothing else here is.
93///
94/// The `ziplist` spellings are the names these had before Redis renamed them
95/// and it still answers to both, so this does too. Two names, one number: a
96/// `CONFIG SET hash-max-ziplist-entries 4` shows up under the listpack name
97/// too, which was checked against 8.10.1 rather than assumed.
98///
99/// Moving one of these leaves every collection that already exists exactly as
100/// it is, and only decides what the next write builds. Redis does the same, and
101/// it is the reason `CONFIG SET set-max-listpack-entries 0` does not rewrite
102/// the keyspace.
103///
104/// The three eviction numbers are in here too, which stretches the name a
105/// little. They belong with these rather than with the immutable settings for
106/// the same reason: a client that sets one and then reads `OBJECT FREQ` or
107/// watches `evicted_keys` expects the two to agree. `maxmemory-samples` says how
108/// many keys a round of sampling looks at, and the two `lfu` numbers set what
109/// the counter under an LFU policy actually measures.
110const LADDER: &[(&str, Knob)] = &[
111    ("hash-max-listpack-entries", Knob::HashListpackEntries),
112    ("hash-max-listpack-value", Knob::HashListpackValue),
113    ("hash-max-ziplist-entries", Knob::HashListpackEntries),
114    ("hash-max-ziplist-value", Knob::HashListpackValue),
115    ("lfu-decay-time", Knob::LfuDecayTime),
116    ("lfu-log-factor", Knob::LfuLogFactor),
117    ("maxmemory-samples", Knob::MaxmemorySamples),
118    ("set-max-intset-entries", Knob::SetIntsetEntries),
119    ("set-max-listpack-entries", Knob::SetListpackEntries),
120    ("set-max-listpack-value", Knob::SetListpackValue),
121];
122
123/// The setting that decides which way the access field on every record is read.
124///
125/// It is on its own rather than in [`SETTINGS`] or [`LADDER`] because it is the
126/// only writable setting that is not a number, and rather than immutable because
127/// it really moves: a client that sets it and then reads `OBJECT FREQ` expects
128/// the two to agree, which is the same argument the size ladder makes.
129///
130/// Setting it changes nothing about the keys already stored. Whatever is in
131/// their access field stays there and means something different from the moment
132/// the policy changes, which is what the `OBJECT FREQ` error text warns about.
133const MAXMEMORY_POLICY: &str = "maxmemory-policy";
134
135/// How much the server is allowed to hold before it starts evicting.
136///
137/// Also on its own, and for the third different reason. It is not immutable,
138/// it is not on the size ladder and it is the only setting whose value is not a
139/// plain integer: a client writes `maxmemory 100mb` and means a hundred and
140/// four million bytes, so it needs a parser of its own.
141///
142/// Zero means no limit, which is the default and is what makes the check in
143/// front of every write one comparison. Setting it to a number smaller than
144/// what the server is already holding is allowed and is a real thing to do: the
145/// next write that would allocate evicts until it fits or is refused, which is
146/// what the `maxmemory-policy` decides between.
147const MAXMEMORY: &str = "maxmemory";
148
149/// How much the server is allowed to keep on the file before it starts evicting.
150///
151/// The other half of the eviction inversion `14` section 4.1 describes, and the
152/// only setting here that has no counterpart in Redis. `maxmemory` is a limit on
153/// memory, and the right answer to a memory limit on a system with a file under
154/// it is to move data to the file. Throwing data away is the right answer to a
155/// limit on the file, and this is that limit.
156///
157/// Minus one is no limit and is the default, so a server that never sets this
158/// grows until the disk is full and then refuses writes, which is what a
159/// database does. Zero is a real setting and it means the file may hold nothing,
160/// so migration cannot make room and eviction is all that is left, which is
161/// Redis exactly and is the documented setting for a drop in cache.
162const MAXSTORE: &str = "maxstore";
163
164/// Where the server writes, which `BACKUP LIST` answers paths under.
165///
166/// On its own for a fourth reason: it is readable and not writable, and it is
167/// not writable in a way of its own. Redis calls it a protected config, which
168/// means `CONFIG SET dir` is refused with a sentence about protection rather
169/// than about immutability unless the server was started with protected configs
170/// enabled. That distinction is copied, because the two messages are what an
171/// operator reads when a `CONFIG SET` does not take.
172const DIR: &str = "dir";
173
174/// What `SAVE` writes, under [`DIR`].
175///
176/// Protected in the same way and for a weaker version of the same reason: a
177/// server whose file name moved under a running backup script leaves a file
178/// nothing goes looking for. Redis protects it too, so `CONFIG SET dbfilename`
179/// is refused there as well without protected configs turned on.
180const DBFILENAME: &str = "dbfilename";
181
182/// The password every connection is asked for, empty when none is.
183///
184/// Writable, and the one setting here whose value is a secret. It reads back in
185/// the clear, which is what a real server does and is not an oversight of one:
186/// an operator who can send `CONFIG GET` on this server can already read
187/// everything in it.
188const REQUIREPASS: &str = "requirepass";
189
190/// How long a sealed backup is kept before it cleans itself up.
191///
192/// Seconds, and zero is the default and means it is kept until somebody says
193/// `BACKUP CLEANUP`. Writable, since a backup taken by a script that then died
194/// is exactly the thing this is for and setting it afterwards has to work.
195const SEALED_TTL: &str = "backup-sealed-ttl";
196
197/// The file the users are read from and written back to, empty when there is
198/// none.
199///
200/// Immutable, which is Redis's rule for it and is the right one: an operator who
201/// could point a running server at a different ACL file would have a way of
202/// changing who may reach it that is invisible to everything watching the file
203/// it was started with.
204const ACLFILE: &str = "aclfile";
205
206/// How many refusals `ACL LOG` keeps, and nought keeps none.
207///
208/// Writable, because the reason to change it is that something is happening
209/// right now and the log is either too short to see it or long enough to be in
210/// the way.
211const ACLLOG_MAX_LEN: &str = "acllog-max-len";
212
213/// Whether a new selector starts out allowed every channel.
214///
215/// Writable, and writing it changes nothing that already exists: it is read at
216/// the moment a selector is made and never looked at again. Redis 6 behaved as
217/// `allchannels` and Redis 7 changed the default to `resetchannels`, which is
218/// what this setting is for, and yo starts where Redis 7 did.
219const ACL_PUBSUB_DEFAULT: &str = "acl-pubsub-default";
220
221/// The two words `acl-pubsub-default` is allowed to be, in Redis's order.
222const CHANNEL_DEFAULTS: [&str; 2] = ["allchannels", "resetchannels"];
223
224/// Whether a client's write is refused while this server follows a master.
225///
226/// Writable, and on by default, which is Redis's default and is the only safe
227/// one: a write that lands on a replica is a write the master never hears about
228/// and that the next full resync throws away. Turning it off is a real thing to
229/// do and is what a cache in front of a slow master wants.
230///
231/// The `slave` spelling is the name this had before Redis renamed it and it
232/// still answers to both, so this does too, the same way the size ladder answers
233/// to `ziplist`. Two names, one setting.
234const REPLICA_READ_ONLY: [&str; 2] = ["replica-read-only", "slave-read-only"];
235
236/// The password and user the link to a master authenticates with.
237///
238/// Writable and both empty by default, which is a master that asks for nothing.
239/// A user without a password is not a thing to send, so an empty `masterauth`
240/// means the link sends no `AUTH` at all whatever `masteruser` says. They read
241/// back in the clear for the same reason `requirepass` does.
242const MASTERAUTH: &str = "masterauth";
243/// The user half of [`MASTERAUTH`], for a master with an ACL rather than a
244/// password.
245const MASTERUSER: &str = "masteruser";
246
247/// The two words a yes or no setting is allowed to be.
248const BOOLS: [&str; 2] = ["yes", "no"];
249
250/// The two cluster settings that really move, both of them a yes or a no.
251///
252/// `cluster-require-full-coverage` decides whether a node with a hole somewhere
253/// in the cluster refuses everything or only the keys in the hole, and
254/// `cluster-allow-reads-when-down` decides whether a node that has decided the
255/// cluster is down still answers reads. Both take effect on the next command,
256/// which is what an operator digging a cluster out of a hole wants.
257const CLUSTER_COVERAGE: [&str; 2] = [
258    "cluster-require-full-coverage",
259    "cluster-allow-reads-when-down",
260];
261
262/// Whether this server is a cluster node, which is fixed for the life of the
263/// process and is Redis's rule.
264///
265/// A server that could be turned into a cluster node while it was holding keys
266/// would be a server whose keys were suddenly in slots it does not own, so
267/// `CONFIG SET` refuses it and the only way to set it is at startup.
268const CLUSTER_ENABLED: &str = "cluster-enabled";
269
270/// Where a cluster node writes its table, which it was given at startup.
271const CLUSTER_CONFIG_FILE: &str = "cluster-config-file";
272
273/// Which classes of keyspace change are published, and on which two channels.
274///
275/// On its own for a fifth reason: it is the only setting whose value is neither
276/// a number nor one of a fixed list of words, but a set of characters that reads
277/// back in a different spelling from the one it was written in. `CONFIG SET
278/// notify-keyspace-events KEA` reads back as `AKE`. See the `notify` module for
279/// what each character means and why the order is what it is.
280const NOTIFY: &str = "notify-keyspace-events";
281
282/// Read a byte count the way `CONFIG SET maxmemory` reads one.
283///
284/// This is Redis's `memtoull`. Digits, then an optional unit that is not case
285/// sensitive: nothing or `b` is bytes, `k` is a thousand and `kb` is a kibibyte,
286/// and the same pairing again for `m` and `g`. The two spellings meaning
287/// different numbers is a trap and it is Redis's trap, so it is repeated here
288/// rather than tidied up.
289///
290/// A unit that overflows clamps rather than failing, which is upstream's
291/// `ULLONG_MAX` arm. There is no sign: a leading minus is refused before the
292/// digits are read, so `maxmemory -1` is not a very large number.
293///
294/// Public because `yodb serve` takes the same limits on the command line that
295/// `CONFIG SET` takes at runtime, and a server that accepts `100mb` from one and
296/// not the other, or reads it as a different number, is a server that gets
297/// misconfigured. One parser, one answer.
298#[must_use]
299pub fn parse_memory(value: &[u8]) -> Option<u64> {
300    let split = value
301        .iter()
302        .position(|b| !b.is_ascii_digit())
303        .unwrap_or(value.len());
304    let (digits, unit) = value.split_at(split);
305    if digits.is_empty() {
306        return None;
307    }
308    let mul: u64 = match unit {
309        [] => 1,
310        u if u.eq_ignore_ascii_case(b"b") => 1,
311        u if u.eq_ignore_ascii_case(b"k") => 1000,
312        u if u.eq_ignore_ascii_case(b"kb") => 1024,
313        u if u.eq_ignore_ascii_case(b"m") => 1000 * 1000,
314        u if u.eq_ignore_ascii_case(b"mb") => 1024 * 1024,
315        u if u.eq_ignore_ascii_case(b"g") => 1000 * 1000 * 1000,
316        u if u.eq_ignore_ascii_case(b"gb") => 1024 * 1024 * 1024,
317        _ => return None,
318    };
319    let mut n: u64 = 0;
320    for d in digits {
321        n = n.saturating_mul(10).saturating_add(u64::from(d - b'0'));
322    }
323    Some(n.saturating_mul(mul))
324}
325
326/// Every policy name, joined the way `CONFIG SET` lists them when it refuses one.
327///
328/// This is a formatter and not a string because the error path should not touch
329/// the allocator, and it walks [`Policy::ALL`] rather than spelling the ten names
330/// out again so the two cannot drift apart. The order is the order in Redis's own
331/// enum table, which is the whole reason `Policy::ALL` is written down.
332struct PolicyNames;
333
334impl core::fmt::Display for PolicyNames {
335    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
336        for (at, policy) in Policy::ALL.iter().enumerate() {
337            if at > 0 {
338                f.write_str(", ")?;
339            }
340            f.write_str(policy.name())?;
341        }
342        Ok(())
343    }
344}
345
346/// Run one connection or server command.
347pub(super) fn execute(
348    server: &Server,
349    session: &mut Session,
350    spec: &Spec,
351    args: Args<'_>,
352    out: &mut Out,
353) -> Result<Flow> {
354    match spec.name {
355        // The arity in the table is a minimum of one, and a real server then
356        // refuses a second argument as a wrong number of them.
357        "ping" => {
358            if args.len() > 2 {
359                return Err(args::wrong_arity("ping"));
360            }
361            // A RESP2 connection in subscribe mode is answered a two element
362            // array with `pong` in front, so that everything reaching a
363            // subscribed client on RESP2 has the same shape. The one place a
364            // command in this file cares what the connection has subscribed to.
365            if super::pubsub::ping(session, args, out) {
366                return Ok(Flow::Continue);
367            }
368            if args.len() == 2 {
369                out.bulk(args.get(1));
370            } else {
371                out.simple(b"PONG");
372            }
373        }
374        "echo" => out.bulk(args.get(1)),
375        "acl" => acl::execute(server, session, args, out)?,
376        "auth" => auth::execute(server, session, args, out)?,
377        "debug" => debug::execute(server, session, args, out)?,
378        "memory" => super::memory::execute(server, session, args, out)?,
379        "replconf" => super::repl::replconf(server, session, args, out)?,
380        "psync" | "sync" => super::repl::psync(server, session, args, out)?,
381        "replicaof" | "slaveof" => super::follow::replicaof(server, args, out)?,
382        "failover" => super::failover::execute(server, args, out)?,
383        "cluster" => super::cluster::execute(server, session, args, out)?,
384        // The three connection commands cluster mode adds. `ASKING` is the one
385        // that does anything: it says the next command is allowed into a slot
386        // this node is receiving and does not own yet, which is how a client
387        // follows an `ASK` it was told.
388        //
389        // `READONLY` and `READWRITE` say whether this connection will take
390        // reads from a replica rather than being redirected to the master, and
391        // on a node that is nobody's replica, which is every node here until the
392        // bus is in, both of them are an `OK` and nothing else. That is what a
393        // real master answers too, so a client library that sends `READONLY` on
394        // connect gets the same answer from both.
395        "asking" => {
396            if !server.cluster_enabled() {
397                return Err(super::cluster::disabled());
398            }
399            session.ask_next();
400            out.ok();
401        }
402        "readonly" | "readwrite" => {
403            if !server.cluster_enabled() {
404                return Err(super::cluster::disabled());
405            }
406            out.ok();
407        }
408        "hello" => hello(server, session, args, out)?,
409        "select" => {
410            // A cluster has one database and the slots are how it is cut up, so
411            // moving to another one would be moving to a database no slot points
412            // at. Database nought is still allowed, because a client library
413            // that sends SELECT 0 on connect is asking for where it already is.
414            let n = args.int(1)?;
415            if server.cluster_enabled() && n != 0 {
416                return Err(Error::new(
417                    Code::Invalid,
418                    "SELECT is not allowed in cluster mode",
419                ));
420            }
421            let ok = usize::try_from(n).is_ok_and(|n| n < DATABASES);
422            if !ok {
423                return Err(Error::new(Code::Invalid, "DB index is out of range"));
424            }
425            session.db = n as usize;
426            out.ok();
427        }
428        "reset" => {
429            // Everything a connection carries goes back to what it was when it
430            // was opened, and that includes the protocol: a connection that
431            // said `HELLO 3` is speaking RESP2 again after this.
432            //
433            // The transaction and the watches go first because letting go of a
434            // watch is a change to the server and not to the connection, so
435            // clearing the list here without saying so would leave rows on the
436            // server that nobody is watching. `RESET` inside `MULTI` answers
437            // `+RESET` and leaves no transaction, which is why it is one of the
438            // six commands a transaction does not queue.
439            // The subscriptions go with them, and for the same reason: a
440            // subscription is a row on the server naming this connection, so
441            // clearing the connection's list alone would leave the server
442            // delivering into a slot that is not listening any more.
443            // And the monitor with them, which is the one way out of monitor
444            // mode short of closing the socket. `RESET` still answers `+RESET`
445            // on a connection that was one, because the reply belongs to the
446            // client the connection has just gone back to being.
447            multi::release(server, session);
448            super::pubsub::release(server, session);
449            if session.monitoring() {
450                server.watch_no_more(session.row());
451            }
452            session.reset();
453            // Including the password, which is what `RESET` means by putting
454            // the connection back the way it was accepted: on a server with a
455            // password the client has to send `AUTH` again, and on a server
456            // without one it never had to.
457            session.admit(!server.guarded());
458            out.set_proto(Proto::Resp2);
459            out.simple(b"RESET");
460        }
461        // The reply goes out before the socket closes, which is why this is a
462        // flow answer and not something the body does to the connection.
463        "quit" => {
464            out.ok();
465            return Ok(Flow::Close);
466        }
467        // Every command on the server, from here on, on this connection. The
468        // reply is `OK` once and nothing after it, and a connection that sends
469        // it twice is answered nothing at all the second time, which is a real
470        // server's behaviour and not an oversight of one.
471        "monitor" => {
472            // A transaction replaying this has been promised a reply for every
473            // command it queued, and a connection that has turned into a feed
474            // cannot give one. A real server refuses it in the same words.
475            if session.running() {
476                return Err(Error::new(
477                    Code::Invalid,
478                    "MONITOR isn't allowed for DENY BLOCKING client",
479                ));
480            }
481            if server.watch_all(session.row()) {
482                out.ok();
483            }
484        }
485        "client" => return super::client::execute(server, session, spec, args, out),
486        "command" => command(args, out)?,
487        "config" => config(server, args, out)?,
488        "info" => info(server, args, out),
489        // A key that is past its deadline and has not been read since is still
490        // counted, which is what Redis does too: `DBSIZE` is the size of the
491        // dictionary and not a walk over it. Redis has an active expiry cycle
492        // that takes those keys out within a tick or so and we do not yet, so
493        // the two servers disagree for as long as a dead key sits unread. That
494        // gap closes with the maintenance slice rather than with a count here,
495        // because a count here would be O(N) on a command that is O(1)
496        // everywhere else.
497        "dbsize" => out.int(server.dbs[session.db].len() as i64),
498        "flushall" => {
499            flush_mode(args)?;
500            for db in &server.dbs {
501                db.clear();
502            }
503            server.search.lock().clear();
504            server.cursors.lock().wipe();
505            out.ok();
506        }
507        // The search indexes go too, and they go whichever database this is.
508        // An index that only ever followed keys on database zero is dropped by
509        // a `FLUSHDB` on database nine, which is measured against a real server
510        // rather than reasoned about: the module hangs its callback on the
511        // flush event without looking at which database flushed.
512        "flushdb" => {
513            flush_mode(args)?;
514            server.dbs[session.db].clear();
515            server.search.lock().clear();
516            server.cursors.lock().wipe();
517            out.ok();
518        }
519        // Two databases change places and no key moves. What is in the stripes
520        // is exchanged and the databases stay where they are, so this costs two
521        // pointer sized writes per stripe whatever is in either of them, which
522        // is what makes `SWAPDB` fast and dangerous at the same time.
523        //
524        // No connection is told. A client on database zero is still on database
525        // zero and is now looking at what used to be database one, which is the
526        // whole point of the command and is why Redis calls it dangerous. A
527        // client parked in `BLPOP` remembers the database index it blocked on
528        // and not the database, so it wakes up against the swapped in one, which
529        // is Redis's behaviour and falls out of the index being what is stored.
530        "swapdb" => {
531            if server.cluster_enabled() {
532                return Err(Error::new(
533                    Code::Invalid,
534                    "SWAPDB is not allowed in cluster mode",
535                ));
536            }
537            let first = db_index(args.get(1), "invalid first DB index")?;
538            let second = db_index(args.get(2), "invalid second DB index")?;
539            server.striped(first).swap_with(server.striped(second));
540            out.ok();
541        }
542        "time" => time(out),
543        // The four commands about writing the dataset to a file and the one
544        // about who this server is, all in the `persist` module because a client
545        // asks them together.
546        "save" | "bgsave" | "bgrewriteaof" | "lastsave" | "role" => {
547            persist::execute(server, session, spec, args, out)?;
548        }
549        "backup" => backup::execute(server, args, out)?,
550        "shutdown" => return shutdown(server, args),
551        _ => return Err(args::unknown_command(args)),
552    }
553    Ok(Flow::Continue)
554}
555
556/// `TIME`, which is two bulk strings and not one integer.
557///
558/// Seconds first and then microseconds within that second, both written out as
559/// decimal text, which is a shape nobody would choose today and is the shape
560/// every client library parses.
561///
562/// It reads the wall clock rather than the coarse clock the keyspace uses. The
563/// coarse one is a cached millisecond that a background tick refreshes, which is
564/// the right trade for deciding whether a key has expired and the wrong one for
565/// a command whose entire job is to say what time it is. A client that calls
566/// `TIME` twice in a row and gets the same microsecond has been lied to.
567fn time(out: &mut Out) {
568    let now = SystemTime::now()
569        .duration_since(UNIX_EPOCH)
570        .unwrap_or_default();
571    out.array(2);
572    out.bulk(now.as_secs().to_string().as_bytes());
573    out.bulk(now.subsec_micros().to_string().as_bytes());
574}
575
576// ---------------------------------------------------------------- SHUTDOWN
577
578/// `SHUTDOWN [NOSAVE | SAVE] [NOW] [FORCE] [ABORT]`.
579///
580/// On success this writes nothing at all and the connection closes under the
581/// client, which is what a server that has stopped looks like from the outside
582/// and is what every client library already expects. There is no `OK`, because
583/// an `OK` would be a promise made by a process that is about to not exist.
584///
585/// `SAVE` writes the file [`persist`] writes, and it is the only word here that
586/// does anything. `NOSAVE` is the default rather than an instruction, which is
587/// the same answer `save` gets from `CONFIG GET`: this server has no save points
588/// and never will, because what durability there is belongs to the file
589/// underneath and is already on disk by the time a command returns. So there is
590/// nothing for `NOSAVE` to skip and the file `SAVE` asks for is an export
591/// somebody wants a copy of on the way down. `NOW` and `FORCE` are about not
592/// waiting for replicas and about going anyway when a save failed, and neither
593/// has anything to wait for or to fail here.
594///
595/// # Errors
596///
597/// [`Code::Invalid`] for a word that is not one of the five, for `SAVE` and
598/// `NOSAVE` in the same call, and for `ABORT` alongside any other flag, all of
599/// which is what 8.10.1 says. `ABORT` on its own gets Redis's message for a
600/// cancel with nothing to cancel, and here that is not a state that can be
601/// reached rather than one that happens to be empty: a shutdown is decided and
602/// done inside one turn of the loop, so there is never a window in which one is
603/// in progress and a second client could call it off.
604fn shutdown(server: &Server, args: Args<'_>) -> Result<Flow> {
605    let (mut save, mut nosave, mut abort, mut other) = (false, false, false, false);
606    for at in 1..args.len() {
607        let arg = args.get(at);
608        match () {
609            () if is(arg, b"save") => save = true,
610            () if is(arg, b"nosave") => nosave = true,
611            () if is(arg, b"abort") => abort = true,
612            () if is(arg, b"now") || is(arg, b"force") => other = true,
613            () => return Err(args::syntax()),
614        }
615    }
616    // Repeating one is fine and contradicting yourself is not, and `ABORT` says
617    // to do nothing so it cannot be combined with a word about how to do it.
618    if (save && nosave) || (abort && (save || nosave || other)) {
619        return Err(args::syntax());
620    }
621    if abort {
622        return Err(Error::new(Code::Invalid, "No shutdown in progress."));
623    }
624    if save {
625        persist::on_shutdown(server);
626    }
627    server.stop();
628    // Closing is what stops anything the client pipelined behind this from
629    // being answered by a server that is on its way out.
630    Ok(Flow::Close)
631}
632
633// ------------------------------------------------------------------- FLUSH
634
635/// Check the optional `ASYNC` or `SYNC` on `FLUSHALL` and `FLUSHDB`.
636///
637/// Both are accepted and neither changes anything. On a real server the choice
638/// is whether the freeing happens on the connection's thread or on the lazy
639/// free thread, and either way the keyspace is empty before the `OK` goes out.
640/// That is the whole of what a client can observe, and it is the same here,
641/// so taking the word and ignoring it is answering the question rather than
642/// pretending to.
643///
644/// # Errors
645///
646/// [`Code::Invalid`] for a third argument, or for a second that is neither
647/// word, which is what Redis says about both.
648fn flush_mode(args: Args<'_>) -> Result<()> {
649    if args.len() == 1 {
650        return Ok(());
651    }
652    if args.len() > 2 || !(is(args.get(1), b"async") || is(args.get(1), b"sync")) {
653        return Err(args::syntax());
654    }
655    Ok(())
656}
657
658/// One of `SWAPDB`'s two database indexes, with Redis's two different
659/// complaints about it.
660///
661/// A word that is not a number, or a number too big to be a database index on a
662/// server that stores the index in a C `int`, gets the caller's message, which
663/// says which of the two arguments was wrong. A number that is a plausible index
664/// and is not one of ours gets the same out of range message `SELECT` gives. The
665/// split looks arbitrary and it is Redis's, and the reason for it is that the
666/// first check happens while reading the argument and the second happens inside
667/// the swap, so only the first one knows which argument it was looking at.
668fn db_index(arg: &[u8], bad: &'static str) -> Result<usize> {
669    let n = parse_i64(arg)
670        .filter(|n| i32::try_from(*n).is_ok())
671        .ok_or_else(|| Error::new(Code::Invalid, bad))?;
672    usize::try_from(n)
673        .ok()
674        .filter(|n| *n < DATABASES)
675        .ok_or_else(|| Error::new(Code::Invalid, "DB index is out of range"))
676}
677
678// ------------------------------------------------------------------- HELLO
679
680/// `HELLO [protover [AUTH username password] [SETNAME name]]`.
681///
682/// The order of the three things that can go wrong here is the reference's and
683/// is worth writing down, because it is not the order they appear in. The
684/// protocol version is read and refused first, so `HELLO 9 AUTH default right`
685/// on a connection that has not authenticated is a `NOPROTO` and leaves the
686/// connection unauthenticated. The `AUTH` option is applied next, so a wrong
687/// password is a `WRONGPASS` and the protocol stays where it was. Only then does
688/// the connection have to be authenticated at all, which is what makes a bare
689/// `HELLO` on a server with a password a `NOAUTH` rather than a greeting.
690fn hello(server: &Server, session: &mut Session, args: Args<'_>, out: &mut Out) -> Result<()> {
691    // The version this call agreed on, if it named one. Held rather than applied
692    // where it is read, because the reply buffer must not change protocol until
693    // the password below has been asked for and answered.
694    let mut agreed = None;
695    if args.len() > 1 {
696        let v = parse_i64(args.get(1)).ok_or_else(|| {
697            Error::new(
698                Code::Invalid,
699                "Protocol version is not an integer or out of range",
700            )
701        })?;
702        let Some(proto) = Proto::from_version(v) else {
703            // `NOPROTO` rather than `ERR`, and it is the one error in this file
704            // written straight into the buffer: the prefix is part of what the
705            // client branches on, and it is the only place in the engine that
706            // needs this one.
707            out.error(b"NOPROTO unsupported protocol version");
708            return Ok(());
709        };
710        let mut i = 2;
711        while i < args.len() {
712            let o = args.get(i);
713            if is(o, b"AUTH") && i + 2 < args.len() {
714                if !acl::authenticate(server, session, args.get(i + 1), args.get(i + 2), args, out)
715                {
716                    out.error(b"WRONGPASS invalid username-password pair or user is disabled.");
717                    return Ok(());
718                }
719                i += 3;
720            } else if is(o, b"SETNAME") && i + 1 < args.len() {
721                session.set_name(args.get(i + 1));
722                i += 2;
723            } else {
724                return Err(yo_alloc::allow(|| {
725                    Error::fmt(
726                        Code::Invalid,
727                        format_args!(
728                            "Syntax error in HELLO option '{}'",
729                            String::from_utf8_lossy(o)
730                        ),
731                    )
732                }));
733            }
734        }
735        agreed = Some(proto);
736    }
737
738    if server.guarded() && !session.authenticated() {
739        // Its own sentence rather than the one every other command gets, because
740        // a client that speaks RESP3 has to send `HELLO` before it can send
741        // `AUTH` and would otherwise be told to do the thing it is doing.
742        out.error(auth::HELLO_NOAUTH.as_bytes());
743        return Ok(());
744    }
745    // The reply is written in the protocol that was just agreed, not the one the
746    // request arrived in.
747    if let Some(proto) = agreed {
748        out.set_proto(proto);
749    }
750
751    let proto = out.proto().version();
752    out.map(7);
753    out.bulk(b"server");
754    out.bulk(REPORTED_SERVER.as_bytes());
755    out.bulk(b"version");
756    out.bulk(REPORTED_VERSION.as_bytes());
757    out.bulk(b"proto");
758    out.int(proto);
759    out.bulk(b"id");
760    out.int(session.id as i64);
761    out.bulk(b"mode");
762    out.bulk(b"standalone");
763    out.bulk(b"role");
764    out.bulk(b"master");
765    out.bulk(b"modules");
766    out.array(0);
767    Ok(())
768}
769
770// ----------------------------------------------------------------- COMMAND
771
772/// `COMMAND [COUNT|LIST|INFO|DOCS|GETKEYS|HELP]`.
773fn command(args: Args<'_>, out: &mut Out) -> Result<()> {
774    if args.len() == 1 {
775        out.array(table::COMMANDS.len());
776        for spec in table::COMMANDS {
777            write_spec(out, spec);
778        }
779        return Ok(());
780    }
781    let sub = args.get(1);
782    if is(sub, b"COUNT") {
783        out.int(table::COMMANDS.len() as i64);
784    } else if is(sub, b"INFO") {
785        if args.len() == 2 {
786            out.array(table::COMMANDS.len());
787            for spec in table::COMMANDS {
788                write_spec(out, spec);
789            }
790        } else {
791            out.array(args.len() - 2);
792            for i in 2..args.len() {
793                match table::lookup(args.get(i)) {
794                    Some(spec) => write_spec(out, spec),
795                    // A name nobody has heard of is a null in the list rather
796                    // than an error, so one bad name in a batch does not cost
797                    // the client the other answers. It is the plain null and
798                    // not the array one, which on RESP2 is the difference
799                    // between `$-1` and `*-1` and is what a real server sends.
800                    None => out.nil(),
801                }
802            }
803        }
804    } else if is(sub, b"LIST") {
805        list(args, out)?;
806    } else if is(sub, b"DOCS") {
807        docs(args, out);
808    } else if is(sub, b"GETKEYS") {
809        getkeys(args, out, false)?;
810    } else if is(sub, b"GETKEYSANDFLAGS") {
811        getkeys(args, out, true)?;
812    } else if is(sub, b"HELP") {
813        help(out, COMMAND_HELP);
814    } else {
815        return Err(args::unknown_subcommand(sub, "COMMAND"));
816    }
817    Ok(())
818}
819
820/// `COMMAND LIST [FILTERBY MODULE m|ACLCAT c|PATTERN p]`.
821fn list(args: Args<'_>, out: &mut Out) -> Result<()> {
822    if args.len() == 2 {
823        out.array(table::COMMANDS.len());
824        for spec in table::COMMANDS {
825            out.bulk(spec.name.as_bytes());
826        }
827        return Ok(());
828    }
829    if args.len() != 5 || !is(args.get(2), b"FILTERBY") {
830        return Err(args::syntax());
831    }
832    let (how, what) = (args.get(3), args.get(4));
833    let keep = |spec: &Spec| {
834        if is(how, b"MODULE") {
835            // Nothing here came from a module, so every filter by one is empty.
836            false
837        } else if is(how, b"ACLCAT") {
838            spec.acl
839                .iter()
840                .any(|c| c.len() == what.len() + 1 && c.as_bytes()[1..].eq_ignore_ascii_case(what))
841        } else {
842            glob::matches(what, spec.name.as_bytes())
843        }
844    };
845    if !is(how, b"MODULE") && !is(how, b"ACLCAT") && !is(how, b"PATTERN") {
846        return Err(args::syntax());
847    }
848    out.array(table::COMMANDS.iter().filter(|s| keep(s)).count());
849    for spec in table::COMMANDS.iter().filter(|s| keep(s)) {
850        out.bulk(spec.name.as_bytes());
851    }
852    Ok(())
853}
854
855/// `COMMAND DOCS [name ...]`.
856///
857/// The arguments field a real server sends is left out. It describes the shape
858/// of every option of every command in a form nothing but `redis-cli`'s hinting
859/// reads, and getting it wrong would be worse than not sending it, since a
860/// client that finds the field trusts it.
861fn docs(args: Args<'_>, out: &mut Out) {
862    if args.len() == 2 {
863        out.map(table::COMMANDS.len());
864        for spec in table::COMMANDS {
865            write_docs(out, spec);
866        }
867        return;
868    }
869    let found = (2..args.len())
870        .filter(|&i| table::lookup(args.get(i)).is_some())
871        .count();
872    out.map(found);
873    for i in 2..args.len() {
874        if let Some(spec) = table::lookup(args.get(i)) {
875            write_docs(out, spec);
876        }
877    }
878}
879
880/// One command's documentation, as the name and then the map about it.
881fn write_docs(out: &mut Out, spec: &Spec) {
882    out.bulk(spec.name.as_bytes());
883    out.map(4);
884    out.bulk(b"summary");
885    out.bulk(spec.summary.as_bytes());
886    out.bulk(b"since");
887    out.bulk(spec.since.as_bytes());
888    out.bulk(b"group");
889    out.bulk(spec.group.as_bytes());
890    out.bulk(b"complexity");
891    out.bulk(spec.complexity.as_bytes());
892}
893
894/// `COMMAND GETKEYS <full command>` and `COMMAND GETKEYSANDFLAGS <full command>`.
895///
896/// This is how a cluster aware client routes a command it does not have a rule
897/// for, so a wrong answer here is a client that sends a write to the wrong
898/// node. The answer comes off the key specs, which is the same place the ACL
899/// reads, so the two can never drift apart.
900///
901/// The three errors are the reference's own and they mean different things. A
902/// name nobody registered is one, a command that never takes a key whatever it
903/// is sent is another, and a command that does take keys and was handed
904/// arguments the specs cannot resolve is the third. Only the last is about what
905/// was actually typed.
906fn getkeys(args: Args<'_>, out: &mut Out, flags: bool) -> Result<()> {
907    let sub = if flags { "getkeysandflags" } else { "getkeys" };
908    if args.len() < 3 {
909        return Err(args::wrong_arity_sub("command", sub));
910    }
911    let inner = args.get(2);
912    let spec = table::lookup(inner)
913        .ok_or_else(|| Error::new(Code::Unsupported, "Invalid command specified"))?;
914    if !keyspec::takes_keys(spec, args, 2) {
915        return Err(Error::new(
916            Code::Invalid,
917            "The command has no key arguments",
918        ));
919    }
920    let argc = args.len() - 2;
921    if !table::arity_ok(spec, argc) {
922        return Err(Error::new(
923            Code::Invalid,
924            "Invalid number of arguments specified for command",
925        ));
926    }
927    // Three specs at most a command and one run each, so the answer is worked
928    // out into a fixed array rather than a list that grows. A run is a first
929    // argument and a count, so a hundred keys behind a count is still one of
930    // these.
931    let mut runs = [None; 4];
932    let mut at = 0;
933    let whole = keyspec::find(spec, args, 2, &mut |run| {
934        if at < runs.len() {
935            runs[at] = Some(run);
936            at += 1;
937        }
938    });
939    let found: usize = runs.iter().flatten().map(|r| r.count).sum();
940    // A command that resolves to nothing is a syntax error, unless it is one of
941    // the six that may honestly have no keys, which is the script family: `EVAL
942    // body 0` is an ordinary thing to write and answers an empty list.
943    if (!whole || found == 0) && !spec.flags.contains(&"no_mandatory_keys") {
944        return Err(Error::new(
945            Code::Invalid,
946            "Invalid arguments specified for command",
947        ));
948    }
949    let found = if whole { found } else { 0 };
950    out.array(found);
951    if found == 0 {
952        return Ok(());
953    }
954    for run in runs.iter().flatten() {
955        for i in 0..run.count {
956            let key = args.get(run.first + i * run.step);
957            if flags {
958                out.array(2);
959                out.bulk(key);
960                out.set(run.flags.len());
961                for f in run.flags {
962                    out.simple(f.as_bytes());
963                }
964            } else {
965                out.bulk(key);
966            }
967        }
968    }
969    Ok(())
970}
971
972/// One command, in the ten field shape `COMMAND INFO` has had since 7.0.
973///
974/// The tips and the subcommands are still empty, which is what is left of
975/// divergence D-13. The key specs are not: they say where the keys are for
976/// everything in this table, including the commands the triple above them
977/// cannot describe.
978///
979/// Five of the ten fields are sets rather than arrays, which only shows on
980/// RESP3 and shows there on every command. A set is what the reference sends
981/// for all five, and it is the honest type for them: nothing in a flag list or
982/// an acl category list is ordered or repeated.
983fn write_spec(out: &mut Out, spec: &Spec) {
984    out.array(10);
985    out.bulk(spec.name.as_bytes());
986    out.int(i64::from(spec.arity));
987    out.set(spec.flags.len());
988    for f in spec.flags {
989        out.simple(f.as_bytes());
990    }
991    out.int(i64::from(spec.first_key));
992    out.int(i64::from(spec.last_key));
993    out.int(i64::from(spec.step));
994    out.set(spec.acl.len());
995    for a in spec.acl {
996        out.simple(a.as_bytes());
997    }
998    out.set(0);
999    out.set(spec.keys.len());
1000    for key in spec.keys {
1001        write_key_spec(out, key);
1002    }
1003    out.set(0);
1004}
1005
1006/// One key spec, as the map `COMMAND INFO` reports it.
1007///
1008/// The notes come first and only when there are any, which is why the map is
1009/// three long or four rather than always four.
1010fn write_key_spec(out: &mut Out, key: &KeySpec) {
1011    out.map(if key.notes.is_empty() { 3 } else { 4 });
1012    if !key.notes.is_empty() {
1013        out.bulk(b"notes");
1014        out.bulk(key.notes.as_bytes());
1015    }
1016    out.bulk(b"flags");
1017    out.set(key.flags.len());
1018    for f in key.flags {
1019        out.simple(f.as_bytes());
1020    }
1021    out.bulk(b"begin_search");
1022    out.map(2);
1023    out.bulk(b"type");
1024    match key.begin {
1025        Begin::At(index) => {
1026            out.bulk(b"index");
1027            out.bulk(b"spec");
1028            out.map(1);
1029            out.bulk(b"index");
1030            out.int(i64::from(index));
1031        }
1032        Begin::After(word, from) => {
1033            out.bulk(b"keyword");
1034            out.bulk(b"spec");
1035            out.map(2);
1036            out.bulk(b"keyword");
1037            out.bulk(word);
1038            out.bulk(b"startfrom");
1039            out.int(i64::from(from));
1040        }
1041        Begin::Unknown => {
1042            out.bulk(b"unknown");
1043            out.bulk(b"spec");
1044            out.map(0);
1045        }
1046    }
1047    out.bulk(b"find_keys");
1048    out.map(2);
1049    out.bulk(b"type");
1050    match key.find {
1051        Find::Range { last, step, limit } => {
1052            out.bulk(b"range");
1053            out.bulk(b"spec");
1054            out.map(3);
1055            out.bulk(b"lastkey");
1056            out.int(i64::from(last));
1057            out.bulk(b"keystep");
1058            out.int(i64::from(step));
1059            out.bulk(b"limit");
1060            out.int(i64::from(limit));
1061        }
1062        Find::Counted { count, first, step } => {
1063            out.bulk(b"keynum");
1064            out.bulk(b"spec");
1065            out.map(3);
1066            out.bulk(b"keynumidx");
1067            out.int(i64::from(count));
1068            out.bulk(b"firstkey");
1069            out.int(i64::from(first));
1070            out.bulk(b"keystep");
1071            out.int(i64::from(step));
1072        }
1073        Find::Unknown => {
1074            out.bulk(b"unknown");
1075            out.bulk(b"spec");
1076            out.map(0);
1077        }
1078    }
1079}
1080
1081// ------------------------------------------------------------------ CONFIG
1082
1083/// What a ladder setting is set to now.
1084fn read_knob(db: &Keyspace, knob: Knob) -> usize {
1085    match knob {
1086        Knob::SetIntsetEntries => db.limits().max_intset_entries,
1087        Knob::SetListpackEntries => db.limits().max_listpack_entries,
1088        Knob::SetListpackValue => db.limits().max_listpack_value,
1089        Knob::HashListpackEntries => db.hash_limits().max_listpack_entries,
1090        Knob::HashListpackValue => db.hash_limits().max_listpack_value,
1091        Knob::MaxmemorySamples => db.samples(),
1092        Knob::LfuLogFactor => db.lfu().log_factor as usize,
1093        Knob::LfuDecayTime => db.lfu().decay_minutes as usize,
1094    }
1095}
1096
1097/// Move one ladder setting on one database.
1098fn write_knob(db: &mut Keyspace, knob: Knob, n: usize) {
1099    let mut set = *db.limits();
1100    let mut hash = *db.hash_limits();
1101    let mut lfu = db.lfu();
1102    match knob {
1103        Knob::SetIntsetEntries => set.max_intset_entries = n,
1104        Knob::SetListpackEntries => set.max_listpack_entries = n,
1105        Knob::SetListpackValue => set.max_listpack_value = n,
1106        Knob::HashListpackEntries => hash.max_listpack_entries = n,
1107        Knob::HashListpackValue => hash.max_listpack_value = n,
1108        Knob::MaxmemorySamples => db.set_samples(n),
1109        // Saturating rather than wrapping, because these two are read as `u32`
1110        // and a client is free to send a number that does not fit. Redis clamps
1111        // `lfu-log-factor` and `lfu-decay-time` to the same width.
1112        Knob::LfuLogFactor => lfu.log_factor = u32::try_from(n).unwrap_or(u32::MAX),
1113        Knob::LfuDecayTime => lfu.decay_minutes = u32::try_from(n).unwrap_or(u32::MAX),
1114    }
1115    db.set_limits(set);
1116    db.set_hash_limits(hash);
1117    db.set_lfu(lfu);
1118}
1119
1120/// The two things a real server says about a number it will not take.
1121///
1122/// Both name the setting the client typed and not the one it is an alias for,
1123/// so `hash-max-ziplist-entries` comes back saying `hash-max-ziplist-entries`.
1124/// A value past the range of an `i64` is the parse complaint and not the range
1125/// one, which is upstream reading it before it checks it.
1126fn bad_setting(name: &str, parsed: bool) -> Error {
1127    if parsed {
1128        Error::fmt(
1129            Code::Invalid,
1130            format_args!(
1131                "CONFIG SET failed (possibly related to argument '{name}') - argument must be between 0 and 9223372036854775807 inclusive"
1132            ),
1133        )
1134    } else {
1135        Error::fmt(
1136            Code::Invalid,
1137            format_args!(
1138                "CONFIG SET failed (possibly related to argument '{name}') - argument couldn't be parsed into an integer"
1139            ),
1140        )
1141    }
1142}
1143
1144/// `CONFIG GET|SET|RESETSTAT|REWRITE|HELP`.
1145fn config(server: &Server, args: Args<'_>, out: &mut Out) -> Result<()> {
1146    let sub = args.get(1);
1147    if is(sub, b"GET") {
1148        if args.len() < 3 {
1149            return Err(args::wrong_arity_sub("config", "get"));
1150        }
1151        let wanted =
1152            |name: &str| (2..args.len()).any(|i| glob::matches(args.get(i), name.as_bytes()));
1153        // A setting that two patterns both ask for is sent once, which is what
1154        // makes this a count of settings rather than a count of matches. The
1155        // two spellings of a ladder setting are two settings by that rule, so
1156        // `CONFIG GET hash-max-*` sends the listpack name and the ziplist name
1157        // and the same number under both, which is what a real server does.
1158        let fixed = SETTINGS.iter().filter(|(k, _)| wanted(k));
1159        let ladder = LADDER.iter().filter(|(k, _)| wanted(k));
1160        let policy = wanted(MAXMEMORY_POLICY);
1161        let limit = wanted(MAXMEMORY);
1162        let store = wanted(MAXSTORE);
1163        let where_ = wanted(DIR);
1164        let file = wanted(DBFILENAME);
1165        let pass = wanted(REQUIREPASS);
1166        let ttl = wanted(SEALED_TTL);
1167        let events = wanted(NOTIFY);
1168        let acls = wanted(ACLFILE);
1169        let logged = wanted(ACLLOG_MAX_LEN);
1170        let channels = wanted(ACL_PUBSUB_DEFAULT);
1171        // Both spellings are two settings by the same rule the ladder follows,
1172        // so `CONFIG GET *read-only*` sends the replica name and the slave name
1173        // and the same word under both.
1174        let readonly = REPLICA_READ_ONLY.map(wanted);
1175        let mauth = wanted(MASTERAUTH);
1176        let muser = wanted(MASTERUSER);
1177        // The four cluster settings, which are there on every server and not
1178        // only on a node, the same way a real server answers them: a tool asking
1179        // `CONFIG GET cluster-enabled` wants a no rather than nothing back.
1180        let coverage = CLUSTER_COVERAGE.map(wanted);
1181        let clustered = wanted(CLUSTER_ENABLED);
1182        let nodes_file = wanted(CLUSTER_CONFIG_FILE);
1183        out.map(
1184            fixed.clone().count()
1185                + ladder.clone().count()
1186                + usize::from(policy)
1187                + usize::from(limit)
1188                + usize::from(store)
1189                + usize::from(where_)
1190                + usize::from(file)
1191                + usize::from(pass)
1192                + usize::from(ttl)
1193                + usize::from(events)
1194                + usize::from(acls)
1195                + usize::from(logged)
1196                + usize::from(channels)
1197                + usize::from(readonly[0])
1198                + usize::from(readonly[1])
1199                + usize::from(mauth)
1200                + usize::from(muser)
1201                + usize::from(coverage[0])
1202                + usize::from(coverage[1])
1203                + usize::from(clustered)
1204                + usize::from(nodes_file),
1205        );
1206        for (k, v) in fixed {
1207            out.bulk(k.as_bytes());
1208            out.bulk(v.as_bytes());
1209        }
1210        for (k, knob) in ladder {
1211            out.bulk(k.as_bytes());
1212            out.bulk_int(read_knob(&server.settings(), *knob) as i64);
1213        }
1214        if policy {
1215            out.bulk(MAXMEMORY_POLICY.as_bytes());
1216            out.bulk(server.settings().policy().name().as_bytes());
1217        }
1218        if limit {
1219            // Back as a plain number of bytes whatever the client typed to set
1220            // it, which is what a real server does: `CONFIG SET maxmemory 1gb`
1221            // reads back as 1073741824.
1222            out.bulk(MAXMEMORY.as_bytes());
1223            out.bulk_int(server.maxmemory() as i64);
1224        }
1225        if store {
1226            // Minus one for no limit, and a plain number of bytes otherwise.
1227            // Zero cannot mean no limit here the way it does for `maxmemory`,
1228            // because zero is the setting that says the file holds nothing.
1229            out.bulk(MAXSTORE.as_bytes());
1230            out.bulk_int(server.maxstore().map_or(-1, |n| n as i64));
1231        }
1232        if where_ {
1233            // Absolute, which is what a real server answers too: it resolves the
1234            // directory at startup and reports the resolved one, so a client can
1235            // tell where the files are without knowing where the process was
1236            // launched from.
1237            out.bulk(DIR.as_bytes());
1238            yo_alloc::allow(|| out.bulk(server.dir().to_string_lossy().as_bytes()));
1239        }
1240        if file {
1241            // The name on its own and not the path, which is how a real server
1242            // answers it too: the two settings are joined by whoever reads them.
1243            out.bulk(DBFILENAME.as_bytes());
1244            out.bulk(persist::FILE.as_bytes());
1245        }
1246        if pass {
1247            out.bulk(REQUIREPASS.as_bytes());
1248            server.with_password(|p| out.bulk(p));
1249        }
1250        if ttl {
1251            out.bulk(SEALED_TTL.as_bytes());
1252            out.bulk_int(server.backup().ttl() as i64);
1253        }
1254        if events {
1255            // The flags and not the string that set them, which is what a real
1256            // server answers too and is why the parser has a formatter next to
1257            // it rather than the text being kept.
1258            out.bulk(NOTIFY.as_bytes());
1259            let (buf, len) = notify::format(server.notify_flags());
1260            out.bulk(&buf[..len]);
1261        }
1262        if acls {
1263            // Exactly what the server was started with, which for nearly every
1264            // server is nothing at all. Not resolved to an absolute path the way
1265            // `dir` is, because a real server answers what it was given here.
1266            out.bulk(ACLFILE.as_bytes());
1267            yo_alloc::allow(|| {
1268                out.bulk(
1269                    server
1270                        .aclfile()
1271                        .map(|p| p.to_string_lossy())
1272                        .unwrap_or_default()
1273                        .as_bytes(),
1274                );
1275            });
1276        }
1277        if logged {
1278            out.bulk(ACLLOG_MAX_LEN.as_bytes());
1279            out.bulk_int(server.acl_log().max_len() as i64);
1280        }
1281        if channels {
1282            out.bulk(ACL_PUBSUB_DEFAULT.as_bytes());
1283            out.bulk(CHANNEL_DEFAULTS[usize::from(!server.users().open_channels())].as_bytes());
1284        }
1285        for (name, asked) in REPLICA_READ_ONLY.iter().zip(readonly) {
1286            if asked {
1287                out.bulk(name.as_bytes());
1288                out.bulk(BOOLS[usize::from(!server.replica_read_only_setting())].as_bytes());
1289            }
1290        }
1291        if coverage[0] {
1292            out.bulk(CLUSTER_COVERAGE[0].as_bytes());
1293            out.bulk(BOOLS[usize::from(!server.cluster_full_coverage())].as_bytes());
1294        }
1295        if coverage[1] {
1296            out.bulk(CLUSTER_COVERAGE[1].as_bytes());
1297            out.bulk(BOOLS[usize::from(!server.cluster_reads_when_down())].as_bytes());
1298        }
1299        if clustered {
1300            out.bulk(CLUSTER_ENABLED.as_bytes());
1301            out.bulk(BOOLS[usize::from(!server.cluster_enabled())].as_bytes());
1302        }
1303        if nodes_file {
1304            out.bulk(CLUSTER_CONFIG_FILE.as_bytes());
1305            yo_alloc::allow(|| out.bulk(server.cluster_file().as_bytes()));
1306        }
1307        if mauth || muser {
1308            server.with_master_auth(|user, pass| {
1309                if mauth {
1310                    out.bulk(MASTERAUTH.as_bytes());
1311                    out.bulk(pass);
1312                }
1313                if muser {
1314                    out.bulk(MASTERUSER.as_bytes());
1315                    out.bulk(user);
1316                }
1317            });
1318        }
1319    } else if is(sub, b"SET") {
1320        // Too few is a wrong number of arguments and an odd number is a syntax
1321        // error, which is not the same sentence and is not the same rule. A
1322        // real server counts the pairs after it has decided there is at least
1323        // one, so `CONFIG SET appendonly` is an arity error and `CONFIG SET
1324        // appendonly no maxmemory` is a syntax one.
1325        if args.len() < 4 {
1326            return Err(args::wrong_arity_sub("config", "set"));
1327        }
1328        if !args.len().is_multiple_of(2) {
1329            return Err(args::syntax());
1330        }
1331        // Every pair is checked before any of them is applied, because a real
1332        // server takes the whole `CONFIG SET` or none of it. `CONFIG SET
1333        // hash-max-listpack-entries 7 set-max-listpack-entries abc` leaves the
1334        // hash setting where it was, which was checked rather than assumed.
1335        let mut writes = [None; 16];
1336        let mut count = 0;
1337        let mut policy = None;
1338        let mut limit = None;
1339        let mut store = None;
1340        let mut ttl = None;
1341        let mut events = None;
1342        let mut password = None;
1343        let mut logged = None;
1344        let mut channels = None;
1345        let mut readonly = None;
1346        let mut coverage: [Option<bool>; 2] = [None, None];
1347        let mut mauth = None;
1348        let mut muser = None;
1349        let mut i = 2;
1350        while i < args.len() {
1351            let (name, value) = (args.get(i), args.get(i + 1));
1352            i += 2;
1353            if is(name, MAXMEMORY.as_bytes()) {
1354                let Some(bytes) = parse_memory(value) else {
1355                    return Err(Error::fmt(
1356                        Code::Invalid,
1357                        format_args!(
1358                            "CONFIG SET failed (possibly related to argument '{MAXMEMORY}') - argument must be a memory value"
1359                        ),
1360                    ));
1361                };
1362                limit = Some(bytes);
1363                continue;
1364            }
1365            if is(name, MAXSTORE.as_bytes()) {
1366                // `-1` before the memory parser sees it, because that parser
1367                // refuses a sign and should keep refusing one: `maxmemory -1`
1368                // is not a very large number and never was.
1369                let parsed = if value == b"-1" {
1370                    Some(None)
1371                } else {
1372                    parse_memory(value).map(Some)
1373                };
1374                let Some(bytes) = parsed else {
1375                    return Err(Error::fmt(
1376                        Code::Invalid,
1377                        format_args!(
1378                            "CONFIG SET failed (possibly related to argument '{MAXSTORE}') - argument must be a memory value or -1"
1379                        ),
1380                    ));
1381                };
1382                store = Some(bytes);
1383                continue;
1384            }
1385            if is(name, MAXMEMORY_POLICY.as_bytes()) {
1386                // Named twice in one command, the last one wins, which is the
1387                // same rule the ladder settings follow here and is not what a
1388                // real server does with a setting repeated in a single `CONFIG
1389                // SET`. It refuses the command instead, which is D-138.
1390                let Some(p) = Policy::parse(value) else {
1391                    return Err(Error::fmt(
1392                        Code::Invalid,
1393                        format_args!(
1394                            "CONFIG SET failed (possibly related to argument '{MAXMEMORY_POLICY}') - argument(s) must be one of the following: {PolicyNames}"
1395                        ),
1396                    ));
1397                };
1398                policy = Some(p);
1399                continue;
1400            }
1401            // Refused whatever the value is, including the one they are already
1402            // set to, which is the one place a setting here does not take the
1403            // write that changes nothing. That is the reference's answer: a
1404            // protected config is refused before anybody looks at what was
1405            // asked for.
1406            if let Some(protected) = [DIR, DBFILENAME]
1407                .into_iter()
1408                .find(|p| is(name, p.as_bytes()))
1409            {
1410                return Err(Error::fmt(
1411                    Code::Unsupported,
1412                    format_args!(
1413                        "CONFIG SET failed (possibly related to argument '{protected}') - can't set protected config"
1414                    ),
1415                ));
1416            }
1417            // Refused whatever the value is, including the one it is already
1418            // set to, which is how a real server answers every immutable
1419            // config: the check is on the name and never reaches the value.
1420            // The names in `SETTINGS` take the value that changes nothing,
1421            // which is a difference and is registered as one.
1422            if is(name, ACLFILE.as_bytes()) {
1423                return Err(Error::fmt(
1424                    Code::Unsupported,
1425                    format_args!(
1426                        "CONFIG SET failed (possibly related to argument '{ACLFILE}') - can't set immutable config"
1427                    ),
1428                ));
1429            }
1430            if is(name, ACLLOG_MAX_LEN.as_bytes()) {
1431                let Some(n) = parse_i64(value).filter(|&n| n >= 0) else {
1432                    return Err(bad_setting(ACLLOG_MAX_LEN, parse_i64(value).is_some()));
1433                };
1434                logged = Some(n as u64);
1435                continue;
1436            }
1437            if is(name, ACL_PUBSUB_DEFAULT.as_bytes()) {
1438                let Some(at) = CHANNEL_DEFAULTS
1439                    .iter()
1440                    .position(|w| is(value, w.as_bytes()))
1441                else {
1442                    return Err(Error::fmt(
1443                        Code::Invalid,
1444                        format_args!(
1445                            "CONFIG SET failed (possibly related to argument '{ACL_PUBSUB_DEFAULT}') - argument(s) must be one of the following: {}, {}",
1446                            CHANNEL_DEFAULTS[0], CHANNEL_DEFAULTS[1]
1447                        ),
1448                    ));
1449                };
1450                channels = Some(at == 0);
1451                continue;
1452            }
1453            if is(name, REQUIREPASS.as_bytes()) {
1454                // Anything at all is a password, including an empty one, which
1455                // is how a password is taken off again. There is nothing to
1456                // refuse here and a real server refuses nothing either.
1457                password = Some(value);
1458                continue;
1459            }
1460            if let Some(spelling) = REPLICA_READ_ONLY.iter().find(|k| is(name, k.as_bytes())) {
1461                let Some(at) = BOOLS.iter().position(|w| is(value, w.as_bytes())) else {
1462                    return Err(Error::fmt(
1463                        Code::Invalid,
1464                        format_args!(
1465                            "CONFIG SET failed (possibly related to argument '{spelling}') - argument must be 'yes' or 'no'"
1466                        ),
1467                    ));
1468                };
1469                readonly = Some(at == 0);
1470                continue;
1471            }
1472            if let Some(at) = CLUSTER_COVERAGE.iter().position(|k| is(name, k.as_bytes())) {
1473                let Some(word) = BOOLS.iter().position(|w| is(value, w.as_bytes())) else {
1474                    return Err(Error::fmt(
1475                        Code::Invalid,
1476                        format_args!(
1477                            "CONFIG SET failed (possibly related to argument '{}') - argument must be 'yes' or 'no'",
1478                            CLUSTER_COVERAGE[at]
1479                        ),
1480                    ));
1481                };
1482                coverage[at] = Some(word == 0);
1483                continue;
1484            }
1485            if is(name, CLUSTER_ENABLED.as_bytes()) || is(name, CLUSTER_CONFIG_FILE.as_bytes()) {
1486                return Err(yo_alloc::allow(|| {
1487                    Error::fmt(
1488                        Code::Unsupported,
1489                        format_args!(
1490                            "CONFIG SET failed (possibly related to argument '{}') - can't set immutable config",
1491                            String::from_utf8_lossy(name).to_lowercase()
1492                        ),
1493                    )
1494                }));
1495            }
1496            if is(name, MASTERAUTH.as_bytes()) {
1497                // Anything at all, including nothing, which is how the password
1498                // is taken off again. It is read at the next dial rather than
1499                // now, so setting it on a replica whose link is already up takes
1500                // effect the next time that link breaks and comes back.
1501                mauth = Some(value);
1502                continue;
1503            }
1504            if is(name, MASTERUSER.as_bytes()) {
1505                muser = Some(value);
1506                continue;
1507            }
1508            if is(name, NOTIFY.as_bytes()) {
1509                // The only setting here whose error names what was wrong with
1510                // the value rather than what the value should have been, and it
1511                // quotes the accepted characters in the reference's order.
1512                let Some(flags) = notify::parse(value) else {
1513                    return Err(Error::fmt(
1514                        Code::Invalid,
1515                        format_args!(
1516                            "CONFIG SET failed (possibly related to argument '{NOTIFY}') - Invalid event class character. Use '{}'.",
1517                            notify::ACCEPTED
1518                        ),
1519                    ));
1520                };
1521                events = Some(flags);
1522                continue;
1523            }
1524            if is(name, SEALED_TTL.as_bytes()) {
1525                let Some(n) = parse_i64(value).filter(|&n| n >= 0) else {
1526                    return Err(bad_setting(SEALED_TTL, parse_i64(value).is_some()));
1527                };
1528                ttl = Some(n as u64);
1529                continue;
1530            }
1531            if let Some((k, knob)) = LADDER.iter().find(|(k, _)| is(name, k.as_bytes())) {
1532                let Some(n) = parse_i64(value).filter(|&n| n >= 0) else {
1533                    return Err(bad_setting(k, parse_i64(value).is_some()));
1534                };
1535                if count == writes.len() {
1536                    // Sixteen pairs is more than the ten names there are, so
1537                    // getting here means a name was given twice enough times to
1538                    // fill it, and the last one would have won anyway.
1539                    return Err(args::syntax());
1540                }
1541                writes[count] = Some((*knob, n as usize));
1542                count += 1;
1543                continue;
1544            }
1545            let Some((k, v)) = SETTINGS.iter().find(|(k, _)| is(name, k.as_bytes())) else {
1546                return Err(yo_alloc::allow(|| {
1547                    Error::fmt(
1548                        Code::Invalid,
1549                        format_args!(
1550                            "Unknown option or number of arguments for CONFIG SET - '{}'",
1551                            String::from_utf8_lossy(name)
1552                        ),
1553                    )
1554                }));
1555            };
1556            if value != v.as_bytes() {
1557                return Err(Error::fmt(
1558                    Code::Unsupported,
1559                    format_args!(
1560                        "CONFIG SET failed (possibly related to argument '{k}') - can't set immutable config"
1561                    ),
1562                ));
1563            }
1564        }
1565        // Every stripe of every database, because these are one server wide
1566        // number in Redis and the fact that a `Keyspace` carries its own copy is
1567        // ours and not the client's problem. A stripe that missed one would put
1568        // a key in a different shape from the same key on the stripe next to it,
1569        // which `OBJECT ENCODING` would then answer differently for depending on
1570        // where the key happened to land.
1571        // The whole database is held while its stripes are set rather than one
1572        // stripe at a time, for the same reason they all get the same number: a
1573        // client that read `OBJECT ENCODING` in the middle of a half done change
1574        // would be told two different things about two keys depending on nothing
1575        // it can see.
1576        for (knob, n) in writes.iter().flatten() {
1577            for at in 0..DATABASES {
1578                let db = server.striped(at);
1579                let mut held = db.hold_many(0..db.width());
1580                for i in 0..db.width() {
1581                    write_knob(held.stripe_mut(i), *knob, *n);
1582                }
1583            }
1584        }
1585        if let Some(p) = policy {
1586            for at in 0..DATABASES {
1587                let db = server.striped(at);
1588                let mut held = db.hold_many(0..db.width());
1589                for i in 0..db.width() {
1590                    held.stripe_mut(i).set_policy(p);
1591                }
1592            }
1593        }
1594        if let Some(seconds) = ttl {
1595            server.backup().set_ttl(seconds);
1596        }
1597        if let Some(flags) = events {
1598            server.set_notify_flags(flags);
1599        }
1600        if let Some(n) = logged {
1601            server.acl_log().set_max_len(n);
1602        }
1603        if let Some(open) = channels {
1604            server.users().set_open_channels(open);
1605        }
1606        if let Some(yes) = readonly {
1607            server.set_replica_read_only(yes);
1608        }
1609        // One write of the pair whichever of the two was named, for the same
1610        // reason the master credentials are written together: they are read as a
1611        // pair and a set of one has to leave the other where it was.
1612        if coverage[0].is_some() || coverage[1].is_some() {
1613            server.set_cluster_coverage(
1614                coverage[0].unwrap_or_else(|| server.cluster_full_coverage()),
1615                coverage[1].unwrap_or_else(|| server.cluster_reads_when_down()),
1616            );
1617        }
1618        // One write of the pair whichever of the two was named, because they
1619        // live together and a set of one has to leave the other where it was.
1620        if mauth.is_some() || muser.is_some() {
1621            let (user, pass) =
1622                yo_alloc::allow(|| server.with_master_auth(|u, p| (u.to_vec(), p.to_vec())));
1623            server.master_auth(muser.unwrap_or(&user), mauth.unwrap_or(&pass));
1624        }
1625        if let Some(value) = password {
1626            // The connections that are already open are left where they are,
1627            // including the one that sent this. See the `auth` module for why
1628            // that is the reference's rule and not an accident of it.
1629            server.set_password(value);
1630        }
1631        // Last, so that a `CONFIG SET maxmemory 1mb maxmemory-policy allkeys-lru`
1632        // has the policy in place before the limit that will act on it. The two
1633        // in the other order would run the first eviction under whatever the
1634        // policy used to be, which for a fresh server is `noeviction` and would
1635        // refuse the next write instead of making room for it.
1636        if let Some(bytes) = store {
1637            server.set_maxstore(bytes);
1638        }
1639        if let Some(bytes) = limit {
1640            server.set_maxmemory(bytes);
1641        }
1642        out.ok();
1643    } else if is(sub, b"RESETSTAT") {
1644        server.reset_stats();
1645        out.ok();
1646    } else if is(sub, b"REWRITE") {
1647        return Err(Error::new(
1648            Code::Unsupported,
1649            "The server is running without a config file",
1650        ));
1651    } else if is(sub, b"HELP") {
1652        help(out, CONFIG_HELP);
1653    } else {
1654        return Err(args::unknown_subcommand(sub, "CONFIG"));
1655    }
1656    Ok(())
1657}
1658
1659// -------------------------------------------------------------------- INFO
1660
1661/// `INFO [section ...]`.
1662///
1663/// Every number in here is one this layer can actually answer. There is no
1664/// `rdb_last_save_time` because there is no save, and a field that is not there
1665/// is a client falling back rather than a client believing a zero.
1666///
1667/// The `CPU` section used to be missing for the same reason and is here now,
1668/// because nothing measured it and then something did. It is one `getrusage`
1669/// call in [`super::cpu`], and the reason it went in is that Redis's own
1670/// `unit/info-command` tests fail without it: a monitoring tool graphs
1671/// processor time against wall clock to decide whether a server is busy or
1672/// waiting, so an absent field there is a real hole and not a tidy omission.
1673fn info(server: &Server, args: Args<'_>, out: &mut Out) {
1674    // Redis keeps two lists: the sections a bare `INFO` hands back, and the ones
1675    // that have to be asked for by name or by `all`. `commandstats` is in the
1676    // second, along with `latencystats` and `errorstats`, because they grow with
1677    // the number of distinct commands a server has seen and a monitoring tool
1678    // polling `INFO` every second does not want them.
1679    //
1680    // `unit/info-command` is exactly this distinction written down: it asks for
1681    // `INFO default` and insists `rejected_calls` is not in the answer, then
1682    // asks for `INFO all` and insists that it is.
1683    let named = |section: &str| (1..args.len()).any(|i| is(args.get(i), section.as_bytes()));
1684    let everything = (1..args.len()).any(|i| {
1685        let a = args.get(i);
1686        is(a, b"all") || is(a, b"everything")
1687    });
1688    let by_default = args.len() == 1 || (1..args.len()).any(|i| is(args.get(i), b"default"));
1689    let want = |section: &str| by_default || everything || named(section);
1690    let extra = |section: &str| everything || named(section);
1691    // One string, built once and written once. It allocates, which is allowed
1692    // here and nowhere near the commands that count: `INFO` is a monitoring
1693    // call and it is not on the path M2 is measured on.
1694    let text = yo_alloc::allow(|| {
1695        let mut s = String::with_capacity(1024);
1696        if want("server") {
1697            let _ = write!(
1698                s,
1699                "# Server\r\nredis_version:{REPORTED_VERSION}\r\nyo_version:{}\r\n\
1700                 redis_mode:{}\r\narch_bits:{}\r\nprocess_id:0\r\n\
1701                 run_id:0000000000000000000000000000000000000000\r\ntcp_port:{}\r\n\
1702                 uptime_in_seconds:{}\r\nio_threads_active:0\r\n\r\n",
1703                env!("CARGO_PKG_VERSION"),
1704                if server.cluster_enabled() {
1705                    "cluster"
1706                } else {
1707                    "standalone"
1708                },
1709                usize::BITS,
1710                // The port the socket was actually bound to, which whoever bound
1711                // it told the server. Nought on an embedded caller that never
1712                // opened one, which is honest: there is no port.
1713                server.announced_port(),
1714                server.uptime_secs(),
1715            );
1716        }
1717        if want("clients") {
1718            let _ = write!(
1719                s,
1720                "# Clients\r\nconnected_clients:{}\r\nblocked_clients:{}\r\n\
1721                 pubsub_clients:{}\r\ncluster_connections:0\r\n\r\n",
1722                server
1723                    .totals()
1724                    .clients
1725                    .saturating_sub(server.replica_count()),
1726                server.parked(),
1727                server.pubsub_counts().clients,
1728            );
1729        }
1730        if want("memory") {
1731            // Both the cap and the quarter of it, because the quarter is an
1732            // empirical number and somebody surprised by it should be able to
1733            // see what it was a quarter of without reading the source. The
1734            // reasoning is written out in `cap`.
1735            let cap = crate::cap::cap();
1736            let compact = server.compaction();
1737            // Read out of its stripe before the write, because an argument list
1738            // keeps every temporary in it alive until the whole call is over
1739            // and one of the other arguments walks that same stripe.
1740            let policy = server.settings().policy().name();
1741            let _ = write!(
1742                s,
1743                "# Memory\r\nused_memory:{}\r\nused_memory_dataset:{}\r\n\
1744                 used_memory_overhead:{}\r\nmem_arena_bytes:{}\r\n\
1745                 mem_arena_segments:{}\r\nmem_arena_listed:{}\r\n\
1746                 mem_compact_walked:{}\r\n\
1747                 mem_compact_moved:{}\r\nmem_compact_bytes:{}\r\n\
1748                 mem_index_bytes:{}\r\n\
1749                 mem_client_buffers:{}\r\ntotal_system_memory:{}\r\n\
1750                 mem_cgroup_limit:{}\r\nmem_limit:{}\r\nmem_budget:{}\r\n\
1751                 maxmemory:{}\r\nmaxmemory_policy:{}\r\n\
1752                 maxstore:{}\r\nyo_store_bytes:{}\r\nyo_memory_regime:{}\r\n\r\n",
1753                server.memory_bytes(),
1754                server.dataset_bytes(),
1755                server.memory_bytes() - server.dataset_bytes(),
1756                server.arena_bytes(),
1757                server.segment_count(),
1758                server.listed_runs(),
1759                compact.walked,
1760                compact.moved,
1761                compact.bytes,
1762                server.index_bytes(),
1763                server.conn_bytes(),
1764                cap.host.unwrap_or(0),
1765                cap.cgroup.unwrap_or(0),
1766                cap.limit().unwrap_or(0),
1767                cap.budget(),
1768                server.maxmemory(),
1769                policy,
1770                server.maxstore().map_or(-1, |n| n as i64),
1771                server.store_bytes(),
1772                server.regime(),
1773            );
1774        }
1775        if want("persistence") {
1776            persist::info(server, &mut s);
1777        }
1778        if want("stats") {
1779            // The cold counters live here and not in the memory section,
1780            // because they are totals since the server started and everything
1781            // in that section is a level right now. `yo_cold_faults` over the
1782            // point reads a run issued is the ratio G9 is a gate on, and it
1783            // cannot be worked out from outside the server.
1784            let cold = server.cold_stats();
1785            let totals = server.totals();
1786            let subs = server.pubsub_counts();
1787            // The five ACL counters last, which is where a real server puts them
1788            // too: they are appended after the rest of the section rather than
1789            // written with it.
1790            let denied = server.acl_log().counters();
1791            let _ = write!(
1792                s,
1793                "# Stats\r\ntotal_connections_received:{}\r\n\
1794                 total_commands_processed:{}\r\nexpired_subkeys:{}\r\n\
1795                 expired_subkeys_active:{}\r\nexpired_keys:{}\r\n\
1796                 evicted_keys:{}\r\nkeyspace_hits:{}\r\nkeyspace_misses:{}\r\n\
1797                 yo_cold_demoted:{}\r\nyo_cold_promoted:{}\r\n\
1798                 yo_cold_faults:{}\r\nyo_cold_served:{}\r\nyo_cold_bytes_out:{}\r\n\
1799                 yo_cold_bytes_in:{}\r\npubsub_channels:{}\r\n\
1800                 pubsub_patterns:{}\r\npubsubshard_channels:{}\r\n\
1801                 acl_access_denied_auth:{}\r\nacl_access_denied_cmd:{}\r\n\
1802                 acl_access_denied_key:{}\r\nacl_access_denied_channel:{}\r\n\
1803                 acl_access_denied_tls_cert:{}\r\n\r\n",
1804                totals.connections,
1805                totals.commands,
1806                server.expired_fields(),
1807                server.expired_fields_active(),
1808                server.expired_keys(),
1809                server.evicted_keys(),
1810                server.keyspace_hits(),
1811                server.keyspace_misses(),
1812                cold.demoted,
1813                cold.promoted,
1814                cold.faults,
1815                cold.served,
1816                cold.bytes_out,
1817                cold.bytes_in,
1818                subs.channels,
1819                subs.patterns,
1820                subs.shard,
1821                denied[0],
1822                denied[1],
1823                denied[2],
1824                denied[3],
1825                denied[4],
1826            );
1827        }
1828        if want("cpu") {
1829            // Two of Redis's six are not here. `used_cpu_sys_main_thread` and
1830            // `used_cpu_user_main_thread` need `RUSAGE_THREAD`, which is Linux
1831            // only, and reporting the process totals under a name that says
1832            // main thread would be right on a single threaded server and wrong
1833            // on the one this becomes.
1834            if let Some(u) = cpu::usage() {
1835                let _ = write!(
1836                    s,
1837                    "# CPU\r\nused_cpu_sys:{:.6}\r\nused_cpu_user:{:.6}\r\n\
1838                     used_cpu_sys_children:{:.6}\r\nused_cpu_user_children:{:.6}\r\n\r\n",
1839                    u.sys, u.user, u.sys_children, u.user_children,
1840                );
1841            }
1842        }
1843        if want("replication") {
1844            super::repl::info(server, &mut s);
1845        }
1846        if want("cluster") {
1847            // One field, which is the one every client library reads on connect
1848            // to decide whether it needs a slot map at all.
1849            let _ = write!(
1850                s,
1851                "# Cluster\r\ncluster_enabled:{}\r\n\r\n",
1852                u8::from(server.cluster_enabled()),
1853            );
1854        }
1855        if extra("threads") {
1856            // An extra rather than a default section for the reason above: it
1857            // grows with the thread count, and a tool polling `INFO` every
1858            // second on a thirty two thread server does not want thirty two
1859            // more lines every time.
1860            //
1861            // The three numbers are the three questions worth asking of a
1862            // server where a connection belongs to the thread that accepted it.
1863            // `clients` says whether the connections open right now are shared
1864            // out. `connections` says whether they were shared out as they
1865            // arrived, which is a different question, because a split that was
1866            // fair at the start and is unfair now is clients hanging up rather
1867            // than an accept race. `commands` says whether an even split of
1868            // connections turned into an even split of work, which it does not
1869            // when the clients are not all asking for the same thing.
1870            let per = server.per_thread();
1871            let _ = write!(s, "# Threads\r\nio_threads:{}\r\n", per.len());
1872            for (at, thread) in per.iter().enumerate() {
1873                let _ = write!(
1874                    s,
1875                    "thread_{at}:clients={},connections={},commands={}\r\n",
1876                    thread.clients, thread.connections, thread.commands,
1877                );
1878            }
1879            s.push_str("\r\n");
1880        }
1881        if extra("commandstats") {
1882            s.push_str("# Commandstats\r\n");
1883            for (name, row) in server.command_stats() {
1884                let _ = write!(
1885                    s,
1886                    "cmdstat_{name}:calls={},rejected_calls={},failed_calls={}\r\n",
1887                    row.calls, row.rejected, row.failed,
1888                );
1889            }
1890            s.push_str("\r\n");
1891        }
1892        if want("keyspace") {
1893            s.push_str("# Keyspace\r\n");
1894            for i in 0..DATABASES {
1895                let keys = server.dbs[i].len();
1896                if keys > 0 {
1897                    // `avg_ttl` is still a zero, and Redis reports a zero there
1898                    // too on a server that has never run its active expiry
1899                    // cycle, because the number is a running estimate that cycle
1900                    // produces rather than something anybody measures on demand.
1901                    let expires = server.dbs[i].expires();
1902                    let _ = write!(s, "db{i}:keys={keys},expires={expires},avg_ttl=0\r\n");
1903                }
1904            }
1905            s.push_str("\r\n");
1906        }
1907        s
1908    });
1909    out.verbatim(b"txt", text.as_bytes());
1910}
1911
1912// -------------------------------------------------------------------- help
1913
1914/// The `HELP` reply, which is an array of simple strings on both protocols.
1915pub(super) fn help(out: &mut Out, lines: &[&str]) {
1916    out.array(lines.len());
1917    for line in lines {
1918        out.simple(line.as_bytes());
1919    }
1920}
1921
1922/// What `COMMAND HELP` says.
1923const COMMAND_HELP: &[&str] = &[
1924    "COMMAND <subcommand> [<arg> [value] [opt] ...]. Subcommands are:",
1925    "(no subcommand)",
1926    "    Return details about all commands.",
1927    "COUNT",
1928    "    Return the total number of commands in this server.",
1929    "LIST [FILTERBY <MODULE <module-name>|ACLCAT <category>|PATTERN <pattern>>]",
1930    "    Return a list of all commands in this server.",
1931    "INFO [<command-name> ...]",
1932    "    Return details about multiple commands.",
1933    "DOCS [<command-name> ...]",
1934    "    Return documentation details about multiple commands.",
1935    "GETKEYS <full-command>",
1936    "    Return the keys from a full command.",
1937    "HELP",
1938    "    Print this help.",
1939];
1940
1941/// What `CONFIG HELP` says.
1942const CONFIG_HELP: &[&str] = &[
1943    "CONFIG <subcommand> [<arg> [value] [opt] ...]. Subcommands are:",
1944    "GET <pattern>",
1945    "    Return parameters matching the glob-like <pattern> and their values.",
1946    "SET <directive> <value>",
1947    "    Set the configuration <directive> to <value>.",
1948    "RESETSTAT",
1949    "    Reset statistics reported by the INFO command.",
1950    "REWRITE",
1951    "    Rewrite the configuration file.",
1952    "HELP",
1953    "    Print this help.",
1954];
1955
1956#[cfg(test)]
1957mod tests {
1958    use super::{BOOLS, REPLICA_READ_ONLY, SETTINGS};
1959
1960    /// The backlog setting is a compiled in number written out twice, and the
1961    /// two have to be the same number: a client that reads `repl-backlog-size`
1962    /// and then works out how far behind a replica may fall before a full resync
1963    /// is reading this to answer that question.
1964    #[test]
1965    fn the_backlog_setting_is_the_size_the_backlog_actually_is() {
1966        let said = SETTINGS
1967            .iter()
1968            .find(|(k, _)| *k == "repl-backlog-size")
1969            .expect("the setting is there")
1970            .1;
1971        assert_eq!(
1972            said.parse::<usize>().expect("a number"),
1973            super::super::repl::BACKLOG_BYTES
1974        );
1975    }
1976
1977    /// The two spellings are one setting and the reference answers to both, so a
1978    /// script written against either works here.
1979    #[test]
1980    fn the_read_only_setting_answers_to_both_of_its_names() {
1981        assert_eq!(REPLICA_READ_ONLY, ["replica-read-only", "slave-read-only"]);
1982        assert_eq!(BOOLS, ["yes", "no"]);
1983    }
1984}