chiark / gitweb /
move several things into spec.rs
authorIan Jackson <ijackson@chiark.greenend.org.uk>
Sat, 22 Aug 2020 21:44:52 +0000 (22:44 +0100)
committerIan Jackson <ijackson@chiark.greenend.org.uk>
Sat, 22 Aug 2020 21:44:52 +0000 (22:44 +0100)
src/cmdlistener.rs
src/gamestate.rs
src/global.rs
src/imports.rs
src/spec.rs

index 9b3d7a8094cdbf78e14935efa6b62c0b684f9d28..e5e26937f3b3e3991fe73abda12ce1592f7b4567 100644 (file)
@@ -24,121 +24,16 @@ type ME = MgmtError;
 from_instance_lock_error!{MgmtError}
 
 const USERLIST : &str = "/etc/userlist";
+const CREATE_PIECES_MAX : u32 = 300;
 
-// ---------- entrypoint for the rest of the program ----------
+const XXX_START_POS : Pos = [20,20];
+const XXX_DEFAULT_POSD : Pos = [5,5];
 
 pub struct CommandListener {
   listener : UnixListener,
 }
 
-// ---------- core listener implementation ----------
-
-struct CommandStream<'d> {
-  euid : Result<u32, ConnectionEuidDiscoverEerror>,
-  desc : &'d str,
-  scope : Option<ManagementScope>,
-  chan : MgmtChannel,
-}
-
-impl CommandStream<'_> {
-  #[throws(CSE)]
-  pub fn mainloop(mut self) {
-    loop {
-      use MgmtChannelReadError::*;
-      let resp = match self.chan.read() {
-        Ok(cmd) => match execute(&mut self, cmd) {
-          Ok(resp) => resp,
-          Err(error) => MgmtResponse::Error { error },
-        },
-        Err(EOF) => break,
-        Err(IO(e)) => Err(e).context("read command stream")?,
-        Err(Parse(s)) => MgmtResponse::Error { error : ParseFailed(s) },
-      };
-      self.chan.write(&resp).context("swrite command stream")?;
-    }
-  }
-
-  #[throws(MgmtError)]
-  fn get_scope(&self) -> &ManagementScope {
-    self.scope.as_ref().ok_or(NoScope)?
-  }
-}
-
-impl CommandListener {
-  #[throws(StartupError)]
-  pub fn new() -> Self {
-    let path = SOCKET_PATH;
-    match fs::remove_file(path) {
-      Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(()),
-      r => r,
-    }
-    .with_context(|| format!("remove socket {:?} before we bind", &path))?;
-    let listener = UnixListener::bind(path)
-      .with_context(|| format!("bind command socket {:?}", &path))?;
-
-    fs::set_permissions(path, unix::fs::PermissionsExt::from_mode(0o666))
-      .with_context(|| format!("chmod sommand socket {:?}", &path))?;
-
-    CommandListener { listener }
-  }
-
-  #[throws(StartupError)]
-  pub fn spawn(mut self) {
-    thread::spawn(move ||{
-      loop {
-        self.accept_one().unwrap_or_else(
-          |e| eprintln!("accept/spawn failed: {:?}", e)
-        );
-      }
-    })
-  }
-
-  #[throws(CSE)]
-  fn accept_one(&mut self) {
-    let (conn, _caller) = self.listener.accept().context("accept")?;
-    let mut desc = format!("{:>5}", conn.as_raw_fd());
-    eprintln!("command connection {}: accepted", &desc);
-    thread::spawn(move||{
-      match (||{
-        let euid = conn.initial_peer_credentials()
-          .map(|creds| creds.euid())
-          .map_err(|e| ConnectionEuidDiscoverEerror(format!("{}", e)));
-
-        #[derive(Error,Debug)]
-        struct EuidLookupError(String);
-        display_as_debug!{EuidLookupError}
-        impl From<&E> for EuidLookupError where E : Display {
-          fn from(e: &E) -> Self { EuidLookupError(format!("{}",e)) }
-        }
-
-        let user_desc : String = (||{
-          let euid = euid.clone()?;
-          let pwent = Passwd::from_uid(euid);
-          let show_username =
-            pwent.map_or_else(|| format!("<euid {}>", euid),
-                              |p| p.name);
-          <Result<_,AE>>::Ok(show_username)
-        })().unwrap_or_else(|e| format!("<error: {}>", e));
-        write!(&mut desc, " user={}", user_desc)?;
-
-        let chan = MgmtChannel::new(conn)?;
-
-        let cs = CommandStream {
-          scope: None, desc: &desc,
-          chan, euid,
-        };
-        cs.mainloop()?;
-        
-        <Result<_,StartupError>>::Ok(())
-      })() {
-        Ok(()) => eprintln!("command connection {}: disconnected", &desc),
-        Err(e) => eprintln!("command connection {}: error: {:?}", &desc, e),
-      }
-    });
-  }
-}
-
-// ---------- core management channel implementation ----------
+// ========== management API ==========
 
 // ---------- management command implementations
 
@@ -213,148 +108,7 @@ fn execute(cs: &mut CommandStream, cmd: MgmtCommand) -> MgmtResponse {
   }
 }
 
-//---------- authorisation ----------
-
-#[derive(Debug,Error,Clone)]
-#[error("connection euid lookup failed (at connection initiation): {0}")]
-pub struct ConnectionEuidDiscoverEerror(String);
-
-impl CommandStream<'_> {
-  #[throws(AuthorisationError)]
-  fn authorised_uid(&self, wanted: Option<uid_t>, xinfo: Option<&str>)
-                    -> Authorised<(Passwd,uid_t),> {
-    let client_euid = *self.euid.as_ref().map_err(|e| e.clone())?;
-    let server_euid = unsafe { libc::getuid() };
-    if client_euid == 0 ||
-       client_euid == server_euid ||
-       Some(client_euid) == wanted
-    {
-      return Authorised::authorise();
-    }
-    throw!(anyhow!("{}: euid mismatch: client={:?} server={:?} wanted={:?}{}",
-                   &self.desc, client_euid, server_euid, wanted,
-                   xinfo.unwrap_or("")));
-  }
-
-  fn map_auth_err(&self, ae: AuthorisationError) -> MgmtError {
-    eprintln!("command connection {}: authorisation error: {}",
-              self.desc, ae.0);
-    MgmtError::AuthorisationError
-  }
-}
-
-#[throws(MgmtError)]
-fn authorise_scope(cs: &CommandStream, wanted: &ManagementScope)
-                   -> AuthorisedSatisfactory {
-  do_authorise_scope(cs, wanted)
-    .map_err(|e| cs.map_auth_err(e))?
-}
-
-#[throws(AuthorisationError)]
-fn do_authorise_scope(cs: &CommandStream, wanted: &ManagementScope)
-                   -> AuthorisedSatisfactory {
-  type AS<T> = (T, ManagementScope);
-
-  match &wanted {
-
-    ManagementScope::Server => {
-      let y : AS<
-        Authorised<(Passwd,uid_t)>,
-      > = {
-        let ok = cs.authorised_uid(None,None)?;
-        (ok,
-         ManagementScope::Server)
-      };
-      return y.into()
-    },
-
-    ManagementScope::Unix { user: wanted } => {
-      let y : AS<
-        Authorised<(Passwd,uid_t)>,
-      > = {
-        struct AuthorisedIf { authorised_for : Option<uid_t> };
-
-        let pwent = Passwd::from_name(&wanted)
-          .map_err(
-            |e| anyhow!("looking up requested username {:?}: {:?}",
-                        &wanted, &e)
-          )?
-          .ok_or_else(
-            || AuthorisationError(format!(
-              "requested username {:?} not found", &wanted
-            ))
-          )?;
-
-        let (in_userlist, xinfo) = (||{ <Result<_,AE>>::Ok({
-          let allowed = BufReader::new(match File::open(USERLIST) {
-            Err(e) if e.kind() == ErrorKind::NotFound => {
-              return Ok((
-                AuthorisedIf{ authorised_for: None },
-                Some(format!(" user list {} does not exist", USERLIST))
-              ))
-            },
-            r => r,            
-          }?);
-          allowed
-            .lines()
-            .filter_map(|le| match le {
-              Ok(l) if l.trim() == wanted => Some(
-                Ok((
-                  AuthorisedIf{ authorised_for: Some(pwent.uid) },
-                  None
-                ))
-              ),
-              Ok(_) => None,
-              Err(e) => Some(<Result<_,AE>>::Err(e.into())),
-            })
-            .next()
-            .unwrap_or_else(
-              || Ok((
-                AuthorisedIf{ authorised_for: None },
-                Some(format!(" requested username {:?} not in {}",
-                             &wanted, USERLIST)),
-              ))
-            )?
-        })})()?;
-
-        let AuthorisedIf{ authorised_for } = in_userlist;
-        let info = xinfo.as_deref();
-        let ok = cs.authorised_uid(authorised_for, info)?;
-        (ok,
-         ManagementScope::Unix { user: pwent.name })
-      };
-      y.into()
-    },
-
-  }
-}
-
-
-#[throws(ME)]
-fn execute_for_game(cs: &CommandStream, ig: &mut InstanceGuard,
-                    mut insns: Vec<MgmtGameInstruction>,
-                    how: MgmtGameUpdateMode) -> MgmtResponse {
-  let mut uh = UpdateHandler::from_how(how);
-  let mut responses = Vec::with_capacity(insns.len());
-  let ok = (||{
-    for insn in insns.drain(0..) {
-      let (updates, resp) = execute_game_insn(cs, ig, insn)?;
-      uh.accumulate(ig, updates)?;
-      responses.push(resp);
-    }
-    uh.complete(cs,ig)?;
-    Ok(None)
-  })();
-  MgmtResponse::AlterGame {
-    responses,
-    error: ok.unwrap_or_else(Some)
-  }
-}
-
-const XXX_START_POS : Pos = [20,20];
-const XXX_DEFAULT_POSD : Pos = [5,5];
-
-const CREATE_PIECES_MAX : u32 = 300;
+// ---------- game command implementations ----------
 
 type ExecuteGameInsnResults = (
   ExecuteGameChangeUpdates,
@@ -510,7 +264,30 @@ fn execute_game_insn(cs: &CommandStream,
     },
   }
 }
-//---------- game update processing ----------
+
+// ---------- how to execute game commands & handle their updates ----------
+
+#[throws(ME)]
+fn execute_for_game(cs: &CommandStream, ig: &mut InstanceGuard,
+                    mut insns: Vec<MgmtGameInstruction>,
+                    how: MgmtGameUpdateMode) -> MgmtResponse {
+  let mut uh = UpdateHandler::from_how(how);
+  let mut responses = Vec::with_capacity(insns.len());
+  let ok = (||{
+    for insn in insns.drain(0..) {
+      let (updates, resp) = execute_game_insn(cs, ig, insn)?;
+      uh.accumulate(ig, updates)?;
+      responses.push(resp);
+    }
+    uh.complete(cs,ig)?;
+    Ok(None)
+  })();
+  MgmtResponse::AlterGame {
+    responses,
+    error: ok.unwrap_or_else(Some)
+  }
+}
+
 
 #[derive(Debug,Default)]
 struct UpdateHandlerBulk {
@@ -596,6 +373,231 @@ impl UpdateHandler {
   }
 }
 
+// ========== general implementation ==========
+
+// ---------- core listener implementation ----------
+
+struct CommandStream<'d> {
+  euid : Result<u32, ConnectionEuidDiscoverEerror>,
+  desc : &'d str,
+  scope : Option<ManagementScope>,
+  chan : MgmtChannel,
+}
+
+impl CommandStream<'_> {
+  #[throws(CSE)]
+  pub fn mainloop(mut self) {
+    loop {
+      use MgmtChannelReadError::*;
+      let resp = match self.chan.read() {
+        Ok(cmd) => match execute(&mut self, cmd) {
+          Ok(resp) => resp,
+          Err(error) => MgmtResponse::Error { error },
+        },
+        Err(EOF) => break,
+        Err(IO(e)) => Err(e).context("read command stream")?,
+        Err(Parse(s)) => MgmtResponse::Error { error : ParseFailed(s) },
+      };
+      self.chan.write(&resp).context("swrite command stream")?;
+    }
+  }
+
+  #[throws(MgmtError)]
+  fn get_scope(&self) -> &ManagementScope {
+    self.scope.as_ref().ok_or(NoScope)?
+  }
+}
+
+impl CommandListener {
+  #[throws(StartupError)]
+  pub fn new() -> Self {
+    let path = SOCKET_PATH;
+    match fs::remove_file(path) {
+      Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(()),
+      r => r,
+    }
+    .with_context(|| format!("remove socket {:?} before we bind", &path))?;
+    let listener = UnixListener::bind(path)
+      .with_context(|| format!("bind command socket {:?}", &path))?;
+
+    fs::set_permissions(path, unix::fs::PermissionsExt::from_mode(0o666))
+      .with_context(|| format!("chmod sommand socket {:?}", &path))?;
+
+    CommandListener { listener }
+  }
+
+  #[throws(StartupError)]
+  pub fn spawn(mut self) {
+    thread::spawn(move ||{
+      loop {
+        self.accept_one().unwrap_or_else(
+          |e| eprintln!("accept/spawn failed: {:?}", e)
+        );
+      }
+    })
+  }
+
+  #[throws(CSE)]
+  fn accept_one(&mut self) {
+    let (conn, _caller) = self.listener.accept().context("accept")?;
+    let mut desc = format!("{:>5}", conn.as_raw_fd());
+    eprintln!("command connection {}: accepted", &desc);
+    thread::spawn(move||{
+      match (||{
+        let euid = conn.initial_peer_credentials()
+          .map(|creds| creds.euid())
+          .map_err(|e| ConnectionEuidDiscoverEerror(format!("{}", e)));
+
+        #[derive(Error,Debug)]
+        struct EuidLookupError(String);
+        display_as_debug!{EuidLookupError}
+        impl From<&E> for EuidLookupError where E : Display {
+          fn from(e: &E) -> Self { EuidLookupError(format!("{}",e)) }
+        }
+
+        let user_desc : String = (||{
+          let euid = euid.clone()?;
+          let pwent = Passwd::from_uid(euid);
+          let show_username =
+            pwent.map_or_else(|| format!("<euid {}>", euid),
+                              |p| p.name);
+          <Result<_,AE>>::Ok(show_username)
+        })().unwrap_or_else(|e| format!("<error: {}>", e));
+        write!(&mut desc, " user={}", user_desc)?;
+
+        let chan = MgmtChannel::new(conn)?;
+
+        let cs = CommandStream {
+          scope: None, desc: &desc,
+          chan, euid,
+        };
+        cs.mainloop()?;
+        
+        <Result<_,StartupError>>::Ok(())
+      })() {
+        Ok(()) => eprintln!("command connection {}: disconnected", &desc),
+        Err(e) => eprintln!("command connection {}: error: {:?}", &desc, e),
+      }
+    });
+  }
+}
+
+//---------- authorisation ----------
+
+#[derive(Debug,Error,Clone)]
+#[error("connection euid lookup failed (at connection initiation): {0}")]
+pub struct ConnectionEuidDiscoverEerror(String);
+
+impl CommandStream<'_> {
+  #[throws(AuthorisationError)]
+  fn authorised_uid(&self, wanted: Option<uid_t>, xinfo: Option<&str>)
+                    -> Authorised<(Passwd,uid_t),> {
+    let client_euid = *self.euid.as_ref().map_err(|e| e.clone())?;
+    let server_euid = unsafe { libc::getuid() };
+    if client_euid == 0 ||
+       client_euid == server_euid ||
+       Some(client_euid) == wanted
+    {
+      return Authorised::authorise();
+    }
+    throw!(anyhow!("{}: euid mismatch: client={:?} server={:?} wanted={:?}{}",
+                   &self.desc, client_euid, server_euid, wanted,
+                   xinfo.unwrap_or("")));
+  }
+
+  fn map_auth_err(&self, ae: AuthorisationError) -> MgmtError {
+    eprintln!("command connection {}: authorisation error: {}",
+              self.desc, ae.0);
+    MgmtError::AuthorisationError
+  }
+}
+
+#[throws(MgmtError)]
+fn authorise_scope(cs: &CommandStream, wanted: &ManagementScope)
+                   -> AuthorisedSatisfactory {
+  do_authorise_scope(cs, wanted)
+    .map_err(|e| cs.map_auth_err(e))?
+}
+
+#[throws(AuthorisationError)]
+fn do_authorise_scope(cs: &CommandStream, wanted: &ManagementScope)
+                   -> AuthorisedSatisfactory {
+  type AS<T> = (T, ManagementScope);
+
+  match &wanted {
+
+    ManagementScope::Server => {
+      let y : AS<
+        Authorised<(Passwd,uid_t)>,
+      > = {
+        let ok = cs.authorised_uid(None,None)?;
+        (ok,
+         ManagementScope::Server)
+      };
+      return y.into()
+    },
+
+    ManagementScope::Unix { user: wanted } => {
+      let y : AS<
+        Authorised<(Passwd,uid_t)>,
+      > = {
+        struct AuthorisedIf { authorised_for : Option<uid_t> };
+
+        let pwent = Passwd::from_name(&wanted)
+          .map_err(
+            |e| anyhow!("looking up requested username {:?}: {:?}",
+                        &wanted, &e)
+          )?
+          .ok_or_else(
+            || AuthorisationError(format!(
+              "requested username {:?} not found", &wanted
+            ))
+          )?;
+
+        let (in_userlist, xinfo) = (||{ <Result<_,AE>>::Ok({
+          let allowed = BufReader::new(match File::open(USERLIST) {
+            Err(e) if e.kind() == ErrorKind::NotFound => {
+              return Ok((
+                AuthorisedIf{ authorised_for: None },
+                Some(format!(" user list {} does not exist", USERLIST))
+              ))
+            },
+            r => r,            
+          }?);
+          allowed
+            .lines()
+            .filter_map(|le| match le {
+              Ok(l) if l.trim() == wanted => Some(
+                Ok((
+                  AuthorisedIf{ authorised_for: Some(pwent.uid) },
+                  None
+                ))
+              ),
+              Ok(_) => None,
+              Err(e) => Some(<Result<_,AE>>::Err(e.into())),
+            })
+            .next()
+            .unwrap_or_else(
+              || Ok((
+                AuthorisedIf{ authorised_for: None },
+                Some(format!(" requested username {:?} not in {}",
+                             &wanted, USERLIST)),
+              ))
+            )?
+        })})()?;
+
+        let AuthorisedIf{ authorised_for } = in_userlist;
+        let info = xinfo.as_deref();
+        let ok = cs.authorised_uid(authorised_for, info)?;
+        (ok,
+         ManagementScope::Unix { user: pwent.name })
+      };
+      y.into()
+    },
+
+  }
+}
+
 use authproofs::*;
 use authproofs::AuthorisationError;
 
index b9ad1171111693cc504edd05975bb0243e5b22f5..73d03025985d3cd680d633203aab8e3cb4df0bd1 100644 (file)
@@ -9,11 +9,6 @@ slotmap::new_key_type!{
   pub struct PieceId;
 }
 
-define_index_type! {
-  #[derive(Default)]
-  pub struct FaceId = u8;
-}
-
 #[derive(Copy,Clone,Debug,Ord,PartialOrd,Eq,PartialEq)]
 #[derive(Serialize,Deserialize)]
 #[serde(transparent)]
index 7bc960b6ca28a48885e1708bddc08cb9da637345..8ef6eca860724c68bbc4a55742de9d98a76be67a 100644 (file)
@@ -9,10 +9,6 @@ use std::sync::PoisonError;
 
 visible_slotmap_key!{ ClientId('C') }
 
-#[derive(Clone,Debug,Eq,PartialEq,Ord,PartialOrd,Hash,Serialize,Deserialize)]
-#[serde(transparent)]
-pub struct RawToken (pub String);
-
 // ---------- public data structure ----------
 
 #[derive(Debug,Serialize,Deserialize)]
index a9e44f47822e7dc14a4f573d05adb16e43e2d08d..d1f04b3875c332b962684b12fd18ecf8d4902dff 100644 (file)
@@ -84,6 +84,4 @@ pub type AE = anyhow::Error;
 pub type OE = OnlineError;
 
 pub type SvgData = Vec<u8>;
-pub type Coord = isize;
-pub type Pos = [Coord; 2];
 pub type Colour = String;
index 5470e155d074319b08da6a64c3cc52e5d87aaf7d..dc59944ad33e26d37f398594631de8c5efd7e7a4 100644 (file)
@@ -1,7 +1,26 @@
+// game specs
 
-#![allow(dead_code)]
+use serde::{Serialize,Deserialize};
+use fehler::throws;
+use index_vec::define_index_type;
+use crate::gamestate::PieceSpec;
+use std::fmt::Debug;
+use implementation::PlayerAccessSpec;
 
-use crate::imports::*;
+//---------- common types ----------
+
+pub type Coord = isize;
+
+pub type Pos = [Coord; 2];
+
+#[derive(Clone,Debug,Eq,PartialEq,Ord,PartialOrd,Hash,Serialize,Deserialize)]
+#[serde(transparent)]
+pub struct RawToken (pub String);
+
+define_index_type! {
+  #[derive(Default)]
+  pub struct FaceId = u8;
+}
 
 //---------- Table TOML file ----------
 
@@ -38,38 +57,44 @@ pub struct PiecesSpec {
   pub count : Option<u32>,
   pub face : Option<FaceId>,
   #[serde(flatten)]
-  pub info : Box<dyn PieceSpec>,
+  pub info : Box<dyn PieceSpec>, // see pieces.rs
 }
 
 //----------  Implementation ----------
 
-#[typetag::serde(tag="access")]
-pub trait PlayerAccessSpec : Debug {
-  fn token_mgi(&self, _player: PlayerId) -> Option<MgmtGameInstruction> {
-    None
+mod implementation {
+  use super::*;
+  use crate::imports::*;
+  type Insn = crate::commands::MgmtGameInstruction;
+
+  #[typetag::serde(tag="access")]
+  pub trait PlayerAccessSpec : Debug {
+    fn token_mgi(&self, _player: PlayerId) -> Option<MgmtGameInstruction> {
+      None
+    }
+    fn deliver_tokens(&self, ps: &PlayerSpec, tokens: &[RawToken])
+                      -> Result<(),AE>;
   }
-  fn deliver_tokens(&self, ps: &PlayerSpec, tokens: &[RawToken])
-    -> Result<(),AE>;
-}
 
-#[typetag::serde]
-impl PlayerAccessSpec for FixedToken {
-  fn token_mgi(&self, player: PlayerId) -> Option<MgmtGameInstruction> {
-    Some(MgmtGameInstruction::SetFixedPlayerAccess {
-      player,
-      token: self.token.clone(),
-    })
+  #[typetag::serde]
+  impl PlayerAccessSpec for FixedToken {
+    fn token_mgi(&self, player: PlayerId) -> Option<MgmtGameInstruction> {
+      Some(Insn::SetFixedPlayerAccess {
+        player,
+        token: self.token.clone(),
+      })
+    }
+    #[throws(AE)]
+    fn deliver_tokens(&self, _ps: &PlayerSpec, _tokens: &[RawToken]) { }
   }
-  #[throws(AE)]
-  fn deliver_tokens(&self, _ps: &PlayerSpec, _tokens: &[RawToken]) { }
-}
 
-#[typetag::serde]
-impl PlayerAccessSpec for TokenOnStdout {
-  #[throws(AE)]
-  fn deliver_tokens(&self, ps: &PlayerSpec, tokens: &[RawToken]) {
-    for token in tokens {
-      println!("access nick={:?} token={}", &ps.nick, token.0);
+  #[typetag::serde]
+  impl PlayerAccessSpec for TokenOnStdout {
+    #[throws(AE)]
+    fn deliver_tokens(&self, ps: &PlayerSpec, tokens: &[RawToken]) {
+      for token in tokens {
+        println!("access nick={:?} token={}", &ps.nick, token.0);
+      }
     }
   }
 }