Implement getting cmdline of process on macOS as well

This commit is contained in:
Kovid Goyal 2018-07-24 11:07:53 +05:30
parent 5485436ea1
commit 0dd3334811
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
5 changed files with 165 additions and 26 deletions

View File

@ -9,21 +9,23 @@
from .constants import is_macos, shell_path, terminfo_dir
if is_macos:
from kitty.fast_data_types import cmdline_of_process as _cmdl, cwd_of_process as _cwd
def cwd_of_process(pid):
if is_macos:
from kitty.fast_data_types import cwd_of_process
ans = cwd_of_process(pid)
else:
def cmdline_of_process(pid):
return _cmdl(pid)
def cwd_of_process(pid):
return os.path.realpath(_cwd(pid))
else:
def cmdline_of_process(pid):
return list(filter(None, open('/proc/{}/cmdline'.format(pid), 'rb').read().decode('utf-8').split('\0')))
def cwd_of_process(pid):
ans = '/proc/{}/cwd'.format(pid)
return os.path.realpath(ans)
def cmdline_of_process(pid):
if is_macos:
# TODO: macOS implementation, see DarwinProcess.c in htop for inspiration
raise NotImplementedError()
return list(filter(None, open('/proc/{}/cmdline'.format(pid), 'rb').read().decode('utf-8').split('\0')))
return os.path.realpath(ans)
def remove_cloexec(fd):

View File

@ -12,8 +12,6 @@
#include <AvailabilityMacros.h>
// Needed for _NSGetProgname
#include <crt_externs.h>
typedef void* rusage_info_t; // needed for libproc.h
#include <libproc.h>
#if (MAC_OS_X_VERSION_MAX_ALLOWED < 101200)
#define NSWindowStyleMaskResizable NSResizableWindowMask
@ -234,15 +232,6 @@ + (GlobalMenuTarget *) shared_instance
return Py_BuildValue("s", [locale UTF8String]);
}
static PyObject*
cwd_of_process(PyObject *self UNUSED, PyObject *pid_) {
long pid = PyLong_AsLong(pid_);
struct proc_vnodepathinfo vpi;
int ret = proc_pidinfo(pid, PROC_PIDVNODEPATHINFO, 0, &vpi, sizeof(vpi));
if (ret < 0) { PyErr_SetFromErrno(PyExc_OSError); return NULL; }
return PyUnicode_FromString(vpi.pvi_cdir.vip_path);
}
void
cocoa_set_hide_from_tasks(void) {
[NSApp setActivationPolicy:NSApplicationActivationPolicyAccessory];
@ -276,7 +265,6 @@ + (GlobalMenuTarget *) shared_instance
static PyMethodDef module_methods[] = {
{"cocoa_get_lang", (PyCFunction)cocoa_get_lang, METH_NOARGS, ""},
{"cwd_of_process", (PyCFunction)cwd_of_process, METH_O, ""},
{"cocoa_set_new_window_trigger", (PyCFunction)cocoa_set_new_window_trigger, METH_VARARGS, ""},
{NULL, NULL, 0, NULL} /* Sentinel */
};

View File

@ -215,6 +215,7 @@ extern bool init_png_reader(PyObject *module);
#ifdef __APPLE__
extern int init_CoreText(PyObject *);
extern bool init_cocoa(PyObject *module);
extern bool init_macos_process_info(PyObject *module);
#else
extern bool init_freetype_library(PyObject*);
#endif
@ -249,6 +250,7 @@ PyInit_fast_data_types(void) {
if (!init_kittens(m)) return NULL;
if (!init_png_reader(m)) return NULL;
#ifdef __APPLE__
if (!init_macos_process_info(m)) return NULL;
if (!init_CoreText(m)) return NULL;
if (!init_cocoa(m)) return NULL;
#else

147
kitty/macos_process_info.c Normal file
View File

@ -0,0 +1,147 @@
/*
* macos_process_info.c
* Copyright (C) 2018 Kovid Goyal <kovid at kovidgoyal.net>
*
* Distributed under terms of the GPL3 license.
*/
#include "data-types.h"
#include <sys/sysctl.h>
typedef void* rusage_info_t; // needed for libproc.h
#include <libproc.h>
static PyObject*
cwd_of_process(PyObject *self UNUSED, PyObject *pid_) {
if (!PyLong_Check(pid_)) { PyErr_SetString(PyExc_TypeError, "pid must be an int"); return NULL; }
long pid = PyLong_AsLong(pid_);
if (pid < 0) { PyErr_SetString(PyExc_TypeError, "pid cannot be negative"); return NULL; }
struct proc_vnodepathinfo vpi;
int ret = proc_pidinfo(pid, PROC_PIDVNODEPATHINFO, 0, &vpi, sizeof(vpi));
if (ret < 0) { PyErr_SetFromErrno(PyExc_OSError); return NULL; }
return PyUnicode_FromString(vpi.pvi_cdir.vip_path);
}
// Read the maximum argument size for processes
static inline int
get_argmax() {
int argmax;
int mib[] = { CTL_KERN, KERN_ARGMAX };
size_t size = sizeof(argmax);
if (sysctl(mib, 2, &argmax, &size, NULL, 0) == 0)
return argmax;
return 0;
}
static PyObject*
cmdline_of_process(PyObject *self UNUSED, PyObject *pid_) {
// Taken from psutil, with thanks (BSD 3-clause license)
int mib[3];
int nargs;
size_t len;
char *procargs = NULL;
char *arg_ptr;
char *arg_end;
char *curr_arg;
size_t argmax;
PyObject *py_arg = NULL;
PyObject *py_retlist = NULL;
if (!PyLong_Check(pid_)) { PyErr_SetString(PyExc_TypeError, "pid must be an int"); goto error; }
long pid = PyLong_AsLong(pid_);
if (pid < 0) { PyErr_SetString(PyExc_TypeError, "pid cannot be negative"); goto error; }
// special case for PID 0 (kernel_task) where cmdline cannot be fetched
if (pid == 0)
return Py_BuildValue("[]");
// read argmax and allocate memory for argument space.
argmax = get_argmax();
if (!argmax) {
PyErr_SetFromErrno(PyExc_OSError);
goto error;
}
procargs = (char *)malloc(argmax);
if (NULL == procargs) {
PyErr_SetFromErrno(PyExc_OSError);
goto error;
}
// read argument space
mib[0] = CTL_KERN;
mib[1] = KERN_PROCARGS2;
mib[2] = (pid_t)pid;
if (sysctl(mib, 3, procargs, &argmax, NULL, 0) < 0) {
// In case of zombie process or non-existant process we'll get EINVAL.
if (errno == EINVAL)
PyErr_Format(PyExc_ValueError, "process with pid %ld either does not exist or is a zombie", pid);
else
PyErr_SetFromErrno(PyExc_OSError);
goto error;
}
arg_end = &procargs[argmax];
// copy the number of arguments to nargs
memcpy(&nargs, procargs, sizeof(nargs));
arg_ptr = procargs + sizeof(nargs);
len = strlen(arg_ptr);
arg_ptr += len + 1;
if (arg_ptr == arg_end) {
free(procargs);
return Py_BuildValue("[]");
}
// skip ahead to the first argument
for (; arg_ptr < arg_end; arg_ptr++) {
if (*arg_ptr != '\0')
break;
}
// iterate through arguments
curr_arg = arg_ptr;
py_retlist = Py_BuildValue("[]");
if (!py_retlist)
goto error;
while (arg_ptr < arg_end && nargs > 0) {
if (*arg_ptr++ == '\0') {
py_arg = PyUnicode_DecodeFSDefault(curr_arg);
if (! py_arg)
goto error;
if (PyList_Append(py_retlist, py_arg))
goto error;
Py_DECREF(py_arg);
// iterate to next arg and decrement # of args
curr_arg = arg_ptr;
nargs--;
}
}
free(procargs);
return py_retlist;
error:
Py_XDECREF(py_arg);
Py_XDECREF(py_retlist);
if (procargs != NULL)
free(procargs);
return NULL;
}
static PyMethodDef module_methods[] = {
{"cwd_of_process", (PyCFunction)cwd_of_process, METH_O, ""},
{"cmdline_of_process", (PyCFunction)cmdline_of_process, METH_O, ""},
{NULL, NULL, 0, NULL} /* Sentinel */
};
bool
init_macos_process_info(PyObject *module) {
if (PyModule_AddFunctions(module, module_methods) != 0) return false;
return true;
}

View File

@ -402,7 +402,7 @@ def compile_c_extension(kenv, module, incremental, compilation_database, all_key
def find_c_files():
ans, headers = [], []
d = os.path.join(base, 'kitty')
exclude = {'fontconfig.c', 'freetype.c', 'desktop.c'} if is_macos else {'core_text.m', 'cocoa_window.m'}
exclude = {'fontconfig.c', 'freetype.c', 'desktop.c'} if is_macos else {'core_text.m', 'cocoa_window.m', 'macos_process_info.c'}
for x in os.listdir(d):
ext = os.path.splitext(x)[1]
if ext in ('.c', '.m') and os.path.basename(x) not in exclude: