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