##// END OF EJS Templates
rust-chg: add config validation and process returned instructions...
Yuya Nishihara -
r45173:9ce613d6 default
parent child Browse files
Show More
@@ -21,10 +21,11 b' use tokio_process::{Child, CommandExt};'
21 use tokio_timer;
21 use tokio_timer;
22
22
23 use super::clientext::ChgClientExt;
23 use super::clientext::ChgClientExt;
24 use super::message::ServerSpec;
24 use super::message::{Instruction, ServerSpec};
25 use super::procutil;
25 use super::procutil;
26
26
27 const REQUIRED_SERVER_CAPABILITIES: &[&str] = &["attachio", "chdir", "runcommand", "setenv"];
27 const REQUIRED_SERVER_CAPABILITIES: &[&str] =
28 &["attachio", "chdir", "runcommand", "setenv", "validate"];
28
29
29 /// Helper to connect to and spawn a server process.
30 /// Helper to connect to and spawn a server process.
30 #[derive(Clone, Debug)]
31 #[derive(Clone, Debug)]
@@ -35,6 +36,7 b' pub struct Locator {'
35 env_vars: Vec<(OsString, OsString)>,
36 env_vars: Vec<(OsString, OsString)>,
36 process_id: u32,
37 process_id: u32,
37 base_sock_path: PathBuf,
38 base_sock_path: PathBuf,
39 redirect_sock_path: Option<PathBuf>,
38 timeout: Duration,
40 timeout: Duration,
39 }
41 }
40
42
@@ -51,6 +53,7 b' impl Locator {'
51 env_vars: env::vars_os().collect(),
53 env_vars: env::vars_os().collect(),
52 process_id: process::id(),
54 process_id: process::id(),
53 base_sock_path: prepare_server_socket_path()?,
55 base_sock_path: prepare_server_socket_path()?,
56 redirect_sock_path: None,
54 timeout: default_timeout(),
57 timeout: default_timeout(),
55 })
58 })
56 }
59 }
@@ -77,16 +80,110 b' impl Locator {'
77 ///
80 ///
78 /// The server process will be spawned if not running.
81 /// The server process will be spawned if not running.
79 pub fn connect(self) -> impl Future<Item = (Self, UnixClient), Error = io::Error> {
82 pub fn connect(self) -> impl Future<Item = (Self, UnixClient), Error = io::Error> {
80 self.try_connect()
83 future::loop_fn((self, 0), |(loc, cnt)| {
84 if cnt < 10 {
85 let fut = loc
86 .try_connect()
87 .and_then(|(loc, client)| {
88 client
89 .validate(&loc.hg_early_args)
90 .map(|(client, instructions)| (loc, client, instructions))
91 })
92 .and_then(move |(loc, client, instructions)| {
93 loc.run_instructions(client, instructions, cnt)
94 });
95 Either::A(fut)
96 } else {
97 let msg = format!(
98 concat!(
99 "too many redirections.\n",
100 "Please make sure {:?} is not a wrapper which ",
101 "changes sensitive environment variables ",
102 "before executing hg. If you have to use a ",
103 "wrapper, wrap chg instead of hg.",
104 ),
105 loc.hg_command
106 );
107 Either::B(future::err(io::Error::new(io::ErrorKind::Other, msg)))
108 }
109 })
110 }
111
112 /// Runs instructions received from the server.
113 fn run_instructions(
114 mut self,
115 client: UnixClient,
116 instructions: Vec<Instruction>,
117 cnt: usize,
118 ) -> io::Result<Loop<(Self, UnixClient), (Self, usize)>> {
119 let mut reconnect = false;
120 for inst in instructions {
121 debug!("instruction: {:?}", inst);
122 match inst {
123 Instruction::Exit(_) => {
124 // Just returns the current connection to run the
125 // unparsable command and report the error
126 return Ok(Loop::Break((self, client)));
127 }
128 Instruction::Reconnect => {
129 reconnect = true;
130 }
131 Instruction::Redirect(path) => {
132 if path.parent() != self.base_sock_path.parent() {
133 let msg = format!(
134 "insecure redirect instruction from server: {}",
135 path.display()
136 );
137 return Err(io::Error::new(io::ErrorKind::InvalidData, msg));
138 }
139 self.redirect_sock_path = Some(path);
140 reconnect = true;
141 }
142 Instruction::Unlink(path) => {
143 if path.parent() != self.base_sock_path.parent() {
144 let msg = format!(
145 "insecure unlink instruction from server: {}",
146 path.display()
147 );
148 return Err(io::Error::new(io::ErrorKind::InvalidData, msg));
149 }
150 fs::remove_file(path).unwrap_or(()); // may race
151 }
152 }
153 }
154
155 if reconnect {
156 Ok(Loop::Continue((self, cnt + 1)))
157 } else {
158 Ok(Loop::Break((self, client)))
159 }
81 }
160 }
82
161
83 /// Tries to connect to the existing server, or spawns new if not running.
162 /// Tries to connect to the existing server, or spawns new if not running.
84 fn try_connect(self) -> impl Future<Item = (Self, UnixClient), Error = io::Error> {
163 fn try_connect(self) -> impl Future<Item = (Self, UnixClient), Error = io::Error> {
85 debug!("try connect to {}", self.base_sock_path.display());
164 let sock_path = self
86 UnixClient::connect(self.base_sock_path.clone())
165 .redirect_sock_path
87 .then(|res| match res {
166 .as_ref()
88 Ok(client) => Either::A(future::ok((self, client))),
167 .unwrap_or(&self.base_sock_path)
89 Err(_) => Either::B(self.spawn_connect()),
168 .clone();
169 debug!("try connect to {}", sock_path.display());
170 UnixClient::connect(sock_path)
171 .then(|res| {
172 match res {
173 Ok(client) => Either::A(future::ok((self, client))),
174 Err(_) => {
175 // Prevent us from being re-connected to the outdated
176 // master server: We were told by the server to redirect
177 // to redirect_sock_path, which didn't work. We do not
178 // want to connect to the same master server again
179 // because it would probably tell us the same thing.
180 if self.redirect_sock_path.is_some() {
181 fs::remove_file(&self.base_sock_path).unwrap_or(());
182 // may race
183 }
184 Either::B(self.spawn_connect())
185 }
186 }
90 })
187 })
91 .and_then(|(loc, client)| {
188 .and_then(|(loc, client)| {
92 check_server_capabilities(client.server_spec())?;
189 check_server_capabilities(client.server_spec())?;
General Comments 0
You need to be logged in to leave comments. Login now