Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
N
nemu3
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
1
Issues
1
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
nexedi
nemu3
Commits
935ce6a2
Commit
935ce6a2
authored
Jul 02, 2010
by
Martín Ferrari
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fixes, tests and basic Popen support
parent
9e912f98
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
144 additions
and
44 deletions
+144
-44
src/netns/subprocess.py
src/netns/subprocess.py
+104
-35
t/test_subprocess.py
t/test_subprocess.py
+40
-9
No files found.
src/netns/subprocess.py
View file @
935ce6a2
...
@@ -22,9 +22,8 @@ def spawn(executable, argv = None, cwd = None, env = None,
...
@@ -22,9 +22,8 @@ def spawn(executable, argv = None, cwd = None, env = None,
The standard input, output, and error of the created process will be
The standard input, output, and error of the created process will be
redirected to the file descriptors specified by `stdin`, `stdout`, and
redirected to the file descriptors specified by `stdin`, `stdout`, and
`stderr`, respectively. These parameters must be integers or None, in which
`stderr`, respectively. These parameters must be open file objects,
case, no redirection will occur. If the value is negative, the respective
integers or None, in which case, no redirection will occur.
file descriptor is closed in the executed program.
Note that the original descriptors are not closed, and that piping should
Note that the original descriptors are not closed, and that piping should
be handled externally.
be handled externally.
...
@@ -34,9 +33,12 @@ def spawn(executable, argv = None, cwd = None, env = None,
...
@@ -34,9 +33,12 @@ def spawn(executable, argv = None, cwd = None, env = None,
userfd
=
[
stdin
,
stdout
,
stderr
]
userfd
=
[
stdin
,
stdout
,
stderr
]
filtered_userfd
=
filter
(
lambda
x
:
x
!=
None
and
x
>=
0
,
userfd
)
filtered_userfd
=
filter
(
lambda
x
:
x
!=
None
and
x
>=
0
,
userfd
)
sysfd
=
[
x
.
fileno
()
for
x
in
sys
.
stdin
,
sys
.
stdout
,
sys
.
stderr
]
for
i
in
range
(
3
):
if
userfd
[
i
]
!=
None
and
not
isinstance
(
userfd
[
i
],
int
):
userfd
[
i
]
=
userfd
[
i
].
fileno
()
# Verify there is no clash
# Verify there is no clash
assert
not
(
set
(
sysfd
)
&
set
(
filtered_userfd
))
assert
not
(
set
(
[
0
,
1
,
2
]
)
&
set
(
filtered_userfd
))
if
user
!=
None
:
if
user
!=
None
:
if
str
(
user
).
isdigit
():
if
str
(
user
).
isdigit
():
...
@@ -61,13 +63,13 @@ def spawn(executable, argv = None, cwd = None, env = None,
...
@@ -61,13 +63,13 @@ def spawn(executable, argv = None, cwd = None, env = None,
# Set up stdio piping
# Set up stdio piping
for
i
in
range
(
3
):
for
i
in
range
(
3
):
if
userfd
[
i
]
!=
None
and
userfd
[
i
]
>=
0
:
if
userfd
[
i
]
!=
None
and
userfd
[
i
]
>=
0
:
os
.
dup2
(
userfd
[
i
],
sysfd
[
i
])
os
.
dup2
(
userfd
[
i
],
i
)
os
.
close
(
userfd
[
i
])
# only in child!
if
userfd
[
i
]
!=
i
and
userfd
[
i
]
not
in
userfd
[
0
:
i
]:
if
userfd
[
i
]
!=
None
and
userfd
[
i
]
<
0
:
_eintr_wrapper
(
os
.
close
,
userfd
[
i
])
# only in child!
os
.
close
(
sysfd
[
i
])
# Set up special control pipe
# Set up special control pipe
os
.
close
(
r
)
_eintr_wrapper
(
os
.
close
,
r
)
fcntl
.
fcntl
(
w
,
fcntl
.
F_SETFD
,
fcntl
.
FD_CLOEXEC
)
flags
=
fcntl
.
fcntl
(
w
,
fcntl
.
F_GETFD
)
fcntl
.
fcntl
(
w
,
fcntl
.
F_SETFD
,
flags
|
fcntl
.
FD_CLOEXEC
)
if
user
!=
None
:
if
user
!=
None
:
# Change user
# Change user
os
.
setgid
(
gid
)
os
.
setgid
(
gid
)
...
@@ -95,45 +97,34 @@ def spawn(executable, argv = None, cwd = None, env = None,
...
@@ -95,45 +97,34 @@ def spawn(executable, argv = None, cwd = None, env = None,
# subprocess.py
# subprocess.py
v
.
child_traceback
=
""
.
join
(
v
.
child_traceback
=
""
.
join
(
traceback
.
format_exception
(
t
,
v
,
tb
))
traceback
.
format_exception
(
t
,
v
,
tb
))
os
.
write
(
w
,
pickle
.
dumps
(
v
))
_eintr_wrapper
(
os
.
write
,
w
,
pickle
.
dumps
(
v
))
os
.
close
(
w
)
_eintr_wrapper
(
os
.
close
,
w
)
#traceback.print_exc()
except
:
except
:
traceback
.
print_exc
()
traceback
.
print_exc
()
os
.
_exit
(
1
)
os
.
_exit
(
1
)
os
.
close
(
w
)
_eintr_wrapper
(
os
.
close
,
w
)
# read EOF for success, or a string as error info
# read EOF for success, or a string as error info
s
=
""
s
=
""
while
True
:
while
True
:
s1
=
os
.
read
(
r
,
4096
)
s1
=
_eintr_wrapper
(
os
.
read
,
r
,
4096
)
if
s1
==
""
:
if
s1
==
""
:
break
break
s
+=
s1
s
+=
s1
os
.
close
(
r
)
_eintr_wrapper
(
os
.
close
,
r
)
if
s
==
""
:
if
s
==
""
:
return
pid
return
pid
# It was an error
# It was an error
os
.
waitpid
(
pid
,
0
)
_eintr_wrapper
(
os
.
waitpid
,
pid
,
0
)
exc
=
pickle
.
loads
(
s
)
exc
=
pickle
.
loads
(
s
)
# XXX: sys.excepthook
# XXX: sys.excepthook
#print exc.child_traceback
#print exc.child_traceback
raise
exc
raise
exc
# Used to print extra info in nested exceptions
def
_custom_hook
(
t
,
v
,
tb
):
sys
.
stderr
.
write
(
"wee
\
n
"
)
if
hasattr
(
v
,
"child_traceback"
):
sys
.
stderr
.
write
(
"Nested exception, original traceback "
+
"(most recent call last):
\
n
"
)
sys
.
stderr
.
write
(
v
.
child_traceback
+
(
"-"
*
70
)
+
"
\
n
"
)
sys
.
__excepthook__
(
t
,
v
,
tb
)
# XXX: somebody kill me, I deserve it :)
sys
.
excepthook
=
_custom_hook
def
poll
(
pid
):
def
poll
(
pid
):
"""Check if the process already died. Returns the exit code or None if
"""Check if the process already died. Returns the exit code or None if
the process is still alive."""
the process is still alive."""
...
@@ -144,7 +135,7 @@ def poll(pid):
...
@@ -144,7 +135,7 @@ def poll(pid):
def
wait
(
pid
):
def
wait
(
pid
):
"""Wait for process to die and return the exit code."""
"""Wait for process to die and return the exit code."""
return
os
.
waitpid
(
pid
,
0
)[
1
]
return
_eintr_wrapper
(
os
.
waitpid
,
pid
,
0
)[
1
]
class
Subprocess
(
object
):
class
Subprocess
(
object
):
# FIXME: this is the visible interface; documentation should move here.
# FIXME: this is the visible interface; documentation should move here.
...
@@ -167,6 +158,7 @@ class Subprocess(object):
...
@@ -167,6 +158,7 @@ class Subprocess(object):
user
=
user
)
user
=
user
)
node
.
_add_subprocess
(
self
)
node
.
_add_subprocess
(
self
)
self
.
_returncode
=
None
@
property
@
property
def
pid
(
self
):
def
pid
(
self
):
...
@@ -176,14 +168,91 @@ class Subprocess(object):
...
@@ -176,14 +168,91 @@ class Subprocess(object):
r
=
self
.
_slave
.
poll
(
self
.
_pid
)
r
=
self
.
_slave
.
poll
(
self
.
_pid
)
if
r
!=
None
:
if
r
!=
None
:
del
self
.
_pid
del
self
.
_pid
self
.
return_valu
e
=
r
self
.
_returncod
e
=
r
return
r
return
self
.
returncode
def
wait
(
self
):
def
wait
(
self
):
r
=
self
.
_slave
.
wait
(
self
.
_pid
)
self
.
_returncode
=
self
.
_slave
.
wait
(
self
.
_pid
)
del
self
.
_pid
del
self
.
_pid
self
.
return_value
=
r
return
self
.
returncode
return
r
def
signal
(
self
,
sig
=
signal
.
SIGTERM
):
def
signal
(
self
,
sig
=
signal
.
SIGTERM
):
return
self
.
_slave
.
signal
(
self
.
_pid
,
sig
)
return
self
.
_slave
.
signal
(
self
.
_pid
,
sig
)
@
property
def
returncode
(
self
):
if
self
.
_returncode
==
None
:
return
None
if
os
.
WIFSIGNALED
(
self
.
_returncode
):
return
-
os
.
WTERMSIG
(
self
.
_returncode
)
if
os
.
WIFEXITED
(
self
.
_returncode
):
return
os
.
EXITSTATUS
(
self
.
_returncode
)
raise
RuntimeError
(
"Invalid return code"
)
# FIXME: do we have any other way to deal with this than having explicit
# destroy?
def
destroy
(
self
):
pass
PIPE
=
-
1
STDOUT
=
-
2
class
Popen
(
Subprocess
):
def
__init__
(
self
,
node
,
executable
,
argv
=
None
,
cwd
=
None
,
env
=
None
,
stdin
=
None
,
stdout
=
None
,
stderr
=
None
,
user
=
None
,
bufsize
=
0
):
self
.
stdin
=
self
.
stdout
=
self
.
stderr
=
None
fdmap
=
{
"stdin"
:
stdin
,
"stdout"
:
stdout
,
"stderr"
:
stderr
}
# if PIPE: all should be closed at the end
for
k
,
v
in
fdmap
:
if
v
==
None
:
continue
if
v
==
PIPE
:
r
,
w
=
os
.
pipe
()
if
k
==
"stdin"
:
setattr
(
self
,
k
,
os
.
fdopen
(
w
,
'wb'
,
bufsize
))
fdmap
[
k
]
=
r
else
:
setattr
(
self
,
k
,
os
.
fdopen
(
w
,
'rb'
,
bufsize
))
fdmap
[
k
]
=
w
elif
isinstance
(
v
,
int
):
pass
else
:
fdmap
[
k
]
=
v
.
fileno
()
if
stderr
==
STDOUT
:
fdmap
[
'stderr'
]
=
fdmap
[
'stdout'
]
#print fdmap
super
(
Popen
,
self
).
__init__
(
node
,
executable
,
argv
=
argv
,
cwd
=
cwd
,
env
=
env
,
stdin
=
fdmap
[
'stdin'
],
stdout
=
fdmap
[
'stdout'
],
stderr
=
fdmap
[
'stderr'
],
user
=
user
)
# Close pipes, they have been dup()ed to the child
for
k
,
v
in
fdmap
:
if
getattr
(
self
,
k
)
!=
None
:
_eintr_wrapper
(
os
.
close
,
v
)
# def comunicate(self, input = None)
def
_eintr_wrapper
(
f
,
*
args
):
"Wraps some callable with a loop that retries on EINTR"
while
True
:
try
:
return
f
(
*
args
)
except
OSError
,
e
:
if
e
.
errno
==
errno
.
EINTR
:
continue
else
:
raise
# Used to print extra info in nested exceptions
def
_custom_hook
(
t
,
v
,
tb
):
if
hasattr
(
v
,
"child_traceback"
):
sys
.
stderr
.
write
(
"Nested exception, original traceback "
+
"(most recent call last):
\
n
"
)
sys
.
stderr
.
write
(
v
.
child_traceback
+
(
"-"
*
70
)
+
"
\
n
"
)
sys
.
__excepthook__
(
t
,
v
,
tb
)
# XXX: somebody kill me, I deserve it :)
sys
.
excepthook
=
_custom_hook
t/test_subprocess.py
View file @
935ce6a2
...
@@ -22,6 +22,21 @@ def _getpwuid(uid):
...
@@ -22,6 +22,21 @@ def _getpwuid(uid):
except
:
except
:
return
None
return
None
def
_readall
(
fd
):
s
=
""
while
True
:
try
:
s1
=
os
.
read
(
fd
,
4096
)
except
OSError
,
e
:
if
e
.
errno
==
errno
.
EINTR
:
continue
else
:
raise
if
s1
==
""
:
break
s
+=
s1
return
s
class
TestSubprocess
(
unittest
.
TestCase
):
class
TestSubprocess
(
unittest
.
TestCase
):
def
_check_ownership
(
self
,
user
,
pid
):
def
_check_ownership
(
self
,
user
,
pid
):
uid
=
pwd
.
getpwnam
(
user
)[
2
]
uid
=
pwd
.
getpwnam
(
user
)[
2
]
...
@@ -51,8 +66,6 @@ class TestSubprocess(unittest.TestCase):
...
@@ -51,8 +66,6 @@ class TestSubprocess(unittest.TestCase):
while
_stat
(
self
.
nofile
):
while
_stat
(
self
.
nofile
):
self
.
nofile
+=
'_'
self
.
nofile
+=
'_'
# XXX: unittest still cannot skip tests
#@unittest.skipUnless(os.getuid() == 0, "Test requires root privileges")
@
test_util
.
skipUnless
(
os
.
getuid
()
==
0
,
"Test requires root privileges"
)
@
test_util
.
skipUnless
(
os
.
getuid
()
==
0
,
"Test requires root privileges"
)
def
test_spawn_chuser
(
self
):
def
test_spawn_chuser
(
self
):
user
=
'nobody'
user
=
'nobody'
...
@@ -62,6 +75,16 @@ class TestSubprocess(unittest.TestCase):
...
@@ -62,6 +75,16 @@ class TestSubprocess(unittest.TestCase):
os
.
kill
(
pid
,
signal
.
SIGTERM
)
os
.
kill
(
pid
,
signal
.
SIGTERM
)
self
.
assertEquals
(
netns
.
subprocess
.
wait
(
pid
),
signal
.
SIGTERM
)
self
.
assertEquals
(
netns
.
subprocess
.
wait
(
pid
),
signal
.
SIGTERM
)
@
test_util
.
skipUnless
(
os
.
getuid
()
==
0
,
"Test requires root privileges"
)
def
test_Subprocess_chuser
(
self
):
node
=
netns
.
Node
(
nonetns
=
True
)
user
=
'nobody'
p
=
netns
.
subprocess
.
Subprocess
(
node
,
'/bin/sleep'
,
[
'/bin/sleep'
,
'1000'
],
user
=
user
)
self
.
_check_ownership
(
user
,
p
.
pid
)
p
.
signal
()
self
.
assertEquals
(
p
.
wait
(),
-
signal
.
SIGTERM
)
def
test_spawn_basic
(
self
):
def
test_spawn_basic
(
self
):
# User does not exist
# User does not exist
self
.
assertRaises
(
ValueError
,
netns
.
subprocess
.
spawn
,
self
.
assertRaises
(
ValueError
,
netns
.
subprocess
.
spawn
,
...
@@ -82,13 +105,16 @@ class TestSubprocess(unittest.TestCase):
...
@@ -82,13 +105,16 @@ class TestSubprocess(unittest.TestCase):
# uses a default search path
# uses a default search path
self
.
assertRaises
(
OSError
,
netns
.
subprocess
.
spawn
,
self
.
assertRaises
(
OSError
,
netns
.
subprocess
.
spawn
,
'sleep'
,
env
=
{
'PATH'
:
''
})
'sleep'
,
env
=
{
'PATH'
:
''
})
#p = netns.subprocess.spawn(None, '/bin/sleep', ['/bin/sleep', '1000'],
# cwd = '/', env = [])
# FIXME: tests fds
@
test_util
.
skipUnless
(
os
.
getuid
()
==
0
,
"Test requires root privileges"
)
r
,
w
=
os
.
pipe
()
p
=
netns
.
subprocess
.
spawn
(
'/bin/echo'
,
[
'echo'
,
'hello world'
],
stdout
=
w
)
os
.
close
(
w
)
self
.
assertEquals
(
_readall
(
r
),
"hello world
\
n
"
)
os
.
close
(
r
)
def
test_Subprocess_basic
(
self
):
def
test_Subprocess_basic
(
self
):
node
=
netns
.
Node
()
node
=
netns
.
Node
(
nonetns
=
True
)
#, debug = True
)
# User does not exist
# User does not exist
self
.
assertRaises
(
RuntimeError
,
netns
.
subprocess
.
Subprocess
,
node
,
self
.
assertRaises
(
RuntimeError
,
netns
.
subprocess
.
Subprocess
,
node
,
'/bin/sleep'
,
[
'/bin/sleep'
,
'1000'
],
user
=
self
.
nouser
)
'/bin/sleep'
,
[
'/bin/sleep'
,
'1000'
],
user
=
self
.
nouser
)
...
@@ -108,10 +134,15 @@ class TestSubprocess(unittest.TestCase):
...
@@ -108,10 +134,15 @@ class TestSubprocess(unittest.TestCase):
# uses a default search path
# uses a default search path
self
.
assertRaises
(
RuntimeError
,
netns
.
subprocess
.
Subprocess
,
node
,
self
.
assertRaises
(
RuntimeError
,
netns
.
subprocess
.
Subprocess
,
node
,
'sleep'
,
env
=
{
'PATH'
:
''
})
'sleep'
,
env
=
{
'PATH'
:
''
})
#p = netns.subprocess.Subprocess(None, '/bin/sleep', ['/bin/sleep', '1000'], cwd = '/', env = [])
# FIXME: tests fds
# FIXME: tests fds
r
,
w
=
os
.
pipe
()
p
=
netns
.
subprocess
.
Subprocess
(
node
,
'/bin/echo'
,
[
'echo'
,
'hello world'
],
stdout
=
w
)
os
.
close
(
w
)
self
.
assertEquals
(
_readall
(
r
),
"hello world
\
n
"
)
os
.
close
(
r
)
# FIXME: tests for Popen!
if
__name__
==
'__main__'
:
if
__name__
==
'__main__'
:
unittest
.
main
()
unittest
.
main
()
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment