Show More
@@ -21,10 +21,11 use tokio_process::{Child, CommandExt}; | |||
|
21 | 21 | use tokio_timer; |
|
22 | 22 | |
|
23 | 23 | use super::clientext::ChgClientExt; |
|
24 | use super::message::ServerSpec; | |
|
24 | use super::message::{Instruction, ServerSpec}; | |
|
25 | 25 | use super::procutil; |
|
26 | 26 | |
|
27 |
const REQUIRED_SERVER_CAPABILITIES: &[&str] = |
|
|
27 | const REQUIRED_SERVER_CAPABILITIES: &[&str] = | |
|
28 | &["attachio", "chdir", "runcommand", "setenv", "validate"]; | |
|
28 | 29 | |
|
29 | 30 | /// Helper to connect to and spawn a server process. |
|
30 | 31 | #[derive(Clone, Debug)] |
@@ -35,6 +36,7 pub struct Locator { | |||
|
35 | 36 | env_vars: Vec<(OsString, OsString)>, |
|
36 | 37 | process_id: u32, |
|
37 | 38 | base_sock_path: PathBuf, |
|
39 | redirect_sock_path: Option<PathBuf>, | |
|
38 | 40 | timeout: Duration, |
|
39 | 41 | } |
|
40 | 42 | |
@@ -51,6 +53,7 impl Locator { | |||
|
51 | 53 | env_vars: env::vars_os().collect(), |
|
52 | 54 | process_id: process::id(), |
|
53 | 55 | base_sock_path: prepare_server_socket_path()?, |
|
56 | redirect_sock_path: None, | |
|
54 | 57 | timeout: default_timeout(), |
|
55 | 58 | }) |
|
56 | 59 | } |
@@ -77,16 +80,110 impl Locator { | |||
|
77 | 80 | /// |
|
78 | 81 | /// The server process will be spawned if not running. |
|
79 | 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 | 162 | /// Tries to connect to the existing server, or spawns new if not running. |
|
84 | 163 | fn try_connect(self) -> impl Future<Item = (Self, UnixClient), Error = io::Error> { |
|
85 | debug!("try connect to {}", self.base_sock_path.display()); | |
|
86 | UnixClient::connect(self.base_sock_path.clone()) | |
|
87 | .then(|res| match res { | |
|
164 | let sock_path = self | |
|
165 | .redirect_sock_path | |
|
166 | .as_ref() | |
|
167 | .unwrap_or(&self.base_sock_path) | |
|
168 | .clone(); | |
|
169 | debug!("try connect to {}", sock_path.display()); | |
|
170 | UnixClient::connect(sock_path) | |
|
171 | .then(|res| { | |
|
172 | match res { | |
|
88 | 173 | Ok(client) => Either::A(future::ok((self, client))), |
|
89 | Err(_) => Either::B(self.spawn_connect()), | |
|
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 | 188 | .and_then(|(loc, client)| { |
|
92 | 189 | check_server_capabilities(client.server_spec())?; |
General Comments 0
You need to be logged in to leave comments.
Login now