Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bpo-20104: Expose posix_spawn in the os module #5109

Merged
merged 8 commits into from
Jan 29, 2018

Conversation

pablogsal
Copy link
Member

@pablogsal pablogsal commented Jan 6, 2018

This PR exposes posix_spawn in the os module. This also adds a new class in said module to work with posix_spawn file_actions argument.

https://bugs.python.org/issue20104

argv is a list or tuple of strings and env is a dictionary
like posix.environ. */

if (!PyList_Check(argv) && !PyTuple_Check(argv)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PySequence_Check

Lib/os.py Outdated
@@ -1076,3 +1076,46 @@ def __fspath__(self):
@classmethod
def __subclasshook__(cls, subclass):
return hasattr(subclass, '__fspath__')

class FileActions(object):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This class does not map well to the POSIX posix_spawn() API. It take an arbitrary ordered sequence of file_actions populated by the underlying posix_spawn_file_actions_add{dup2,open.close} C APIs.

What you really want is for the posix_spawn Python API to take a list of actions, each of which is an instance of a fileaction describing a dup2/open/close operation.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @gpshead ! That makes a lot of sense. Should I create three different classes for dup2/open/close and pass a list of these of there is a better approach?

file_actions.add_open(3,os.path.realpath(__file__),0,0)
file_actions.add_close(2)
file_actions.add_dup2(1,4)
pid = posix.posixspawn(sys.executable, [sys.executable, "-c", "pass"], os.environ, file_actions)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Python API name should be .posix_spawn to match the posix C API name.

@bedevere-bot
Copy link

A Python core developer has requested some changes be made to your pull request before we can consider merging it. If you could please address their requests along with any other requests in other reviews from core developers that would be appreciated.

Once you have made the requested changes, please leave a comment on this pull request containing the phrase I have made the requested changes; please review again. I will then notify any core developers who have left a review that you're ready for them to take another look at this pull request.

@gpshead
Copy link
Member

gpshead commented Jan 8, 2018

I suggest creating namedtuple instances for each of dup2, open, and close in the module and requiring this parameter to be a sequence of those.

@pablogsal
Copy link
Member Author

Is then ok to bring collections to the os module? I am asking this because is a big dependency.

@gpshead
Copy link
Member

gpshead commented Jan 8, 2018

good point. given that, just define a trio of simple data classes of your own or some constants to indicate open/close/dup2 for use as the first element of a tuple? the "good" thing about os/posix module APIs is that they can be quite low level looking. This is the module providing a relatively raw wrapper of system calls. higher level abstrations to make using them easier often live in other modules.

@pablogsal
Copy link
Member Author

I have made the requested changes; please review again

@bedevere-bot
Copy link

Thanks for making the requested changes!

@gpshead: please review the changes made to this pull request.

@serhiy-storchaka serhiy-storchaka changed the title bpo-20104 Expose expose posix_spawn in the os module bpo-20104 Expose posix_spawn in the os module Jan 12, 2018
@serhiy-storchaka serhiy-storchaka changed the title bpo-20104 Expose posix_spawn in the os module bpo-20104: Expose posix_spawn in the os module Jan 12, 2018
self.assertEqual(os.waitpid(pid,0),(pid,0))


@unittest.skipUnless(hasattr(os, 'posixspawn'), "test needs os.posix_spawn")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be hasattr(os, "posix_spawn"). Same for the other test

@@ -0,0 +1,2 @@
Expose posix_spawn and a FileActions object to work with said function in
Copy link

@pppery pppery Jan 18, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

News needs update to no longer mention FileActions class

@pablogsal
Copy link
Member Author

@pppery Thanks! Corrected in b9db2c8

@pablogsal pablogsal force-pushed the bpo20104 branch 7 times, most recently from 7c9d964 to b25b7d1 Compare January 21, 2018 02:12
switch(mode) {

case POSIX_SPAWN_OPEN:
if(PySequence_Size(file_actions_obj) != 5){
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you need to check for errors in all of these cases: The PyXXX_AsYYY APIs can cause TypeError or OverflowError. PyLong_As APIs return -1 on error, use PyErr_Occurred() to disambiguate. Check for NULL from PyUnicode_As.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in cb440d5



}
#endif
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mention the name of the ifdef this pairs with.

return PyLong_FromPid(pid);
_Py_END_SUPPRESS_IPH

path_error(path);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lack of indentation down here looks odd for everything but the "fail:" label.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in cb440d5

@pablogsal
Copy link
Member Author

I have made the requested changes; please review again

@bedevere-bot
Copy link

Thanks for making the requested changes!

@gpshead: please review the changes made to this pull request.

@gpshead gpshead merged commit 6c6ddf9 into python:master Jan 29, 2018
@pablogsal pablogsal deleted the bpo20104 branch January 29, 2018 02:12
argv is a list or tuple of strings and env is a dictionary
like posix.environ. */

if (!PySequence_Check(argv)){
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not (!PyList_Check(argv) && !PyTuple_Check(argv)) as in other places?

Py_ssize_t argc, envc;

/* posix_spawn has three arguments: (path, argv, env), where
argv is a list or tuple of strings and env is a dictionary
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bad indentation.

posix_spawn_file_actions_t *file_actionsp = NULL;
if (file_actions != NULL && file_actions != Py_None){
posix_spawn_file_actions_t _file_actions;
if(posix_spawn_file_actions_init(&_file_actions) != 0){
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't conform PEP 7. Needed spaces after "if" and before "{".

"Error initializing file actions");
goto fail;
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Too much empty lines.

for (int i = 0; i < PySequence_Fast_GET_SIZE(seq); ++i) {
file_actions_obj = PySequence_Fast_GET_ITEM(seq, i);

if(!PySequence_Check(file_actions_obj) | !PySequence_Size(file_actions_obj)){
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PySequence_Size() can be called before PySequence_Check().

Use PySequence_Fast_GET_SIZE() instead of PySequence_Check().

goto fail;
}

long open_fd = PyLong_AsLong(PySequence_GetItem(file_actions_obj, 1));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PyLong_AsLong() can call arbitrary Python code. This can cause changing the size of file_actions_obj. Following PySequence_GetItem() can fail.

I would require file_actions_obj to be a tuple and use PyArg_ParseTuple() for parsing it.

}

long open_fd = PyLong_AsLong(PySequence_GetItem(file_actions_obj, 1));
if(PyErr_Occurred()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use idiomatic code:

if (open_fd == -1 && PyErr_Occurred()) {


_Py_BEGIN_SUPPRESS_IPH
posix_spawn(&pid, path->narrow, file_actionsp, NULL, argvlist, envlist);
return PyLong_FromPid(pid);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Return before _Py_END_SUPPRESS_IPH? This looks like a bug.

}

_Py_BEGIN_SUPPRESS_IPH
posix_spawn(&pid, path->narrow, file_actionsp, NULL, argvlist, envlist);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The result of the call is not checked.


path_error(path);

free_string_array(envlist, envc);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't it leaked in error case?

if(PyErr_Occurred()) {
goto fail;
}
const char* open_path = PyUnicode_AsUTF8(PySequence_GetItem(file_actions_obj, 2));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use filesystem encoding. PyUnicode_FSDecoder() or like.

goto fail;
}

long open_fd = PyLong_AsLong(PySequence_GetItem(file_actions_obj, 1));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

open_fd should be of type int. Use _PyLong_AsInt().

if(open_path == NULL){
goto fail;
}
long open_oflag = PyLong_AsLong(PySequence_GetItem(file_actions_obj, 3));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be int.

if(PyErr_Occurred()) {
goto fail;
}
long open_mode = PyLong_AsLong(PySequence_GetItem(file_actions_obj, 4));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check integer overflow when cast to mode_t.

goto fail;
}

long close_fd = PyLong_AsLong(PySequence_GetItem(file_actions_obj, 1));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be int.

goto fail;
}

long fd1 = PyLong_AsLong(PySequence_GetItem(file_actions_obj, 1));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be int.

break;

default:
PyErr_SetString(PyExc_TypeError,"Unknown file_actions identifier");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't ValueError more appropriate exception type?



mode_obj = PySequence_Fast_GET_ITEM(file_actions_obj, 0);
int mode = PyLong_AsLong(mode_obj);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be checked for error.

@serhiy-storchaka
Copy link
Member

Also there are trailing spaces added by this PR.

if (file_actions != NULL && file_actions != Py_None){
posix_spawn_file_actions_t _file_actions;
if(posix_spawn_file_actions_init(&_file_actions) != 0){
PyErr_SetString(PyExc_TypeError,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't errno be included in the exception?


pid_t pid;
posix_spawn_file_actions_t *file_actionsp = NULL;
if (file_actions != NULL && file_actions != Py_None){
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

file_actions is never NULL.

@pablogsal
Copy link
Member Author

pablogsal commented Apr 1, 2018

@serhiy-storchaka I am trying to correct all these new issues in Corrected in #6331. Please, notice that some of them (like the result of posix_spawn not checked and the return before _Py_END_SUPPRESS_IPH) were already corrected as this is an outdated diff. Thanks for all the effort with this!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants