ctypes-unicode-proclaunch 0.1.0a0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ctypes_unicode_proclaunch-0.1.0a0/LICENSE +21 -0
- ctypes_unicode_proclaunch-0.1.0a0/PKG-INFO +151 -0
- ctypes_unicode_proclaunch-0.1.0a0/README.md +129 -0
- ctypes_unicode_proclaunch-0.1.0a0/ctypes_unicode_proclaunch.egg-info/PKG-INFO +151 -0
- ctypes_unicode_proclaunch-0.1.0a0/ctypes_unicode_proclaunch.egg-info/SOURCES.txt +10 -0
- ctypes_unicode_proclaunch-0.1.0a0/ctypes_unicode_proclaunch.egg-info/dependency_links.txt +1 -0
- ctypes_unicode_proclaunch-0.1.0a0/ctypes_unicode_proclaunch.egg-info/requires.txt +8 -0
- ctypes_unicode_proclaunch-0.1.0a0/ctypes_unicode_proclaunch.egg-info/top_level.txt +1 -0
- ctypes_unicode_proclaunch-0.1.0a0/ctypes_unicode_proclaunch.py +563 -0
- ctypes_unicode_proclaunch-0.1.0a0/pyproject.toml +31 -0
- ctypes_unicode_proclaunch-0.1.0a0/setup.cfg +7 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Jifeng Wu
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ctypes-unicode-proclaunch
|
|
3
|
+
Version: 0.1.0a0
|
|
4
|
+
Summary: A minimal, robust, and Unicode-aware process launching library using ctypes.
|
|
5
|
+
Author-email: Jifeng Wu <jifengwu2k@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/jifengwu2k/ctypes-unicode-proclaunch
|
|
8
|
+
Project-URL: Bug Tracker, https://github.com/jifengwu2k/ctypes-unicode-proclaunch/issues
|
|
9
|
+
Classifier: Programming Language :: Python :: 2
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Requires-Python: >=2
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Requires-Dist: escape-nt-command-line-argument
|
|
16
|
+
Requires-Dist: find-unicode-executable
|
|
17
|
+
Requires-Dist: posix-or-nt
|
|
18
|
+
Requires-Dist: read-unicode-environment-variables-dictionary
|
|
19
|
+
Requires-Dist: send-recv-json
|
|
20
|
+
Requires-Dist: typing; python_version < "3.5"
|
|
21
|
+
Dynamic: license-file
|
|
22
|
+
|
|
23
|
+
# `ctypes-unicode-proclaunch`
|
|
24
|
+
|
|
25
|
+
A minimal, robust, and Unicode-aware process launching library using ctypes.
|
|
26
|
+
|
|
27
|
+
- No:
|
|
28
|
+
- Shell involvement: Arguments are not interpolated or globbed by any shell, ensuring "what you pass is what you
|
|
29
|
+
get".
|
|
30
|
+
- Python `subprocess` abstractions: No `Popen`/`communicate`/`universal_newlines`/encoding drama.
|
|
31
|
+
- Yes:
|
|
32
|
+
- Native system call usage for maximum control and correctness.
|
|
33
|
+
- `CreateProcessW` (NT)
|
|
34
|
+
- `fork` and `execve` (POSIX)
|
|
35
|
+
- Unicode on both Python 2 and 3.
|
|
36
|
+
- Fully typed.
|
|
37
|
+
|
|
38
|
+
## Features
|
|
39
|
+
|
|
40
|
+
- Precise Process Spawning: Direct system call wrappers, not wrappers around wrappers.
|
|
41
|
+
- Escape-proof Argument Passing: Arguments are not interpreted or glob-expanded.
|
|
42
|
+
- Full Unicode Support: Give and receive Unicode paths/arguments everywhere.
|
|
43
|
+
- Explicit Environment: Can supply a Unicode environment dictionary. Does not mutate global environment variables.
|
|
44
|
+
- Handles Redirection: Pass open file descriptors for `stdin`, `stdout`, `stderr` just like with the C API.
|
|
45
|
+
|
|
46
|
+
## Installation
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pip install ctypes-unicode-proclaunch
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Usage
|
|
53
|
+
|
|
54
|
+
### Process Launched
|
|
55
|
+
|
|
56
|
+
A Python script `print_argv.py`:
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
# coding=utf-8
|
|
60
|
+
from __future__ import print_function
|
|
61
|
+
import sys
|
|
62
|
+
|
|
63
|
+
if __name__ == '__main__':
|
|
64
|
+
for i, arg in enumerate(sys.argv):
|
|
65
|
+
print('sys.argv[%d]=%s' % (i, arg))
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### NT
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
# coding=utf-8
|
|
72
|
+
from ctypes_unicode_proclaunch import launch, wait
|
|
73
|
+
|
|
74
|
+
# No `cmd.exe`'s handling of special characters:
|
|
75
|
+
process_handle_1 = launch([u'python', u'print_argv.py', u'%USERNAME%'])
|
|
76
|
+
wait(process_handle_1)
|
|
77
|
+
# sys.argv[0]=print_argv.py
|
|
78
|
+
# sys.argv[1]=%USERNAME%
|
|
79
|
+
|
|
80
|
+
process_handle_2 = launch([u'python', u'print_argv.py', u'Hello', u'&', u'python', u'print_argv.py', u'injected'])
|
|
81
|
+
wait(process_handle_2)
|
|
82
|
+
# sys.argv[0]=print_argv.py
|
|
83
|
+
# sys.argv[1]=Hello
|
|
84
|
+
# sys.argv[2]=&
|
|
85
|
+
# sys.argv[3]=python
|
|
86
|
+
# sys.argv[4]=print_argv.py
|
|
87
|
+
# sys.argv[5]=injected
|
|
88
|
+
|
|
89
|
+
process_handle_3 = launch([u'python', u'print_argv.py', u'*.txt'])
|
|
90
|
+
wait(process_handle_3)
|
|
91
|
+
# sys.argv[0]=print_argv.py
|
|
92
|
+
# sys.argv[1]=*.txt
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### POSIX
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
# coding=utf-8
|
|
99
|
+
from ctypes_unicode_proclaunch import launch, wait
|
|
100
|
+
|
|
101
|
+
pid_1 = launch([u'python', u'print_argv.py', u'$HOME'])
|
|
102
|
+
wait(pid_1)
|
|
103
|
+
# sys.argv[0]=print_argv.py
|
|
104
|
+
# sys.argv[1]=$HOME
|
|
105
|
+
|
|
106
|
+
pid_2 = launch([u'python', u'print_argv.py', u'"test"', u'>', u'$file'])
|
|
107
|
+
wait(pid_2)
|
|
108
|
+
# sys.argv[0]=print_argv.py
|
|
109
|
+
# sys.argv[1]="test"
|
|
110
|
+
# sys.argv[2]=>
|
|
111
|
+
# sys.argv[3]=$file
|
|
112
|
+
|
|
113
|
+
pid_3 = launch([u'python', u'print_argv.py', u'*.txt'])
|
|
114
|
+
wait(pid_3)
|
|
115
|
+
# sys.argv[0]=print_argv.py
|
|
116
|
+
# sys.argv[1]=*.txt
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Custom Environment and File Redirection
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
# coding=utf-8
|
|
123
|
+
from ctypes_unicode_proclaunch import launch, wait
|
|
124
|
+
from read_unicode_environment_variables_dictionary import read_unicode_environment_variables_dictionary
|
|
125
|
+
|
|
126
|
+
# Doesn't modify `os.environ`
|
|
127
|
+
unicode_environment_variables_dictionary = read_unicode_environment_variables_dictionary()
|
|
128
|
+
unicode_environment_variables_dictionary[u'COVERAGE_FILE'] = u'test_coverage.sqlite3'
|
|
129
|
+
|
|
130
|
+
# We pass the file descriptors of the opened files for redirection.
|
|
131
|
+
# Just as in the Unix C API.
|
|
132
|
+
# The launched processes DO NOT respect the encodings you specified if you opened the files in text mode.
|
|
133
|
+
# This means that you should always open files in binary mode (`'rb'`, `'wb'`, etc.) to make that explicit.
|
|
134
|
+
with open('test_stdin.txt', 'rb') as f_0, open('test_stdout.txt', 'wb') as f_1, open('test_stderr.txt', 'wb') as f_2:
|
|
135
|
+
pid_or_process_handle = launch(
|
|
136
|
+
[u'python', u'-m', u'coverage', u'run', u'test.py'],
|
|
137
|
+
environment=unicode_environment_variables_dictionary,
|
|
138
|
+
stdin_file_descriptor=f_0.fileno(),
|
|
139
|
+
stdout_file_descriptor=f_1.fileno(),
|
|
140
|
+
stderr_file_descriptor=f_2.fileno()
|
|
141
|
+
)
|
|
142
|
+
wait(pid_or_process_handle)
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Contributing
|
|
146
|
+
|
|
147
|
+
Contributions are welcome! Please submit pull requests or open issues on the GitHub repository.
|
|
148
|
+
|
|
149
|
+
## License
|
|
150
|
+
|
|
151
|
+
This project is licensed under the [MIT License](LICENSE).
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# `ctypes-unicode-proclaunch`
|
|
2
|
+
|
|
3
|
+
A minimal, robust, and Unicode-aware process launching library using ctypes.
|
|
4
|
+
|
|
5
|
+
- No:
|
|
6
|
+
- Shell involvement: Arguments are not interpolated or globbed by any shell, ensuring "what you pass is what you
|
|
7
|
+
get".
|
|
8
|
+
- Python `subprocess` abstractions: No `Popen`/`communicate`/`universal_newlines`/encoding drama.
|
|
9
|
+
- Yes:
|
|
10
|
+
- Native system call usage for maximum control and correctness.
|
|
11
|
+
- `CreateProcessW` (NT)
|
|
12
|
+
- `fork` and `execve` (POSIX)
|
|
13
|
+
- Unicode on both Python 2 and 3.
|
|
14
|
+
- Fully typed.
|
|
15
|
+
|
|
16
|
+
## Features
|
|
17
|
+
|
|
18
|
+
- Precise Process Spawning: Direct system call wrappers, not wrappers around wrappers.
|
|
19
|
+
- Escape-proof Argument Passing: Arguments are not interpreted or glob-expanded.
|
|
20
|
+
- Full Unicode Support: Give and receive Unicode paths/arguments everywhere.
|
|
21
|
+
- Explicit Environment: Can supply a Unicode environment dictionary. Does not mutate global environment variables.
|
|
22
|
+
- Handles Redirection: Pass open file descriptors for `stdin`, `stdout`, `stderr` just like with the C API.
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install ctypes-unicode-proclaunch
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Usage
|
|
31
|
+
|
|
32
|
+
### Process Launched
|
|
33
|
+
|
|
34
|
+
A Python script `print_argv.py`:
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
# coding=utf-8
|
|
38
|
+
from __future__ import print_function
|
|
39
|
+
import sys
|
|
40
|
+
|
|
41
|
+
if __name__ == '__main__':
|
|
42
|
+
for i, arg in enumerate(sys.argv):
|
|
43
|
+
print('sys.argv[%d]=%s' % (i, arg))
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### NT
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
# coding=utf-8
|
|
50
|
+
from ctypes_unicode_proclaunch import launch, wait
|
|
51
|
+
|
|
52
|
+
# No `cmd.exe`'s handling of special characters:
|
|
53
|
+
process_handle_1 = launch([u'python', u'print_argv.py', u'%USERNAME%'])
|
|
54
|
+
wait(process_handle_1)
|
|
55
|
+
# sys.argv[0]=print_argv.py
|
|
56
|
+
# sys.argv[1]=%USERNAME%
|
|
57
|
+
|
|
58
|
+
process_handle_2 = launch([u'python', u'print_argv.py', u'Hello', u'&', u'python', u'print_argv.py', u'injected'])
|
|
59
|
+
wait(process_handle_2)
|
|
60
|
+
# sys.argv[0]=print_argv.py
|
|
61
|
+
# sys.argv[1]=Hello
|
|
62
|
+
# sys.argv[2]=&
|
|
63
|
+
# sys.argv[3]=python
|
|
64
|
+
# sys.argv[4]=print_argv.py
|
|
65
|
+
# sys.argv[5]=injected
|
|
66
|
+
|
|
67
|
+
process_handle_3 = launch([u'python', u'print_argv.py', u'*.txt'])
|
|
68
|
+
wait(process_handle_3)
|
|
69
|
+
# sys.argv[0]=print_argv.py
|
|
70
|
+
# sys.argv[1]=*.txt
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### POSIX
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
# coding=utf-8
|
|
77
|
+
from ctypes_unicode_proclaunch import launch, wait
|
|
78
|
+
|
|
79
|
+
pid_1 = launch([u'python', u'print_argv.py', u'$HOME'])
|
|
80
|
+
wait(pid_1)
|
|
81
|
+
# sys.argv[0]=print_argv.py
|
|
82
|
+
# sys.argv[1]=$HOME
|
|
83
|
+
|
|
84
|
+
pid_2 = launch([u'python', u'print_argv.py', u'"test"', u'>', u'$file'])
|
|
85
|
+
wait(pid_2)
|
|
86
|
+
# sys.argv[0]=print_argv.py
|
|
87
|
+
# sys.argv[1]="test"
|
|
88
|
+
# sys.argv[2]=>
|
|
89
|
+
# sys.argv[3]=$file
|
|
90
|
+
|
|
91
|
+
pid_3 = launch([u'python', u'print_argv.py', u'*.txt'])
|
|
92
|
+
wait(pid_3)
|
|
93
|
+
# sys.argv[0]=print_argv.py
|
|
94
|
+
# sys.argv[1]=*.txt
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Custom Environment and File Redirection
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
# coding=utf-8
|
|
101
|
+
from ctypes_unicode_proclaunch import launch, wait
|
|
102
|
+
from read_unicode_environment_variables_dictionary import read_unicode_environment_variables_dictionary
|
|
103
|
+
|
|
104
|
+
# Doesn't modify `os.environ`
|
|
105
|
+
unicode_environment_variables_dictionary = read_unicode_environment_variables_dictionary()
|
|
106
|
+
unicode_environment_variables_dictionary[u'COVERAGE_FILE'] = u'test_coverage.sqlite3'
|
|
107
|
+
|
|
108
|
+
# We pass the file descriptors of the opened files for redirection.
|
|
109
|
+
# Just as in the Unix C API.
|
|
110
|
+
# The launched processes DO NOT respect the encodings you specified if you opened the files in text mode.
|
|
111
|
+
# This means that you should always open files in binary mode (`'rb'`, `'wb'`, etc.) to make that explicit.
|
|
112
|
+
with open('test_stdin.txt', 'rb') as f_0, open('test_stdout.txt', 'wb') as f_1, open('test_stderr.txt', 'wb') as f_2:
|
|
113
|
+
pid_or_process_handle = launch(
|
|
114
|
+
[u'python', u'-m', u'coverage', u'run', u'test.py'],
|
|
115
|
+
environment=unicode_environment_variables_dictionary,
|
|
116
|
+
stdin_file_descriptor=f_0.fileno(),
|
|
117
|
+
stdout_file_descriptor=f_1.fileno(),
|
|
118
|
+
stderr_file_descriptor=f_2.fileno()
|
|
119
|
+
)
|
|
120
|
+
wait(pid_or_process_handle)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Contributing
|
|
124
|
+
|
|
125
|
+
Contributions are welcome! Please submit pull requests or open issues on the GitHub repository.
|
|
126
|
+
|
|
127
|
+
## License
|
|
128
|
+
|
|
129
|
+
This project is licensed under the [MIT License](LICENSE).
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ctypes-unicode-proclaunch
|
|
3
|
+
Version: 0.1.0a0
|
|
4
|
+
Summary: A minimal, robust, and Unicode-aware process launching library using ctypes.
|
|
5
|
+
Author-email: Jifeng Wu <jifengwu2k@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/jifengwu2k/ctypes-unicode-proclaunch
|
|
8
|
+
Project-URL: Bug Tracker, https://github.com/jifengwu2k/ctypes-unicode-proclaunch/issues
|
|
9
|
+
Classifier: Programming Language :: Python :: 2
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Requires-Python: >=2
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Requires-Dist: escape-nt-command-line-argument
|
|
16
|
+
Requires-Dist: find-unicode-executable
|
|
17
|
+
Requires-Dist: posix-or-nt
|
|
18
|
+
Requires-Dist: read-unicode-environment-variables-dictionary
|
|
19
|
+
Requires-Dist: send-recv-json
|
|
20
|
+
Requires-Dist: typing; python_version < "3.5"
|
|
21
|
+
Dynamic: license-file
|
|
22
|
+
|
|
23
|
+
# `ctypes-unicode-proclaunch`
|
|
24
|
+
|
|
25
|
+
A minimal, robust, and Unicode-aware process launching library using ctypes.
|
|
26
|
+
|
|
27
|
+
- No:
|
|
28
|
+
- Shell involvement: Arguments are not interpolated or globbed by any shell, ensuring "what you pass is what you
|
|
29
|
+
get".
|
|
30
|
+
- Python `subprocess` abstractions: No `Popen`/`communicate`/`universal_newlines`/encoding drama.
|
|
31
|
+
- Yes:
|
|
32
|
+
- Native system call usage for maximum control and correctness.
|
|
33
|
+
- `CreateProcessW` (NT)
|
|
34
|
+
- `fork` and `execve` (POSIX)
|
|
35
|
+
- Unicode on both Python 2 and 3.
|
|
36
|
+
- Fully typed.
|
|
37
|
+
|
|
38
|
+
## Features
|
|
39
|
+
|
|
40
|
+
- Precise Process Spawning: Direct system call wrappers, not wrappers around wrappers.
|
|
41
|
+
- Escape-proof Argument Passing: Arguments are not interpreted or glob-expanded.
|
|
42
|
+
- Full Unicode Support: Give and receive Unicode paths/arguments everywhere.
|
|
43
|
+
- Explicit Environment: Can supply a Unicode environment dictionary. Does not mutate global environment variables.
|
|
44
|
+
- Handles Redirection: Pass open file descriptors for `stdin`, `stdout`, `stderr` just like with the C API.
|
|
45
|
+
|
|
46
|
+
## Installation
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pip install ctypes-unicode-proclaunch
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Usage
|
|
53
|
+
|
|
54
|
+
### Process Launched
|
|
55
|
+
|
|
56
|
+
A Python script `print_argv.py`:
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
# coding=utf-8
|
|
60
|
+
from __future__ import print_function
|
|
61
|
+
import sys
|
|
62
|
+
|
|
63
|
+
if __name__ == '__main__':
|
|
64
|
+
for i, arg in enumerate(sys.argv):
|
|
65
|
+
print('sys.argv[%d]=%s' % (i, arg))
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### NT
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
# coding=utf-8
|
|
72
|
+
from ctypes_unicode_proclaunch import launch, wait
|
|
73
|
+
|
|
74
|
+
# No `cmd.exe`'s handling of special characters:
|
|
75
|
+
process_handle_1 = launch([u'python', u'print_argv.py', u'%USERNAME%'])
|
|
76
|
+
wait(process_handle_1)
|
|
77
|
+
# sys.argv[0]=print_argv.py
|
|
78
|
+
# sys.argv[1]=%USERNAME%
|
|
79
|
+
|
|
80
|
+
process_handle_2 = launch([u'python', u'print_argv.py', u'Hello', u'&', u'python', u'print_argv.py', u'injected'])
|
|
81
|
+
wait(process_handle_2)
|
|
82
|
+
# sys.argv[0]=print_argv.py
|
|
83
|
+
# sys.argv[1]=Hello
|
|
84
|
+
# sys.argv[2]=&
|
|
85
|
+
# sys.argv[3]=python
|
|
86
|
+
# sys.argv[4]=print_argv.py
|
|
87
|
+
# sys.argv[5]=injected
|
|
88
|
+
|
|
89
|
+
process_handle_3 = launch([u'python', u'print_argv.py', u'*.txt'])
|
|
90
|
+
wait(process_handle_3)
|
|
91
|
+
# sys.argv[0]=print_argv.py
|
|
92
|
+
# sys.argv[1]=*.txt
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### POSIX
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
# coding=utf-8
|
|
99
|
+
from ctypes_unicode_proclaunch import launch, wait
|
|
100
|
+
|
|
101
|
+
pid_1 = launch([u'python', u'print_argv.py', u'$HOME'])
|
|
102
|
+
wait(pid_1)
|
|
103
|
+
# sys.argv[0]=print_argv.py
|
|
104
|
+
# sys.argv[1]=$HOME
|
|
105
|
+
|
|
106
|
+
pid_2 = launch([u'python', u'print_argv.py', u'"test"', u'>', u'$file'])
|
|
107
|
+
wait(pid_2)
|
|
108
|
+
# sys.argv[0]=print_argv.py
|
|
109
|
+
# sys.argv[1]="test"
|
|
110
|
+
# sys.argv[2]=>
|
|
111
|
+
# sys.argv[3]=$file
|
|
112
|
+
|
|
113
|
+
pid_3 = launch([u'python', u'print_argv.py', u'*.txt'])
|
|
114
|
+
wait(pid_3)
|
|
115
|
+
# sys.argv[0]=print_argv.py
|
|
116
|
+
# sys.argv[1]=*.txt
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Custom Environment and File Redirection
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
# coding=utf-8
|
|
123
|
+
from ctypes_unicode_proclaunch import launch, wait
|
|
124
|
+
from read_unicode_environment_variables_dictionary import read_unicode_environment_variables_dictionary
|
|
125
|
+
|
|
126
|
+
# Doesn't modify `os.environ`
|
|
127
|
+
unicode_environment_variables_dictionary = read_unicode_environment_variables_dictionary()
|
|
128
|
+
unicode_environment_variables_dictionary[u'COVERAGE_FILE'] = u'test_coverage.sqlite3'
|
|
129
|
+
|
|
130
|
+
# We pass the file descriptors of the opened files for redirection.
|
|
131
|
+
# Just as in the Unix C API.
|
|
132
|
+
# The launched processes DO NOT respect the encodings you specified if you opened the files in text mode.
|
|
133
|
+
# This means that you should always open files in binary mode (`'rb'`, `'wb'`, etc.) to make that explicit.
|
|
134
|
+
with open('test_stdin.txt', 'rb') as f_0, open('test_stdout.txt', 'wb') as f_1, open('test_stderr.txt', 'wb') as f_2:
|
|
135
|
+
pid_or_process_handle = launch(
|
|
136
|
+
[u'python', u'-m', u'coverage', u'run', u'test.py'],
|
|
137
|
+
environment=unicode_environment_variables_dictionary,
|
|
138
|
+
stdin_file_descriptor=f_0.fileno(),
|
|
139
|
+
stdout_file_descriptor=f_1.fileno(),
|
|
140
|
+
stderr_file_descriptor=f_2.fileno()
|
|
141
|
+
)
|
|
142
|
+
wait(pid_or_process_handle)
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Contributing
|
|
146
|
+
|
|
147
|
+
Contributions are welcome! Please submit pull requests or open issues on the GitHub repository.
|
|
148
|
+
|
|
149
|
+
## License
|
|
150
|
+
|
|
151
|
+
This project is licensed under the [MIT License](LICENSE).
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
ctypes_unicode_proclaunch.py
|
|
4
|
+
pyproject.toml
|
|
5
|
+
setup.cfg
|
|
6
|
+
ctypes_unicode_proclaunch.egg-info/PKG-INFO
|
|
7
|
+
ctypes_unicode_proclaunch.egg-info/SOURCES.txt
|
|
8
|
+
ctypes_unicode_proclaunch.egg-info/dependency_links.txt
|
|
9
|
+
ctypes_unicode_proclaunch.egg-info/requires.txt
|
|
10
|
+
ctypes_unicode_proclaunch.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ctypes_unicode_proclaunch
|
|
@@ -0,0 +1,563 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
# Copyright (c) 2025 Jifeng Wu
|
|
3
|
+
# Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
|
4
|
+
import ctypes
|
|
5
|
+
from itertools import chain
|
|
6
|
+
|
|
7
|
+
from escape_nt_command_line_argument import escape_nt_command_line_argument
|
|
8
|
+
from find_unicode_executable import find_unicode_executable
|
|
9
|
+
from posix_or_nt import posix_or_nt
|
|
10
|
+
from read_unicode_environment_variables_dictionary import read_unicode_environment_variables_dictionary
|
|
11
|
+
from send_recv_json import recv_json, send_json
|
|
12
|
+
from typing import Any, Iterable, Mapping, Optional, Sequence, Text, Tuple
|
|
13
|
+
|
|
14
|
+
# STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO
|
|
15
|
+
STDIN_FILENO = 0
|
|
16
|
+
STDOUT_FILENO = 1
|
|
17
|
+
STDERR_FILENO = 2
|
|
18
|
+
|
|
19
|
+
if posix_or_nt() == 'nt':
|
|
20
|
+
import ctypes.wintypes
|
|
21
|
+
import msvcrt
|
|
22
|
+
|
|
23
|
+
# Load `user32`, `kernel32`
|
|
24
|
+
user32 = ctypes.WinDLL('user32', use_last_error=True)
|
|
25
|
+
kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# `STARTUPINFOW` Definition
|
|
29
|
+
class STARTUPINFOW(ctypes.Structure):
|
|
30
|
+
_fields_ = [
|
|
31
|
+
('cb', ctypes.wintypes.DWORD),
|
|
32
|
+
('lpReserved', ctypes.wintypes.LPWSTR),
|
|
33
|
+
('lpDesktop', ctypes.wintypes.LPWSTR),
|
|
34
|
+
('lpTitle', ctypes.wintypes.LPWSTR),
|
|
35
|
+
('dwX', ctypes.wintypes.DWORD),
|
|
36
|
+
('dwY', ctypes.wintypes.DWORD),
|
|
37
|
+
('dwXSize', ctypes.wintypes.DWORD),
|
|
38
|
+
('dwYSize', ctypes.wintypes.DWORD),
|
|
39
|
+
('dwXCountChars', ctypes.wintypes.DWORD),
|
|
40
|
+
('dwYCountChars', ctypes.wintypes.DWORD),
|
|
41
|
+
('dwFillAttribute', ctypes.wintypes.DWORD),
|
|
42
|
+
('dwFlags', ctypes.wintypes.DWORD),
|
|
43
|
+
('wShowWindow', ctypes.wintypes.WORD),
|
|
44
|
+
('cbReserved2', ctypes.wintypes.WORD),
|
|
45
|
+
('lpReserved2', ctypes.POINTER(ctypes.wintypes.BYTE)),
|
|
46
|
+
('hStdInput', ctypes.wintypes.HANDLE),
|
|
47
|
+
('hStdOutput', ctypes.wintypes.HANDLE),
|
|
48
|
+
('hStdError', ctypes.wintypes.HANDLE),
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
STARTF_USESTDHANDLES = 0x100
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# `PROCESS_INFORMATION` Definition
|
|
56
|
+
class PROCESS_INFORMATION(ctypes.Structure):
|
|
57
|
+
_fields_ = [
|
|
58
|
+
('hProcess', ctypes.wintypes.HANDLE),
|
|
59
|
+
('hThread', ctypes.wintypes.HANDLE),
|
|
60
|
+
('dwProcessId', ctypes.wintypes.DWORD),
|
|
61
|
+
('dwThreadId', ctypes.wintypes.DWORD),
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
# `GetCurrentProcess` Definition
|
|
66
|
+
GetCurrentProcess = kernel32.GetCurrentProcess
|
|
67
|
+
GetCurrentProcess.restype = ctypes.wintypes.HANDLE
|
|
68
|
+
|
|
69
|
+
# `DuplicateHandle` Definition
|
|
70
|
+
DuplicateHandle = kernel32.DuplicateHandle
|
|
71
|
+
DuplicateHandle.argtypes = [
|
|
72
|
+
ctypes.wintypes.HANDLE, # hSourceProcessHandle
|
|
73
|
+
ctypes.wintypes.HANDLE, # hSourceHandle
|
|
74
|
+
ctypes.wintypes.HANDLE, # hTargetProcessHandle
|
|
75
|
+
ctypes.POINTER(ctypes.wintypes.HANDLE), # lpTargetHandle
|
|
76
|
+
ctypes.wintypes.DWORD, # dwDesiredAccess
|
|
77
|
+
ctypes.wintypes.BOOL, # bInheritHandle
|
|
78
|
+
ctypes.wintypes.DWORD # dwOptions
|
|
79
|
+
]
|
|
80
|
+
DuplicateHandle.restype = ctypes.wintypes.BOOL
|
|
81
|
+
|
|
82
|
+
DUPLICATE_SAME_ACCESS = 0x00000002
|
|
83
|
+
|
|
84
|
+
# `GetEnvironmentStringsW` Definition
|
|
85
|
+
# Declared to return a `void *`, which will be converted into an `int`
|
|
86
|
+
GetEnvironmentStringsW = kernel32.GetEnvironmentStringsW
|
|
87
|
+
GetEnvironmentStringsW.restype = ctypes.c_void_p
|
|
88
|
+
|
|
89
|
+
# `FreeEnvironmentStringsW` Definition
|
|
90
|
+
# Declared to return a `void *`, which will be converted into an `int`
|
|
91
|
+
FreeEnvironmentStringsW = kernel32.FreeEnvironmentStringsW
|
|
92
|
+
FreeEnvironmentStringsW.argtypes = [ctypes.c_void_p]
|
|
93
|
+
FreeEnvironmentStringsW.restype = ctypes.wintypes.BOOL
|
|
94
|
+
|
|
95
|
+
# `CreateProcessW` Definition
|
|
96
|
+
CreateProcessW = kernel32.CreateProcessW
|
|
97
|
+
CreateProcessW.argtypes = [
|
|
98
|
+
ctypes.wintypes.LPCWSTR, # lpApplicationName
|
|
99
|
+
ctypes.wintypes.LPWSTR, # lpCommandLine
|
|
100
|
+
ctypes.wintypes.LPVOID, # lpProcessAttributes
|
|
101
|
+
ctypes.wintypes.LPVOID, # lpThreadAttributes
|
|
102
|
+
ctypes.wintypes.BOOL, # bInheritHandles
|
|
103
|
+
ctypes.wintypes.DWORD, # dwCreationFlags
|
|
104
|
+
ctypes.wintypes.LPVOID, # lpEnvironment
|
|
105
|
+
ctypes.wintypes.LPCWSTR, # lpCurrentDirectory
|
|
106
|
+
ctypes.POINTER(STARTUPINFOW), # lpStartupInfo
|
|
107
|
+
ctypes.POINTER(PROCESS_INFORMATION) # lpProcessInformation
|
|
108
|
+
]
|
|
109
|
+
CreateProcessW.restype = ctypes.wintypes.BOOL
|
|
110
|
+
|
|
111
|
+
# Must set the `CREATE_UNICODE_ENVIRONMENT` flag in `dwCreationFlags`
|
|
112
|
+
CREATE_UNICODE_ENVIRONMENT = 0x00000400
|
|
113
|
+
|
|
114
|
+
# `WaitForSingleObject` Definition
|
|
115
|
+
WaitForSingleObject = kernel32.WaitForSingleObject
|
|
116
|
+
WaitForSingleObject.argtypes = [ctypes.wintypes.HANDLE, ctypes.wintypes.DWORD]
|
|
117
|
+
WaitForSingleObject.restype = ctypes.wintypes.DWORD
|
|
118
|
+
|
|
119
|
+
INFINITE = -1 # Wait indefinitely
|
|
120
|
+
WAIT_OBJECT_0 = 0 # Wait successful
|
|
121
|
+
|
|
122
|
+
# `GetExitCodeProcess` Definition
|
|
123
|
+
GetExitCodeProcess = kernel32.GetExitCodeProcess
|
|
124
|
+
GetExitCodeProcess.argtypes = [
|
|
125
|
+
ctypes.wintypes.HANDLE, # hProcess
|
|
126
|
+
ctypes.POINTER(ctypes.wintypes.DWORD) # lpExitCode
|
|
127
|
+
]
|
|
128
|
+
GetExitCodeProcess.restype = ctypes.wintypes.BOOL
|
|
129
|
+
|
|
130
|
+
STILL_ACTIVE = 259
|
|
131
|
+
|
|
132
|
+
# `CloseHandle` Definition
|
|
133
|
+
CloseHandle = kernel32.CloseHandle
|
|
134
|
+
CloseHandle.argtypes = [ctypes.wintypes.HANDLE]
|
|
135
|
+
CloseHandle.restype = ctypes.wintypes.BOOL
|
|
136
|
+
|
|
137
|
+
# `GenerateConsoleCtrlEvent` Definition
|
|
138
|
+
GenerateConsoleCtrlEvent = kernel32.GenerateConsoleCtrlEvent
|
|
139
|
+
GenerateConsoleCtrlEvent.argtypes = [
|
|
140
|
+
ctypes.wintypes.DWORD, # dwCtrlEvent
|
|
141
|
+
ctypes.wintypes.DWORD # dwProcessGroupId
|
|
142
|
+
]
|
|
143
|
+
GenerateConsoleCtrlEvent.restype = ctypes.wintypes.BOOL
|
|
144
|
+
|
|
145
|
+
# Create the callback function type
|
|
146
|
+
WNDENUMPROC = ctypes.WINFUNCTYPE(ctypes.wintypes.BOOL, ctypes.wintypes.HWND, ctypes.wintypes.LPARAM)
|
|
147
|
+
|
|
148
|
+
# `EnumWindows` Definition
|
|
149
|
+
EnumWindows = user32.EnumWindows
|
|
150
|
+
EnumWindows.argtypes = [WNDENUMPROC, ctypes.wintypes.LPARAM]
|
|
151
|
+
EnumWindows.restype = ctypes.wintypes.BOOL
|
|
152
|
+
|
|
153
|
+
# `GetWindowThreadProcessId` Definition
|
|
154
|
+
GetWindowThreadProcessId = user32.GetWindowThreadProcessId
|
|
155
|
+
GetWindowThreadProcessId.argtypes = [ctypes.wintypes.HWND, ctypes.POINTER(ctypes.wintypes.DWORD)]
|
|
156
|
+
GetWindowThreadProcessId.restype = ctypes.wintypes.DWORD
|
|
157
|
+
|
|
158
|
+
# `GetParent` Definition
|
|
159
|
+
GetParent = user32.GetParent
|
|
160
|
+
GetParent.argtypes = [ctypes.wintypes.HWND]
|
|
161
|
+
GetParent.restype = ctypes.wintypes.HWND
|
|
162
|
+
|
|
163
|
+
# `PostMessageW` Definition
|
|
164
|
+
PostMessageW = user32.PostMessageW
|
|
165
|
+
PostMessageW.argtypes = (
|
|
166
|
+
ctypes.wintypes.HWND, # hWnd
|
|
167
|
+
ctypes.wintypes.UINT, # Msg
|
|
168
|
+
ctypes.wintypes.WPARAM, # wParam
|
|
169
|
+
ctypes.wintypes.LPARAM # lParam
|
|
170
|
+
)
|
|
171
|
+
PostMessageW.restype = ctypes.wintypes.BOOL
|
|
172
|
+
|
|
173
|
+
# `TerminateProcess` Definition
|
|
174
|
+
TerminateProcess = kernel32.TerminateProcess
|
|
175
|
+
TerminateProcess.argtypes = [ctypes.wintypes.HANDLE, ctypes.wintypes.UINT]
|
|
176
|
+
TerminateProcess.restype = ctypes.wintypes.BOOL
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def join_arguments_to_nt_command_line(arguments):
|
|
180
|
+
# type: (Iterable[Text]) -> Text
|
|
181
|
+
return u' '.join(map(escape_nt_command_line_argument, arguments))
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def make_environment_block(environment):
|
|
185
|
+
# type: (Mapping[Text, Text]) -> Text
|
|
186
|
+
return u'\0'.join(
|
|
187
|
+
chain(
|
|
188
|
+
(u'%s=%s' % (name, value) for name, value in environment.items()),
|
|
189
|
+
(u'\0',)
|
|
190
|
+
)
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def duplicate_file_handle_of_file_descriptor(file_descriptor):
|
|
195
|
+
# type: (int) -> ctypes.wintypes.HANDLE
|
|
196
|
+
file_handle = msvcrt.get_osfhandle(file_descriptor)
|
|
197
|
+
inheritable = ctypes.wintypes.HANDLE()
|
|
198
|
+
|
|
199
|
+
if not DuplicateHandle(
|
|
200
|
+
GetCurrentProcess(),
|
|
201
|
+
file_handle,
|
|
202
|
+
GetCurrentProcess(),
|
|
203
|
+
ctypes.byref(inheritable),
|
|
204
|
+
0,
|
|
205
|
+
True,
|
|
206
|
+
DUPLICATE_SAME_ACCESS
|
|
207
|
+
):
|
|
208
|
+
raise ctypes.WinError(ctypes.get_last_error())
|
|
209
|
+
|
|
210
|
+
return inheritable
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def launch(
|
|
214
|
+
arguments,
|
|
215
|
+
environment=None,
|
|
216
|
+
stdin_file_descriptor=None,
|
|
217
|
+
stdout_file_descriptor=None,
|
|
218
|
+
stderr_file_descriptor=None
|
|
219
|
+
):
|
|
220
|
+
# type: (Sequence[Text], Optional[Mapping[Text, Text]], Optional[int], Optional[int], Optional[int]) -> ctypes.wintypes.HANDLE
|
|
221
|
+
if len(arguments) == 0:
|
|
222
|
+
raise ValueError('Empty command line')
|
|
223
|
+
|
|
224
|
+
command_line = join_arguments_to_nt_command_line(arguments)
|
|
225
|
+
|
|
226
|
+
use_redirection = stdin_file_descriptor is not None or stdout_file_descriptor is not None or stderr_file_descriptor is not None
|
|
227
|
+
|
|
228
|
+
lp_application_name = None
|
|
229
|
+
lp_command_line = ctypes.create_unicode_buffer(command_line)
|
|
230
|
+
|
|
231
|
+
if environment is not None:
|
|
232
|
+
lp_environment = ctypes.create_unicode_buffer(make_environment_block(environment))
|
|
233
|
+
else:
|
|
234
|
+
lp_environment = None
|
|
235
|
+
|
|
236
|
+
dw_creation_flags = CREATE_UNICODE_ENVIRONMENT
|
|
237
|
+
|
|
238
|
+
startup_info_w = STARTUPINFOW()
|
|
239
|
+
startup_info_w.cb = ctypes.sizeof(STARTUPINFOW)
|
|
240
|
+
|
|
241
|
+
if use_redirection:
|
|
242
|
+
h_std_input = duplicate_file_handle_of_file_descriptor(
|
|
243
|
+
stdin_file_descriptor if stdin_file_descriptor is not None else STDIN_FILENO)
|
|
244
|
+
h_std_output = duplicate_file_handle_of_file_descriptor(
|
|
245
|
+
stdout_file_descriptor if stdout_file_descriptor is not None else STDOUT_FILENO)
|
|
246
|
+
h_std_error = duplicate_file_handle_of_file_descriptor(
|
|
247
|
+
stderr_file_descriptor if stderr_file_descriptor is not None else STDERR_FILENO)
|
|
248
|
+
|
|
249
|
+
startup_info_w.dwFlags |= STARTF_USESTDHANDLES
|
|
250
|
+
startup_info_w.hStdInput = h_std_input
|
|
251
|
+
startup_info_w.hStdOutput = h_std_output
|
|
252
|
+
startup_info_w.hStdError = h_std_error
|
|
253
|
+
|
|
254
|
+
handles_of_file_descriptors_to_close = [h_std_input, h_std_output, h_std_error]
|
|
255
|
+
else:
|
|
256
|
+
handles_of_file_descriptors_to_close = []
|
|
257
|
+
|
|
258
|
+
process_information = PROCESS_INFORMATION()
|
|
259
|
+
|
|
260
|
+
if not CreateProcessW(
|
|
261
|
+
lp_application_name,
|
|
262
|
+
lp_command_line,
|
|
263
|
+
None,
|
|
264
|
+
None,
|
|
265
|
+
True,
|
|
266
|
+
dw_creation_flags,
|
|
267
|
+
lp_environment,
|
|
268
|
+
None,
|
|
269
|
+
ctypes.byref(startup_info_w),
|
|
270
|
+
ctypes.byref(process_information)
|
|
271
|
+
):
|
|
272
|
+
# Clean up handles on failure
|
|
273
|
+
for handle in handles_of_file_descriptors_to_close:
|
|
274
|
+
CloseHandle(handle)
|
|
275
|
+
|
|
276
|
+
raise ctypes.WinError(ctypes.get_last_error())
|
|
277
|
+
|
|
278
|
+
for handle in handles_of_file_descriptors_to_close:
|
|
279
|
+
CloseHandle(handle)
|
|
280
|
+
CloseHandle(process_information.hThread)
|
|
281
|
+
|
|
282
|
+
return process_information.hProcess
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def wait(process_handle):
|
|
286
|
+
# type: (ctypes.wintypes.HANDLE) -> int
|
|
287
|
+
if WaitForSingleObject(process_handle, INFINITE) != WAIT_OBJECT_0:
|
|
288
|
+
raise ctypes.WinError(ctypes.get_last_error())
|
|
289
|
+
|
|
290
|
+
exit_code = ctypes.wintypes.DWORD()
|
|
291
|
+
if not GetExitCodeProcess(process_handle, ctypes.byref(exit_code)):
|
|
292
|
+
raise ctypes.WinError(ctypes.get_last_error())
|
|
293
|
+
|
|
294
|
+
CloseHandle(process_handle)
|
|
295
|
+
|
|
296
|
+
return exit_code.value
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def kill(process_handle):
|
|
300
|
+
# type: (ctypes.wintypes.HANDLE) -> None
|
|
301
|
+
exit_code = ctypes.wintypes.DWORD()
|
|
302
|
+
if not GetExitCodeProcess(process_handle, ctypes.byref(exit_code)):
|
|
303
|
+
raise ctypes.WinError(ctypes.get_last_error())
|
|
304
|
+
|
|
305
|
+
if exit_code.value == STILL_ACTIVE:
|
|
306
|
+
if not TerminateProcess(process_handle, 1):
|
|
307
|
+
raise ctypes.WinError(ctypes.get_last_error())
|
|
308
|
+
|
|
309
|
+
wait(process_handle)
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
else:
|
|
313
|
+
libc = ctypes.CDLL(None, use_errno=True)
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
# WIFEXITED, WEXITSTATUS, WIFSIGNALED, WTERMSIG
|
|
317
|
+
def WIFEXITED(status):
|
|
318
|
+
# type: (int) -> bool
|
|
319
|
+
return (status & 0x7F) == 0
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def WEXITSTATUS(status):
|
|
323
|
+
# type: (int) -> int
|
|
324
|
+
return (status >> 8) & 0xFF
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def WIFSIGNALED(status):
|
|
328
|
+
# type: (int) -> bool
|
|
329
|
+
return ((status & 0x7F) != 0) and ((status & 0x7F) != 0x7F)
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def WTERMSIG(status):
|
|
333
|
+
# type: (int) -> int
|
|
334
|
+
return status & 0x7F
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
# pid_t
|
|
338
|
+
pid_t = ctypes.c_int
|
|
339
|
+
|
|
340
|
+
# close
|
|
341
|
+
close = libc.close
|
|
342
|
+
close.argtypes = [ctypes.c_int]
|
|
343
|
+
close.restype = ctypes.c_int
|
|
344
|
+
|
|
345
|
+
# dup2
|
|
346
|
+
dup2 = libc.dup2
|
|
347
|
+
dup2.argtypes = [ctypes.c_int, ctypes.c_int]
|
|
348
|
+
dup2.restype = ctypes.c_int
|
|
349
|
+
|
|
350
|
+
# _exit
|
|
351
|
+
_exit = libc._exit
|
|
352
|
+
_exit.argtypes = [ctypes.c_int]
|
|
353
|
+
|
|
354
|
+
# fork
|
|
355
|
+
fork = libc.fork
|
|
356
|
+
fork.restype = pid_t
|
|
357
|
+
|
|
358
|
+
# execve
|
|
359
|
+
execve = libc.execve
|
|
360
|
+
execve.argtypes = [ctypes.c_char_p, ctypes.POINTER(ctypes.c_char_p), ctypes.POINTER(ctypes.c_char_p)]
|
|
361
|
+
execve.restype = ctypes.c_int
|
|
362
|
+
|
|
363
|
+
# kill
|
|
364
|
+
kill_ = libc.kill
|
|
365
|
+
kill_.argtypes = [pid_t, ctypes.c_int]
|
|
366
|
+
kill_.restype = ctypes.c_int
|
|
367
|
+
|
|
368
|
+
# pipe
|
|
369
|
+
pipe = libc.pipe
|
|
370
|
+
pipe.argtypes = [ctypes.POINTER(ctypes.c_int)]
|
|
371
|
+
pipe.restype = ctypes.c_int
|
|
372
|
+
|
|
373
|
+
# read
|
|
374
|
+
read = libc.read
|
|
375
|
+
read.argtypes = [ctypes.c_int, ctypes.c_void_p, ctypes.c_size_t]
|
|
376
|
+
read.restype = ctypes.c_size_t
|
|
377
|
+
|
|
378
|
+
# strerror
|
|
379
|
+
strerror = libc.strerror
|
|
380
|
+
strerror.argtypes = [ctypes.c_int]
|
|
381
|
+
strerror.restype = ctypes.c_char_p
|
|
382
|
+
|
|
383
|
+
# waitpid
|
|
384
|
+
waitpid = libc.waitpid
|
|
385
|
+
waitpid.argtypes = [
|
|
386
|
+
pid_t,
|
|
387
|
+
ctypes.POINTER(ctypes.c_int),
|
|
388
|
+
ctypes.c_int
|
|
389
|
+
]
|
|
390
|
+
waitpid.restype = pid_t
|
|
391
|
+
|
|
392
|
+
# write
|
|
393
|
+
write = libc.write
|
|
394
|
+
write.argtypes = [ctypes.c_int, ctypes.c_void_p, ctypes.c_size_t]
|
|
395
|
+
write.restype = ctypes.c_size_t
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
def create_pipe():
|
|
399
|
+
# type: () -> Tuple[int, int]
|
|
400
|
+
fds = (ctypes.c_int * 2)() # Array of 2 integers
|
|
401
|
+
|
|
402
|
+
# Create the pipe
|
|
403
|
+
result = pipe(fds)
|
|
404
|
+
if result < 0:
|
|
405
|
+
error_number = ctypes.get_errno()
|
|
406
|
+
raise OSError(error_number, 'pipe failed: %s' % strerror(error_number).decode('utf-8'))
|
|
407
|
+
|
|
408
|
+
return fds[0], fds[1]
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
def utf_8_c_char_p_array_from_unicode_strings(unicode_strings):
|
|
412
|
+
encoded_unicode_strings = []
|
|
413
|
+
encoded_unicode_strings.extend(unicode_string.encode('utf-8') for unicode_string in unicode_strings)
|
|
414
|
+
encoded_unicode_strings.append(None)
|
|
415
|
+
|
|
416
|
+
return (ctypes.c_char_p * len(encoded_unicode_strings))(*encoded_unicode_strings)
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
def serialize_to_os_error_json(errno, strerror):
|
|
420
|
+
# type: (int, str) -> Any
|
|
421
|
+
return {"errno": errno, "strerror": strerror}
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
def deserialize_from_os_error_json(os_error_json):
|
|
425
|
+
# type: (Any) -> OSError
|
|
426
|
+
return OSError(os_error_json['errno'], os_error_json['strerror'])
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
def send_to_pipe(write_fd, data):
|
|
430
|
+
# type: (int, bytes) -> int
|
|
431
|
+
return write(write_fd, data, len(data))
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
def recv_from_pipe(read_fd, size):
|
|
435
|
+
# type: (int, int) -> bytes
|
|
436
|
+
string_buffer = ctypes.create_string_buffer(size)
|
|
437
|
+
bytes_read = read(read_fd, string_buffer, size)
|
|
438
|
+
return string_buffer.raw[:bytes_read]
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
def launch(
|
|
442
|
+
arguments,
|
|
443
|
+
environment=None,
|
|
444
|
+
stdin_file_descriptor=None,
|
|
445
|
+
stdout_file_descriptor=None,
|
|
446
|
+
stderr_file_descriptor=None
|
|
447
|
+
):
|
|
448
|
+
# type: (Sequence[Text], Optional[Mapping[Text, Text]], Optional[int], Optional[int], Optional[int]) -> int
|
|
449
|
+
if len(arguments) == 0:
|
|
450
|
+
raise ValueError('Empty command line')
|
|
451
|
+
|
|
452
|
+
executable = arguments[0]
|
|
453
|
+
path = next(find_unicode_executable(executable), None)
|
|
454
|
+
if path is None:
|
|
455
|
+
raise ValueError('Cannot find executable %s' % executable)
|
|
456
|
+
|
|
457
|
+
remaining_arguments = arguments[1:]
|
|
458
|
+
|
|
459
|
+
if environment is None:
|
|
460
|
+
environment = read_unicode_environment_variables_dictionary()
|
|
461
|
+
|
|
462
|
+
utf_8_c_char_p_path = ctypes.c_char_p(path.encode('utf-8'))
|
|
463
|
+
utf_8_c_char_p_array_argv = utf_8_c_char_p_array_from_unicode_strings(
|
|
464
|
+
chain([path], remaining_arguments)
|
|
465
|
+
)
|
|
466
|
+
utf_8_c_char_p_array_envp = utf_8_c_char_p_array_from_unicode_strings(
|
|
467
|
+
u'%s=%s' % (name, value)
|
|
468
|
+
for name, value in environment.items()
|
|
469
|
+
)
|
|
470
|
+
|
|
471
|
+
read_fd, write_fd = create_pipe()
|
|
472
|
+
|
|
473
|
+
pid = fork()
|
|
474
|
+
|
|
475
|
+
if pid < 0:
|
|
476
|
+
# fork failed, parent process
|
|
477
|
+
error_number = ctypes.get_errno()
|
|
478
|
+
raise OSError(error_number, 'fork failed: %s' % strerror(error_number).decode('utf-8'))
|
|
479
|
+
|
|
480
|
+
if pid == 0:
|
|
481
|
+
# fork succeeded, child process
|
|
482
|
+
|
|
483
|
+
# Close read_fd
|
|
484
|
+
close(read_fd)
|
|
485
|
+
|
|
486
|
+
# Handle redirection
|
|
487
|
+
for redirected_fd, original_fd in zip(
|
|
488
|
+
[stdin_file_descriptor, stdout_file_descriptor, stderr_file_descriptor],
|
|
489
|
+
[STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO]):
|
|
490
|
+
if redirected_fd is not None and redirected_fd != original_fd:
|
|
491
|
+
if dup2(redirected_fd, original_fd) < 0:
|
|
492
|
+
error_number = ctypes.get_errno()
|
|
493
|
+
error_string = 'dup2 failed: %s' % strerror(error_number).decode('utf-8')
|
|
494
|
+
send_json(
|
|
495
|
+
lambda data: send_to_pipe(write_fd, data),
|
|
496
|
+
serialize_to_os_error_json(error_number, error_string)
|
|
497
|
+
)
|
|
498
|
+
|
|
499
|
+
_exit(1)
|
|
500
|
+
elif redirected_fd not in [STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO]:
|
|
501
|
+
close(redirected_fd)
|
|
502
|
+
|
|
503
|
+
# Replace with new process
|
|
504
|
+
execve(utf_8_c_char_p_path, utf_8_c_char_p_array_argv, utf_8_c_char_p_array_envp)
|
|
505
|
+
|
|
506
|
+
# execvp fails
|
|
507
|
+
error_number = ctypes.get_errno()
|
|
508
|
+
error_string = 'execve failed: %s' % strerror(error_number).decode('utf-8')
|
|
509
|
+
send_json(
|
|
510
|
+
lambda data: send_to_pipe(write_fd, data),
|
|
511
|
+
serialize_to_os_error_json(error_number, error_string)
|
|
512
|
+
)
|
|
513
|
+
|
|
514
|
+
_exit(1)
|
|
515
|
+
|
|
516
|
+
# fork succeeded, parent process
|
|
517
|
+
|
|
518
|
+
# Close write_fd
|
|
519
|
+
close(write_fd)
|
|
520
|
+
|
|
521
|
+
# Attempt to recv an OSError JSON
|
|
522
|
+
try:
|
|
523
|
+
os_error_json = recv_json(lambda size: recv_from_pipe(read_fd, size))
|
|
524
|
+
raise deserialize_from_os_error_json(os_error_json)
|
|
525
|
+
except EOFError:
|
|
526
|
+
pass
|
|
527
|
+
|
|
528
|
+
return pid
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
def wait(pid):
|
|
532
|
+
# type: (int) -> int
|
|
533
|
+
# Call waitpid
|
|
534
|
+
status = ctypes.c_int()
|
|
535
|
+
if waitpid(pid, ctypes.byref(status), 0) < 0:
|
|
536
|
+
# Return an error and leave the state unchanged
|
|
537
|
+
error_number = ctypes.get_errno()
|
|
538
|
+
error_string = 'waitpid failed: %s' % strerror(error_number).decode('utf-8')
|
|
539
|
+
raise OSError(error_number, error_string)
|
|
540
|
+
|
|
541
|
+
# Decode exit status
|
|
542
|
+
if WIFEXITED(status.value):
|
|
543
|
+
return WEXITSTATUS(status.value) # Return exit code
|
|
544
|
+
elif WIFSIGNALED(status.value):
|
|
545
|
+
raise OSError('Child killed by signal: %d' % WTERMSIG(status.value))
|
|
546
|
+
else:
|
|
547
|
+
raise OSError('Child terminated abnormally')
|
|
548
|
+
|
|
549
|
+
|
|
550
|
+
def kill(pid):
|
|
551
|
+
# type: (int) -> None
|
|
552
|
+
# Send SIGKILL signal to terminate the process
|
|
553
|
+
if kill_(pid, 9) != 0:
|
|
554
|
+
error_number = ctypes.get_errno()
|
|
555
|
+
error_string = 'kill failed: %s' % strerror(error_number).decode('utf-8')
|
|
556
|
+
raise OSError(error_number, error_string)
|
|
557
|
+
|
|
558
|
+
# Call waitpid to prevent process from becoming zombie
|
|
559
|
+
status = ctypes.c_int()
|
|
560
|
+
if waitpid(pid, ctypes.byref(status), 0) == -1:
|
|
561
|
+
error_number = ctypes.get_errno()
|
|
562
|
+
error_string = 'waitpid failed: %s' % strerror(error_number).decode('utf-8')
|
|
563
|
+
raise OSError(error_number, error_string)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "ctypes-unicode-proclaunch"
|
|
7
|
+
version = "0.1.0a0"
|
|
8
|
+
description = "A minimal, robust, and Unicode-aware process launching library using ctypes."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=2"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name="Jifeng Wu", email="jifengwu2k@gmail.com" }
|
|
14
|
+
]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Programming Language :: Python :: 2",
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
]
|
|
20
|
+
dependencies = [
|
|
21
|
+
"escape-nt-command-line-argument",
|
|
22
|
+
"find-unicode-executable",
|
|
23
|
+
"posix-or-nt",
|
|
24
|
+
"read-unicode-environment-variables-dictionary",
|
|
25
|
+
"send-recv-json",
|
|
26
|
+
"typing; python_version < '3.5'"
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[project.urls]
|
|
30
|
+
"Homepage" = "https://github.com/jifengwu2k/ctypes-unicode-proclaunch"
|
|
31
|
+
"Bug Tracker" = "https://github.com/jifengwu2k/ctypes-unicode-proclaunch/issues"
|