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