feat(tvix/nix-compat): read client setting from wire
Add the primitives necessary to read the client settings from the Nix daemon wire protocol. Introducing the read_string primitive. This trivial primitive parses a read_bytes call, check the bytes are valid utf-8 bytes and wraps the result in a String. Change-Id: Ie1253523a6bd4e31e7924e9898a0898109da2fa0 Reviewed-on: https://cl.tvl.fyi/c/depot/+/11358 Reviewed-by: flokli <flokli@flokli.de> Tested-by: BuildkiteCI
This commit is contained in:
		
							parent
							
								
									289b3126db
								
							
						
					
					
						commit
						199f9b0a79
					
				
					 2 changed files with 225 additions and 3 deletions
				
			
		| 
						 | 
					@ -1,4 +1,7 @@
 | 
				
			||||||
use std::ops::RangeBounds;
 | 
					use std::{
 | 
				
			||||||
 | 
					    io::{Error, ErrorKind},
 | 
				
			||||||
 | 
					    ops::RangeBounds,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
 | 
					use tokio::io::{AsyncReadExt, AsyncWriteExt};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -58,6 +61,21 @@ where
 | 
				
			||||||
    Ok(buf)
 | 
					    Ok(buf)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Read a Nix daemon string from the AsyncWrite, encoded as utf8.
 | 
				
			||||||
 | 
					/// Rejects reading more than `allowed_size` bytes
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// A Nix daemon string is made up of two distincts parts:
 | 
				
			||||||
 | 
					/// 1. Its lenght, LE-encoded on 64 bits.
 | 
				
			||||||
 | 
					/// 2. Its content. 0-padded on 64 bits.
 | 
				
			||||||
 | 
					pub async fn read_string<R, S>(r: &mut R, allowed_size: S) -> std::io::Result<String>
 | 
				
			||||||
 | 
					where
 | 
				
			||||||
 | 
					    R: AsyncReadExt + Unpin,
 | 
				
			||||||
 | 
					    S: RangeBounds<u64>,
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    let bytes = read_bytes(r, allowed_size).await?;
 | 
				
			||||||
 | 
					    String::from_utf8(bytes).map_err(|e| Error::new(ErrorKind::InvalidData, e))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Writes a sequence of sized bits to a (hopefully buffered)
 | 
					/// Writes a sequence of sized bits to a (hopefully buffered)
 | 
				
			||||||
/// [AsyncWriteExt] handle.
 | 
					/// [AsyncWriteExt] handle.
 | 
				
			||||||
///
 | 
					///
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,7 @@
 | 
				
			||||||
use std::io::{Error, ErrorKind};
 | 
					use std::{
 | 
				
			||||||
 | 
					    collections::HashMap,
 | 
				
			||||||
 | 
					    io::{Error, ErrorKind},
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use enum_primitive_derive::Primitive;
 | 
					use enum_primitive_derive::Primitive;
 | 
				
			||||||
use num_traits::{FromPrimitive, ToPrimitive};
 | 
					use num_traits::{FromPrimitive, ToPrimitive};
 | 
				
			||||||
| 
						 | 
					@ -6,8 +9,16 @@ use tokio::io::{AsyncReadExt, AsyncWriteExt};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::wire::primitive;
 | 
					use crate::wire::primitive;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use super::bytes::read_string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub static STDERR_LAST: u64 = 0x616c7473;
 | 
					pub static STDERR_LAST: u64 = 0x616c7473;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Max length of a Nix setting name/value. In bytes.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// This value has been arbitrarily choosen after looking the nix.conf
 | 
				
			||||||
 | 
					/// manpage. Don't hesitate to increase it if it's too limiting.
 | 
				
			||||||
 | 
					pub static MAX_SETTING_SIZE: u64 = 1024;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Worker Operation
 | 
					/// Worker Operation
 | 
				
			||||||
///
 | 
					///
 | 
				
			||||||
/// These operations are encoded as unsigned 64 bits before being sent
 | 
					/// These operations are encoded as unsigned 64 bits before being sent
 | 
				
			||||||
| 
						 | 
					@ -66,11 +77,102 @@ pub enum Operation {
 | 
				
			||||||
    AddPermRoot = 47,
 | 
					    AddPermRoot = 47,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Log verbosity. In the Nix wire protocol, the client requests a
 | 
				
			||||||
 | 
					/// verbosity level to the daemon, which in turns does not produce any
 | 
				
			||||||
 | 
					/// log below this verbosity.
 | 
				
			||||||
 | 
					#[derive(Debug, PartialEq, Primitive)]
 | 
				
			||||||
 | 
					pub enum Verbosity {
 | 
				
			||||||
 | 
					    LvlError = 0,
 | 
				
			||||||
 | 
					    LvlWarn = 1,
 | 
				
			||||||
 | 
					    LvlNotice = 2,
 | 
				
			||||||
 | 
					    LvlInfo = 3,
 | 
				
			||||||
 | 
					    LvlTalkative = 4,
 | 
				
			||||||
 | 
					    LvlChatty = 5,
 | 
				
			||||||
 | 
					    LvlDebug = 6,
 | 
				
			||||||
 | 
					    LvlVomit = 7,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Settings requested by the client. These settings are applied to a
 | 
				
			||||||
 | 
					/// connection to between the daemon and a client.
 | 
				
			||||||
 | 
					#[derive(Debug, PartialEq)]
 | 
				
			||||||
 | 
					pub struct ClientSettings {
 | 
				
			||||||
 | 
					    pub keep_failed: bool,
 | 
				
			||||||
 | 
					    pub keep_going: bool,
 | 
				
			||||||
 | 
					    pub try_fallback: bool,
 | 
				
			||||||
 | 
					    pub verbosity: Verbosity,
 | 
				
			||||||
 | 
					    pub max_build_jobs: u64,
 | 
				
			||||||
 | 
					    pub max_silent_time: u64,
 | 
				
			||||||
 | 
					    pub verbose_build: bool,
 | 
				
			||||||
 | 
					    pub build_cores: u64,
 | 
				
			||||||
 | 
					    pub use_substitutes: bool,
 | 
				
			||||||
 | 
					    /// Key/Value dictionary in charge of overriding the settings set
 | 
				
			||||||
 | 
					    /// by the Nix config file.
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    /// Some settings can be safely overidden,
 | 
				
			||||||
 | 
					    /// some other require the user running the Nix client to be part
 | 
				
			||||||
 | 
					    /// of the trusted users group.
 | 
				
			||||||
 | 
					    pub overrides: HashMap<String, String>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Reads the client settings from the wire.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// Note: this function **only** reads the settings. It does not
 | 
				
			||||||
 | 
					/// manage the log state with the daemon. You'll have to do that on
 | 
				
			||||||
 | 
					/// your own. A minimal log implementation will consist in sending
 | 
				
			||||||
 | 
					/// back [STDERR_LAST] to the client after reading the client
 | 
				
			||||||
 | 
					/// settings.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// FUTUREWORK: write serialization.
 | 
				
			||||||
 | 
					pub async fn read_client_settings<R: AsyncReadExt + Unpin>(
 | 
				
			||||||
 | 
					    r: &mut R,
 | 
				
			||||||
 | 
					    client_version: u64,
 | 
				
			||||||
 | 
					) -> std::io::Result<ClientSettings> {
 | 
				
			||||||
 | 
					    let keep_failed = primitive::read_bool(r).await?;
 | 
				
			||||||
 | 
					    let keep_going = primitive::read_bool(r).await?;
 | 
				
			||||||
 | 
					    let try_fallback = primitive::read_bool(r).await?;
 | 
				
			||||||
 | 
					    let verbosity_uint = primitive::read_u64(r).await?;
 | 
				
			||||||
 | 
					    let verbosity = Verbosity::from_u64(verbosity_uint).ok_or_else(|| {
 | 
				
			||||||
 | 
					        Error::new(
 | 
				
			||||||
 | 
					            ErrorKind::InvalidData,
 | 
				
			||||||
 | 
					            format!("Can't convert integer {} to verbosity", verbosity_uint),
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    })?;
 | 
				
			||||||
 | 
					    let max_build_jobs = primitive::read_u64(r).await?;
 | 
				
			||||||
 | 
					    let max_silent_time = primitive::read_u64(r).await?;
 | 
				
			||||||
 | 
					    _ = primitive::read_u64(r).await?; // obsolete useBuildHook
 | 
				
			||||||
 | 
					    let verbose_build = primitive::read_bool(r).await?;
 | 
				
			||||||
 | 
					    _ = primitive::read_u64(r).await?; // obsolete logType
 | 
				
			||||||
 | 
					    _ = primitive::read_u64(r).await?; // obsolete printBuildTrace
 | 
				
			||||||
 | 
					    let build_cores = primitive::read_u64(r).await?;
 | 
				
			||||||
 | 
					    let use_substitutes = primitive::read_bool(r).await?;
 | 
				
			||||||
 | 
					    let mut overrides = HashMap::new();
 | 
				
			||||||
 | 
					    if client_version >= 12 {
 | 
				
			||||||
 | 
					        let num_overrides = primitive::read_u64(r).await?;
 | 
				
			||||||
 | 
					        for _ in 0..num_overrides {
 | 
				
			||||||
 | 
					            let name = read_string(r, 0..MAX_SETTING_SIZE).await?;
 | 
				
			||||||
 | 
					            let value = read_string(r, 0..MAX_SETTING_SIZE).await?;
 | 
				
			||||||
 | 
					            overrides.insert(name, value);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    Ok(ClientSettings {
 | 
				
			||||||
 | 
					        keep_failed,
 | 
				
			||||||
 | 
					        keep_going,
 | 
				
			||||||
 | 
					        try_fallback,
 | 
				
			||||||
 | 
					        verbosity,
 | 
				
			||||||
 | 
					        max_build_jobs,
 | 
				
			||||||
 | 
					        max_silent_time,
 | 
				
			||||||
 | 
					        verbose_build,
 | 
				
			||||||
 | 
					        build_cores,
 | 
				
			||||||
 | 
					        use_substitutes,
 | 
				
			||||||
 | 
					        overrides,
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Read a worker [Operation] from the wire.
 | 
					/// Read a worker [Operation] from the wire.
 | 
				
			||||||
pub async fn read_op<R: AsyncReadExt + Unpin>(r: &mut R) -> std::io::Result<Operation> {
 | 
					pub async fn read_op<R: AsyncReadExt + Unpin>(r: &mut R) -> std::io::Result<Operation> {
 | 
				
			||||||
    let op_number = primitive::read_u64(r).await?;
 | 
					    let op_number = primitive::read_u64(r).await?;
 | 
				
			||||||
    Operation::from_u64(op_number).ok_or(Error::new(
 | 
					    Operation::from_u64(op_number).ok_or(Error::new(
 | 
				
			||||||
        ErrorKind::Other,
 | 
					        ErrorKind::InvalidData,
 | 
				
			||||||
        format!("Invalid OP number {}", op_number),
 | 
					        format!("Invalid OP number {}", op_number),
 | 
				
			||||||
    ))
 | 
					    ))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -105,3 +207,105 @@ where
 | 
				
			||||||
        Trust::NotTrusted => primitive::write_u64(conn, 2).await,
 | 
					        Trust::NotTrusted => primitive::write_u64(conn, 2).await,
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[cfg(test)]
 | 
				
			||||||
 | 
					mod tests {
 | 
				
			||||||
 | 
					    use super::*;
 | 
				
			||||||
 | 
					    use hex_literal::hex;
 | 
				
			||||||
 | 
					    use tokio_test::io::Builder;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[tokio::test]
 | 
				
			||||||
 | 
					    async fn test_read_client_settings_without_overrides() {
 | 
				
			||||||
 | 
					        // Client settings bits captured from a Nix 2.3.17 run w/ sockdump (protocol version 21).
 | 
				
			||||||
 | 
					        let wire_bits = hex!(
 | 
				
			||||||
 | 
					            "00 00 00 00 00 00 00 00 \
 | 
				
			||||||
 | 
					             00 00 00 00 00 00 00 00 \
 | 
				
			||||||
 | 
					             00 00 00 00 00 00 00 00 \
 | 
				
			||||||
 | 
					             02 00 00 00 00 00 00 00 \
 | 
				
			||||||
 | 
					             10 00 00 00 00 00 00 00 \
 | 
				
			||||||
 | 
					             00 00 00 00 00 00 00 00 \
 | 
				
			||||||
 | 
					             01 00 00 00 00 00 00 00 \
 | 
				
			||||||
 | 
					             00 00 00 00 00 00 00 00 \
 | 
				
			||||||
 | 
					             00 00 00 00 00 00 00 00 \
 | 
				
			||||||
 | 
					             00 00 00 00 00 00 00 00 \
 | 
				
			||||||
 | 
					             00 00 00 00 00 00 00 00 \
 | 
				
			||||||
 | 
					             01 00 00 00 00 00 00 00 \
 | 
				
			||||||
 | 
					             00 00 00 00 00 00 00 00"
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        let mut mock = Builder::new().read(&wire_bits).build();
 | 
				
			||||||
 | 
					        let settings = read_client_settings(&mut mock, 21)
 | 
				
			||||||
 | 
					            .await
 | 
				
			||||||
 | 
					            .expect("should parse");
 | 
				
			||||||
 | 
					        let expected = ClientSettings {
 | 
				
			||||||
 | 
					            keep_failed: false,
 | 
				
			||||||
 | 
					            keep_going: false,
 | 
				
			||||||
 | 
					            try_fallback: false,
 | 
				
			||||||
 | 
					            verbosity: Verbosity::LvlNotice,
 | 
				
			||||||
 | 
					            max_build_jobs: 16,
 | 
				
			||||||
 | 
					            max_silent_time: 0,
 | 
				
			||||||
 | 
					            verbose_build: false,
 | 
				
			||||||
 | 
					            build_cores: 0,
 | 
				
			||||||
 | 
					            use_substitutes: true,
 | 
				
			||||||
 | 
					            overrides: HashMap::new(),
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        assert_eq!(settings, expected);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[tokio::test]
 | 
				
			||||||
 | 
					    async fn test_read_client_settings_with_overrides() {
 | 
				
			||||||
 | 
					        // Client settings bits captured from a Nix 2.3.17 run w/ sockdump (protocol version 21).
 | 
				
			||||||
 | 
					        let wire_bits = hex!(
 | 
				
			||||||
 | 
					            "00 00 00 00 00 00 00 00 \
 | 
				
			||||||
 | 
					             00 00 00 00 00 00 00 00 \
 | 
				
			||||||
 | 
					             00 00 00 00 00 00 00 00 \
 | 
				
			||||||
 | 
					             02 00 00 00 00 00 00 00 \
 | 
				
			||||||
 | 
					             10 00 00 00 00 00 00 00 \
 | 
				
			||||||
 | 
					             00 00 00 00 00 00 00 00 \
 | 
				
			||||||
 | 
					             01 00 00 00 00 00 00 00 \
 | 
				
			||||||
 | 
					             00 00 00 00 00 00 00 00 \
 | 
				
			||||||
 | 
					             00 00 00 00 00 00 00 00 \
 | 
				
			||||||
 | 
					             00 00 00 00 00 00 00 00 \
 | 
				
			||||||
 | 
					             00 00 00 00 00 00 00 00 \
 | 
				
			||||||
 | 
					             01 00 00 00 00 00 00 00 \
 | 
				
			||||||
 | 
					             02 00 00 00 00 00 00 00 \
 | 
				
			||||||
 | 
					             0c 00 00 00 00 00 00 00 \
 | 
				
			||||||
 | 
					             61 6c 6c 6f 77 65 64 2d \
 | 
				
			||||||
 | 
					             75 72 69 73 00 00 00 00 \
 | 
				
			||||||
 | 
					             1e 00 00 00 00 00 00 00 \
 | 
				
			||||||
 | 
					             68 74 74 70 73 3a 2f 2f \
 | 
				
			||||||
 | 
					             62 6f 72 64 65 61 75 78 \
 | 
				
			||||||
 | 
					             2e 67 75 69 78 2e 67 6e \
 | 
				
			||||||
 | 
					             75 2e 6f 72 67 2f 00 00 \
 | 
				
			||||||
 | 
					             0d 00 00 00 00 00 00 00 \
 | 
				
			||||||
 | 
					             61 6c 6c 6f 77 65 64 2d \
 | 
				
			||||||
 | 
					             75 73 65 72 73 00 00 00 \
 | 
				
			||||||
 | 
					             0b 00 00 00 00 00 00 00 \
 | 
				
			||||||
 | 
					             6a 65 61 6e 20 70 69 65 \
 | 
				
			||||||
 | 
					             72 72 65 00 00 00 00 00"
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        let mut mock = Builder::new().read(&wire_bits).build();
 | 
				
			||||||
 | 
					        let settings = read_client_settings(&mut mock, 21)
 | 
				
			||||||
 | 
					            .await
 | 
				
			||||||
 | 
					            .expect("should parse");
 | 
				
			||||||
 | 
					        let overrides = HashMap::from([
 | 
				
			||||||
 | 
					            (
 | 
				
			||||||
 | 
					                String::from("allowed-uris"),
 | 
				
			||||||
 | 
					                String::from("https://bordeaux.guix.gnu.org/"),
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            (String::from("allowed-users"), String::from("jean pierre")),
 | 
				
			||||||
 | 
					        ]);
 | 
				
			||||||
 | 
					        let expected = ClientSettings {
 | 
				
			||||||
 | 
					            keep_failed: false,
 | 
				
			||||||
 | 
					            keep_going: false,
 | 
				
			||||||
 | 
					            try_fallback: false,
 | 
				
			||||||
 | 
					            verbosity: Verbosity::LvlNotice,
 | 
				
			||||||
 | 
					            max_build_jobs: 16,
 | 
				
			||||||
 | 
					            max_silent_time: 0,
 | 
				
			||||||
 | 
					            verbose_build: false,
 | 
				
			||||||
 | 
					            build_cores: 0,
 | 
				
			||||||
 | 
					            use_substitutes: true,
 | 
				
			||||||
 | 
					            overrides,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        assert_eq!(settings, expected);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue