From 7d8c23d5ce46eaeb13f1ca1910eabf76bdcda6a5 Mon Sep 17 00:00:00 2001 From: wiggin15 Date: Mon, 6 May 2019 01:13:56 -0600 Subject: [PATCH] Fix #1276: [AIX] use getargs to get process cmdline (#1500) (patch by @wiggin15) --- psutil/_psaix.py | 18 +++----- psutil/_psutil_aix.c | 86 +++++++++++++++++++++++++++--------- psutil/tests/test_process.py | 16 +++++++ 3 files changed, 87 insertions(+), 33 deletions(-) diff --git a/psutil/_psaix.py b/psutil/_psaix.py index 174dac1ec..d0aa0e94e 100644 --- a/psutil/_psaix.py +++ b/psutil/_psaix.py @@ -358,20 +358,13 @@ def __init__(self, pid): self._procfs_path = get_procfs_path() def oneshot_enter(self): - self._proc_name_and_args.cache_activate(self) self._proc_basic_info.cache_activate(self) self._proc_cred.cache_activate(self) def oneshot_exit(self): - self._proc_name_and_args.cache_deactivate(self) self._proc_basic_info.cache_deactivate(self) self._proc_cred.cache_deactivate(self) - @wrap_exceptions - @memoize_when_activated - def _proc_name_and_args(self): - return cext.proc_name_and_args(self.pid, self._procfs_path) - @wrap_exceptions @memoize_when_activated def _proc_basic_info(self): @@ -386,14 +379,17 @@ def _proc_cred(self): def name(self): if self.pid == 0: return "swapper" - # note: this is limited to 15 characters - return self._proc_name_and_args()[0].rstrip("\x00") + # note: max 16 characters + return cext.proc_name(self.pid, self._procfs_path).rstrip("\x00") @wrap_exceptions def exe(self): # there is no way to get executable path in AIX other than to guess, # and guessing is more complex than what's in the wrapping class - exe = self.cmdline()[0] + cmdline = self.cmdline() + if not cmdline: + return '' + exe = cmdline[0] if os.path.sep in exe: # relative or absolute path if not os.path.isabs(exe): @@ -415,7 +411,7 @@ def exe(self): @wrap_exceptions def cmdline(self): - return self._proc_name_and_args()[1].split(' ') + return cext.proc_args(self.pid) @wrap_exceptions def create_time(self): diff --git a/psutil/_psutil_aix.c b/psutil/_psutil_aix.c index adb65b170..1a83ac24e 100644 --- a/psutil/_psutil_aix.c +++ b/psutil/_psutil_aix.c @@ -23,20 +23,21 @@ * * Useful resources: * - proc filesystem: http://www-01.ibm.com/support/knowledgecenter/ - * ssw_aix_61/com.ibm.aix.files/proc.htm + * ssw_aix_72/com.ibm.aix.files/proc.htm * - libperfstat: http://www-01.ibm.com/support/knowledgecenter/ - * ssw_aix_61/com.ibm.aix.files/libperfstat.h.htm + * ssw_aix_72/com.ibm.aix.files/libperfstat.h.htm */ #include -#include -#include +#include #include -#include #include #include +#include +#include #include +#include #include #include #include @@ -134,17 +135,14 @@ psutil_proc_basic_info(PyObject *self, PyObject *args) { /* - * Return process name and args as a Python tuple. + * Return process name as a Python string. */ static PyObject * -psutil_proc_name_and_args(PyObject *self, PyObject *args) { +psutil_proc_name(PyObject *self, PyObject *args) { int pid; char path[100]; psinfo_t info; const char *procfs_path; - PyObject *py_name = NULL; - PyObject *py_args = NULL; - PyObject *py_retlist = NULL; if (! PyArg_ParseTuple(args, "is", &pid, &procfs_path)) return NULL; @@ -152,23 +150,65 @@ psutil_proc_name_and_args(PyObject *self, PyObject *args) { if (! psutil_file_to_struct(path, (void *)&info, sizeof(info))) return NULL; - py_name = PyUnicode_DecodeFSDefault(info.pr_fname); - if (!py_name) + return PyUnicode_DecodeFSDefaultAndSize(info.pr_fname, PRFNSZ); +} + + +/* + * Return process command line arguments as a Python list + */ +static PyObject * +psutil_proc_args(PyObject *self, PyObject *args) { + int pid; + PyObject *py_retlist = PyList_New(0); + PyObject *py_arg = NULL; + struct procsinfo procbuf; + long arg_max; + char *argbuf = NULL; + char *curarg = NULL; + int ret; + + if (py_retlist == NULL) + return NULL; + if (!PyArg_ParseTuple(args, "i", &pid)) goto error; - py_args = PyUnicode_DecodeFSDefault(info.pr_psargs); - if (!py_args) + arg_max = sysconf(_SC_ARG_MAX); + argbuf = malloc(arg_max); + if (argbuf == NULL) { + PyErr_NoMemory(); goto error; - py_retlist = Py_BuildValue("OO", py_name, py_args); - if (!py_retlist) + } + + procbuf.pi_pid = pid; + ret = getargs(&procbuf, sizeof(struct procinfo), argbuf, ARG_MAX); + if (ret == -1) { + PyErr_SetFromErrno(PyExc_OSError); goto error; - Py_DECREF(py_name); - Py_DECREF(py_args); + } + + curarg = argbuf; + /* getargs will always append an extra NULL to end the arg list, + * even if the buffer is not big enough (even though it is supposed + * to be) so the following 'while' is safe */ + while (*curarg != '\0') { + py_arg = PyUnicode_DecodeFSDefault(curarg); + if (!py_arg) + goto error; + if (PyList_Append(py_retlist, py_arg)) + goto error; + Py_DECREF(py_arg); + curarg = strchr(curarg, '\0') + 1; + } + + free(argbuf); + return py_retlist; error: - Py_XDECREF(py_name); - Py_XDECREF(py_args); + if (argbuf != NULL) + free(argbuf); Py_XDECREF(py_retlist); + Py_XDECREF(py_arg); return NULL; } @@ -880,8 +920,10 @@ PsutilMethods[] = // --- process-related functions {"proc_basic_info", psutil_proc_basic_info, METH_VARARGS, "Return process ppid, rss, vms, ctime, nice, nthreads, status and tty"}, - {"proc_name_and_args", psutil_proc_name_and_args, METH_VARARGS, - "Return process name and args."}, + {"proc_name", psutil_proc_name, METH_VARARGS, + "Return process name."}, + {"proc_args", psutil_proc_args, METH_VARARGS, + "Return process command line arguments."}, {"proc_cpu_times", psutil_proc_cpu_times, METH_VARARGS, "Return process user and system CPU times."}, {"proc_cred", psutil_proc_cred, METH_VARARGS, diff --git a/psutil/tests/test_process.py b/psutil/tests/test_process.py index a4b738eeb..512a84371 100755 --- a/psutil/tests/test_process.py +++ b/psutil/tests/test_process.py @@ -724,12 +724,28 @@ def test_cmdline(self): else: raise + def test_long_cmdline(self): + create_exe(TESTFN) + self.addCleanup(safe_rmpath, TESTFN) + cmdline = [TESTFN] + (["0123456789"] * 20) + sproc = get_test_subprocess(cmdline) + p = psutil.Process(sproc.pid) + self.assertEqual(p.cmdline(), cmdline) + def test_name(self): sproc = get_test_subprocess(PYTHON_EXE) name = psutil.Process(sproc.pid).name().lower() pyexe = os.path.basename(os.path.realpath(sys.executable)).lower() assert pyexe.startswith(name), (pyexe, name) + def test_long_name(self): + long_name = TESTFN + ("0123456789" * 2) + create_exe(long_name) + self.addCleanup(safe_rmpath, long_name) + sproc = get_test_subprocess(long_name) + p = psutil.Process(sproc.pid) + self.assertEqual(p.name(), os.path.basename(long_name)) + # XXX @unittest.skipIf(SUNOS, "broken on SUNOS") @unittest.skipIf(AIX, "broken on AIX")