##// END OF EJS Templates
rust: convert Unix path to CString transparently...
Yuya Nishihara -
r35650:fa9747e7 default
parent child Browse files
Show More
@@ -1,225 +1,233 b''
1 // main.rs -- Main routines for `hg` program
1 // main.rs -- Main routines for `hg` program
2 //
2 //
3 // Copyright 2017 Gregory Szorc <gregory.szorc@gmail.com>
3 // Copyright 2017 Gregory Szorc <gregory.szorc@gmail.com>
4 //
4 //
5 // This software may be used and distributed according to the terms of the
5 // This software may be used and distributed according to the terms of the
6 // GNU General Public License version 2 or any later version.
6 // GNU General Public License version 2 or any later version.
7
7
8 extern crate libc;
8 extern crate libc;
9 extern crate cpython;
9 extern crate cpython;
10 extern crate python27_sys;
10 extern crate python27_sys;
11
11
12 use cpython::{NoArgs, ObjectProtocol, PyModule, PyResult, Python};
12 use cpython::{NoArgs, ObjectProtocol, PyModule, PyResult, Python};
13 use libc::{c_char, c_int};
13 use libc::{c_char, c_int};
14
14
15 use std::env;
15 use std::env;
16 use std::path::PathBuf;
16 use std::path::PathBuf;
17 use std::ffi::{CString, OsStr};
17 use std::ffi::{CString, OsStr};
18 #[cfg(target_family = "unix")]
18 #[cfg(target_family = "unix")]
19 use std::os::unix::ffi::OsStringExt;
19 use std::os::unix::ffi::{OsStrExt, OsStringExt};
20
20
21 #[derive(Debug)]
21 #[derive(Debug)]
22 struct Environment {
22 struct Environment {
23 _exe: PathBuf,
23 _exe: PathBuf,
24 python_exe: PathBuf,
24 python_exe: PathBuf,
25 python_home: PathBuf,
25 python_home: PathBuf,
26 mercurial_modules: PathBuf,
26 mercurial_modules: PathBuf,
27 }
27 }
28
28
29 /// Run Mercurial locally from a source distribution or checkout.
29 /// Run Mercurial locally from a source distribution or checkout.
30 ///
30 ///
31 /// hg is <srcdir>/rust/target/<target>/hg
31 /// hg is <srcdir>/rust/target/<target>/hg
32 /// Python interpreter is detected by build script.
32 /// Python interpreter is detected by build script.
33 /// Python home is relative to Python interpreter.
33 /// Python home is relative to Python interpreter.
34 /// Mercurial files are relative to hg binary, which is relative to source root.
34 /// Mercurial files are relative to hg binary, which is relative to source root.
35 #[cfg(feature = "localdev")]
35 #[cfg(feature = "localdev")]
36 fn get_environment() -> Environment {
36 fn get_environment() -> Environment {
37 let exe = env::current_exe().unwrap();
37 let exe = env::current_exe().unwrap();
38
38
39 let mut mercurial_modules = exe.clone();
39 let mut mercurial_modules = exe.clone();
40 mercurial_modules.pop(); // /rust/target/<target>
40 mercurial_modules.pop(); // /rust/target/<target>
41 mercurial_modules.pop(); // /rust/target
41 mercurial_modules.pop(); // /rust/target
42 mercurial_modules.pop(); // /rust
42 mercurial_modules.pop(); // /rust
43 mercurial_modules.pop(); // /
43 mercurial_modules.pop(); // /
44
44
45 let python_exe: &'static str = env!("PYTHON_INTERPRETER");
45 let python_exe: &'static str = env!("PYTHON_INTERPRETER");
46 let python_exe = PathBuf::from(python_exe);
46 let python_exe = PathBuf::from(python_exe);
47
47
48 let mut python_home = python_exe.clone();
48 let mut python_home = python_exe.clone();
49 python_home.pop();
49 python_home.pop();
50
50
51 // On Windows, python2.7.exe exists at the root directory of the Python
51 // On Windows, python2.7.exe exists at the root directory of the Python
52 // install. Everywhere else, the Python install root is one level up.
52 // install. Everywhere else, the Python install root is one level up.
53 if !python_exe.ends_with("python2.7.exe") {
53 if !python_exe.ends_with("python2.7.exe") {
54 python_home.pop();
54 python_home.pop();
55 }
55 }
56
56
57 Environment {
57 Environment {
58 _exe: exe.clone(),
58 _exe: exe.clone(),
59 python_exe: python_exe,
59 python_exe: python_exe,
60 python_home: python_home,
60 python_home: python_home,
61 mercurial_modules: mercurial_modules.to_path_buf(),
61 mercurial_modules: mercurial_modules.to_path_buf(),
62 }
62 }
63 }
63 }
64
64
65 // On UNIX, platform string is just bytes and should not contain NUL.
66 #[cfg(target_family = "unix")]
67 fn cstring_from_os<T: AsRef<OsStr>>(s: T) -> CString {
68 CString::new(s.as_ref().as_bytes()).unwrap()
69 }
70
71 // TODO convert to ANSI characters?
72 #[cfg(target_family = "windows")]
65 fn cstring_from_os<T: AsRef<OsStr>>(s: T) -> CString {
73 fn cstring_from_os<T: AsRef<OsStr>>(s: T) -> CString {
66 CString::new(s.as_ref().to_str().unwrap()).unwrap()
74 CString::new(s.as_ref().to_str().unwrap()).unwrap()
67 }
75 }
68
76
69 // On UNIX, argv starts as an array of char*. So it is easy to convert
77 // On UNIX, argv starts as an array of char*. So it is easy to convert
70 // to C strings.
78 // to C strings.
71 #[cfg(target_family = "unix")]
79 #[cfg(target_family = "unix")]
72 fn args_to_cstrings() -> Vec<CString> {
80 fn args_to_cstrings() -> Vec<CString> {
73 env::args_os()
81 env::args_os()
74 .map(|a| CString::new(a.into_vec()).unwrap())
82 .map(|a| CString::new(a.into_vec()).unwrap())
75 .collect()
83 .collect()
76 }
84 }
77
85
78 // TODO Windows support is incomplete. We should either use env::args_os()
86 // TODO Windows support is incomplete. We should either use env::args_os()
79 // (or call into GetCommandLineW() + CommandLinetoArgvW()), convert these to
87 // (or call into GetCommandLineW() + CommandLinetoArgvW()), convert these to
80 // PyUnicode instances, and pass these into Python/Mercurial outside the
88 // PyUnicode instances, and pass these into Python/Mercurial outside the
81 // standard PySys_SetArgvEx() mechanism. This will allow us to preserve the
89 // standard PySys_SetArgvEx() mechanism. This will allow us to preserve the
82 // raw bytes (since PySys_SetArgvEx() is based on char* and can drop wchar
90 // raw bytes (since PySys_SetArgvEx() is based on char* and can drop wchar
83 // data.
91 // data.
84 //
92 //
85 // For now, we use env::args(). This will choke on invalid UTF-8 arguments.
93 // For now, we use env::args(). This will choke on invalid UTF-8 arguments.
86 // But it is better than nothing.
94 // But it is better than nothing.
87 #[cfg(target_family = "windows")]
95 #[cfg(target_family = "windows")]
88 fn args_to_cstrings() -> Vec<CString> {
96 fn args_to_cstrings() -> Vec<CString> {
89 env::args().map(|a| CString::new(a).unwrap()).collect()
97 env::args().map(|a| CString::new(a).unwrap()).collect()
90 }
98 }
91
99
92 fn set_python_home(env: &Environment) {
100 fn set_python_home(env: &Environment) {
93 let raw = cstring_from_os(&env.python_home).into_raw();
101 let raw = cstring_from_os(&env.python_home).into_raw();
94 unsafe {
102 unsafe {
95 python27_sys::Py_SetPythonHome(raw);
103 python27_sys::Py_SetPythonHome(raw);
96 }
104 }
97 }
105 }
98
106
99 fn update_encoding(_py: Python, _sys_mod: &PyModule) {
107 fn update_encoding(_py: Python, _sys_mod: &PyModule) {
100 // Call sys.setdefaultencoding("undefined") if HGUNICODEPEDANTRY is set.
108 // Call sys.setdefaultencoding("undefined") if HGUNICODEPEDANTRY is set.
101 let pedantry = env::var("HGUNICODEPEDANTRY").is_ok();
109 let pedantry = env::var("HGUNICODEPEDANTRY").is_ok();
102
110
103 if pedantry {
111 if pedantry {
104 // site.py removes the sys.setdefaultencoding attribute. So we need
112 // site.py removes the sys.setdefaultencoding attribute. So we need
105 // to reload the module to get a handle on it. This is a lesser
113 // to reload the module to get a handle on it. This is a lesser
106 // used feature and we'll support this later.
114 // used feature and we'll support this later.
107 // TODO support this
115 // TODO support this
108 panic!("HGUNICODEPEDANTRY is not yet supported");
116 panic!("HGUNICODEPEDANTRY is not yet supported");
109 }
117 }
110 }
118 }
111
119
112 fn update_modules_path(env: &Environment, py: Python, sys_mod: &PyModule) {
120 fn update_modules_path(env: &Environment, py: Python, sys_mod: &PyModule) {
113 let sys_path = sys_mod.get(py, "path").unwrap();
121 let sys_path = sys_mod.get(py, "path").unwrap();
114 sys_path
122 sys_path
115 .call_method(py, "insert", (0, env.mercurial_modules.to_str()), None)
123 .call_method(py, "insert", (0, env.mercurial_modules.to_str()), None)
116 .expect("failed to update sys.path to location of Mercurial modules");
124 .expect("failed to update sys.path to location of Mercurial modules");
117 }
125 }
118
126
119 fn run() -> Result<(), i32> {
127 fn run() -> Result<(), i32> {
120 let env = get_environment();
128 let env = get_environment();
121
129
122 //println!("{:?}", env);
130 //println!("{:?}", env);
123
131
124 // Tell Python where it is installed.
132 // Tell Python where it is installed.
125 set_python_home(&env);
133 set_python_home(&env);
126
134
127 // Set program name. The backing memory needs to live for the duration of the
135 // Set program name. The backing memory needs to live for the duration of the
128 // interpreter.
136 // interpreter.
129 //
137 //
130 // TODO consider storing this in a static or associating with lifetime of
138 // TODO consider storing this in a static or associating with lifetime of
131 // the Python interpreter.
139 // the Python interpreter.
132 //
140 //
133 // Yes, we use the path to the Python interpreter not argv[0] here. The
141 // Yes, we use the path to the Python interpreter not argv[0] here. The
134 // reason is because Python uses the given path to find the location of
142 // reason is because Python uses the given path to find the location of
135 // Python files. Apparently we could define our own ``Py_GetPath()``
143 // Python files. Apparently we could define our own ``Py_GetPath()``
136 // implementation. But this may require statically linking Python, which is
144 // implementation. But this may require statically linking Python, which is
137 // not desirable.
145 // not desirable.
138 let program_name = cstring_from_os(&env.python_exe).as_ptr();
146 let program_name = cstring_from_os(&env.python_exe).as_ptr();
139 unsafe {
147 unsafe {
140 python27_sys::Py_SetProgramName(program_name as *mut i8);
148 python27_sys::Py_SetProgramName(program_name as *mut i8);
141 }
149 }
142
150
143 unsafe {
151 unsafe {
144 python27_sys::Py_Initialize();
152 python27_sys::Py_Initialize();
145 }
153 }
146
154
147 // https://docs.python.org/2/c-api/init.html#c.PySys_SetArgvEx has important
155 // https://docs.python.org/2/c-api/init.html#c.PySys_SetArgvEx has important
148 // usage information about PySys_SetArgvEx:
156 // usage information about PySys_SetArgvEx:
149 //
157 //
150 // * It says the first argument should be the script that is being executed.
158 // * It says the first argument should be the script that is being executed.
151 // If not a script, it can be empty. We are definitely not a script.
159 // If not a script, it can be empty. We are definitely not a script.
152 // However, parts of Mercurial do look at sys.argv[0]. So we need to set
160 // However, parts of Mercurial do look at sys.argv[0]. So we need to set
153 // something here.
161 // something here.
154 //
162 //
155 // * When embedding Python, we should use ``PySys_SetArgvEx()`` and set
163 // * When embedding Python, we should use ``PySys_SetArgvEx()`` and set
156 // ``updatepath=0`` for security reasons. Essentially, Python's default
164 // ``updatepath=0`` for security reasons. Essentially, Python's default
157 // logic will treat an empty argv[0] in a manner that could result in
165 // logic will treat an empty argv[0] in a manner that could result in
158 // sys.path picking up directories it shouldn't and this could lead to
166 // sys.path picking up directories it shouldn't and this could lead to
159 // loading untrusted modules.
167 // loading untrusted modules.
160
168
161 // env::args() will panic if it sees a non-UTF-8 byte sequence. And
169 // env::args() will panic if it sees a non-UTF-8 byte sequence. And
162 // Mercurial supports arbitrary encodings of input data. So we need to
170 // Mercurial supports arbitrary encodings of input data. So we need to
163 // use OS-specific mechanisms to get the raw bytes without UTF-8
171 // use OS-specific mechanisms to get the raw bytes without UTF-8
164 // interference.
172 // interference.
165 let args = args_to_cstrings();
173 let args = args_to_cstrings();
166 let argv: Vec<*const c_char> = args.iter().map(|a| a.as_ptr()).collect();
174 let argv: Vec<*const c_char> = args.iter().map(|a| a.as_ptr()).collect();
167
175
168 unsafe {
176 unsafe {
169 python27_sys::PySys_SetArgvEx(args.len() as c_int, argv.as_ptr() as *mut *mut i8, 0);
177 python27_sys::PySys_SetArgvEx(args.len() as c_int, argv.as_ptr() as *mut *mut i8, 0);
170 }
178 }
171
179
172 let result;
180 let result;
173 {
181 {
174 // These need to be dropped before we call Py_Finalize(). Hence the
182 // These need to be dropped before we call Py_Finalize(). Hence the
175 // block.
183 // block.
176 let gil = Python::acquire_gil();
184 let gil = Python::acquire_gil();
177 let py = gil.python();
185 let py = gil.python();
178
186
179 // Mercurial code could call sys.exit(), which will call exit()
187 // Mercurial code could call sys.exit(), which will call exit()
180 // itself. So this may not return.
188 // itself. So this may not return.
181 // TODO this may cause issues on Windows due to the CRT mismatch.
189 // TODO this may cause issues on Windows due to the CRT mismatch.
182 // Investigate if we can intercept sys.exit() or SystemExit() to
190 // Investigate if we can intercept sys.exit() or SystemExit() to
183 // ensure we handle process exit.
191 // ensure we handle process exit.
184 result = match run_py(&env, py) {
192 result = match run_py(&env, py) {
185 // Print unhandled exceptions and exit code 255, as this is what
193 // Print unhandled exceptions and exit code 255, as this is what
186 // `python` does.
194 // `python` does.
187 Err(err) => {
195 Err(err) => {
188 err.print(py);
196 err.print(py);
189 Err(255)
197 Err(255)
190 }
198 }
191 Ok(()) => Ok(()),
199 Ok(()) => Ok(()),
192 };
200 };
193 }
201 }
194
202
195 unsafe {
203 unsafe {
196 python27_sys::Py_Finalize();
204 python27_sys::Py_Finalize();
197 }
205 }
198
206
199 result
207 result
200 }
208 }
201
209
202 fn run_py(env: &Environment, py: Python) -> PyResult<()> {
210 fn run_py(env: &Environment, py: Python) -> PyResult<()> {
203 let sys_mod = py.import("sys").unwrap();
211 let sys_mod = py.import("sys").unwrap();
204
212
205 update_encoding(py, &sys_mod);
213 update_encoding(py, &sys_mod);
206 update_modules_path(&env, py, &sys_mod);
214 update_modules_path(&env, py, &sys_mod);
207
215
208 // TODO consider a better error message on failure to import.
216 // TODO consider a better error message on failure to import.
209 let demand_mod = py.import("hgdemandimport")?;
217 let demand_mod = py.import("hgdemandimport")?;
210 demand_mod.call(py, "enable", NoArgs, None)?;
218 demand_mod.call(py, "enable", NoArgs, None)?;
211
219
212 let dispatch_mod = py.import("mercurial.dispatch")?;
220 let dispatch_mod = py.import("mercurial.dispatch")?;
213 dispatch_mod.call(py, "run", NoArgs, None)?;
221 dispatch_mod.call(py, "run", NoArgs, None)?;
214
222
215 Ok(())
223 Ok(())
216 }
224 }
217
225
218 fn main() {
226 fn main() {
219 let exit_code = match run() {
227 let exit_code = match run() {
220 Err(err) => err,
228 Err(err) => err,
221 Ok(()) => 0,
229 Ok(()) => 0,
222 };
230 };
223
231
224 std::process::exit(exit_code);
232 std::process::exit(exit_code);
225 }
233 }
General Comments 0
You need to be logged in to leave comments. Login now