Skip to content

Commit dadcd3d

Browse files
committed
journal: allow sd_journal_open_directory_fd to be used
1 parent 42f627b commit dadcd3d

File tree

3 files changed

+125
-53
lines changed

3 files changed

+125
-53
lines changed

systemd/_reader.c

Lines changed: 106 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@
4444
# define HAVE_HAS_PERSISTENT_FILES
4545
#endif
4646

47+
#if LIBSYSTEMD_VERSION >= 230
48+
# define HAVE_JOURNAL_OPEN_DIRECTORY_FD
49+
#endif
50+
4751
typedef struct {
4852
PyObject_HEAD
4953
sd_journal *j;
@@ -75,22 +79,49 @@ static PyStructSequence_Desc Monotonic_desc = {
7579
#endif
7680

7781
/**
78-
* Convert a Python sequence object into a strv (char**), and
79-
* None into a NULL pointer.
82+
* Convert a str or bytes object into a C-string path.
83+
* Returns NULL on error.
8084
*/
81-
static int strv_converter(PyObject* obj, void *_result) {
82-
char ***result = _result;
83-
Py_ssize_t i, len;
85+
static char* convert_path(PyObject *path, PyObject **bytes) {
86+
#if PY_MAJOR_VERSION >=3 && PY_MINOR_VERSION >= 1
87+
int r;
88+
89+
r = PyUnicode_FSConverter(path, bytes);
90+
if (r == 0)
91+
return NULL;
92+
93+
return PyBytes_AsString(*bytes);
94+
#else
95+
return PyString_AsString(path);
96+
#endif
97+
}
98+
99+
/**
100+
* Return NULL is obj is None, and the object otherwise.
101+
*/
102+
static int null_converter(PyObject* obj, void *_result) {
103+
PyObject **result = _result;
84104

85105
assert(result);
86106

87107
if (!obj)
88108
return 0;
89109

90-
if (obj == Py_None) {
110+
if (obj == Py_None)
91111
*result = NULL;
92-
return 1;
93-
}
112+
else
113+
*result = obj;
114+
return 1;
115+
}
116+
117+
/**
118+
* Convert a Python sequence object into a strv (char**).
119+
*/
120+
static int strv_converter(PyObject* obj, void *_result) {
121+
char ***result = _result;
122+
Py_ssize_t i, len;
123+
124+
assert(result);
94125

95126
if (!PySequence_Check(obj))
96127
return 0;
@@ -104,32 +135,21 @@ static int strv_converter(PyObject* obj, void *_result) {
104135

105136
for (i = 0; i < len; i++) {
106137
PyObject *item;
107-
#if PY_MAJOR_VERSION >=3 && PY_MINOR_VERSION >= 1
108-
int r;
109-
PyObject *bytes;
110-
#endif
111-
char *s, *s2;
138+
_cleanup_Py_DECREF_ PyObject *bytes = NULL;
139+
char *s;
112140

113141
item = PySequence_ITEM(obj, i);
114-
#if PY_MAJOR_VERSION >=3 && PY_MINOR_VERSION >= 1
115-
r = PyUnicode_FSConverter(item, &bytes);
116-
if (r == 0)
117-
goto cleanup;
118-
119-
s = PyBytes_AsString(bytes);
120-
#else
121-
s = PyString_AsString(item);
122-
#endif
142+
s = convert_path(item, &bytes);
123143
if (!s)
124144
goto cleanup;
125145

126-
s2 = strdup(s);
127-
if (!s2) {
146+
s = strdup(s);
147+
if (!s) {
128148
set_error(-ENOMEM, NULL, NULL);
129149
goto cleanup;
130150
}
131151

132-
(*result)[i] = s2;
152+
(*result)[i] = s;
133153
}
134154

135155
return 1;
@@ -151,49 +171,84 @@ PyDoc_STRVAR(Reader__doc__,
151171
"_Reader allows filtering and retrieval of Journal entries.\n"
152172
"Note: this is a low-level interface, and probably not what you\n"
153173
"want, use systemd.journal.Reader instead.\n\n"
154-
"Argument `flags` sets open flags of the journal, which can be one\n"
155-
"of, or ORed combination of constants: LOCAL_ONLY (default) opens\n"
156-
"journal on local machine only; RUNTIME_ONLY opens only\n"
157-
"volatile journal files; and SYSTEM opens journal files of\n"
158-
"system services and the kernel, and CURRENT_USER opens files\n"
159-
"of the current user.\n\n"
160-
"Argument `path` is the directory of journal files.\n"
161-
"Argument `files` is a list of files. Note that\n"
162-
"`flags`, `path`, and `files` are exclusive.\n\n"
163-
"_Reader implements the context manager protocol: the journal\n"
164-
"will be closed when exiting the block.");
174+
"Argument `flags` sets open flags of the journal, which can be one of, or an ORed\n"
175+
"combination of constants: LOCAL_ONLY (default) opens journal on local machine only;\n"
176+
"RUNTIME_ONLY opens only volatile journal files; and SYSTEM opens journal files of\n"
177+
"system services and the kernel, and CURRENT_USER opens file of the current user.\n"
178+
"\n"
179+
"Instead of opening the system journal, argument `path` may specify a directory\n"
180+
"which contains the journal. It maybe be either a file system path (a string), or\n"
181+
"a file descriptor (an integer). Alternatively, argument `files` may specify a list\n"
182+
"of journal file names. Note that `flags`, `path`, `files`, `directory_fd` are\n"
183+
"exclusive.\n\n"
184+
"_Reader implements the context manager protocol: the journal will be closed when\n"
185+
"exiting the block.");
165186
static int Reader_init(Reader *self, PyObject *args, PyObject *keywds) {
166187
int flags = 0, r;
167-
char *path = NULL;
168-
char **files = NULL;
188+
PyObject *_path = NULL;
189+
_cleanup_strv_free_ char **files = NULL;
169190

170191
static const char* const kwlist[] = {"flags", "path", "files", NULL};
171-
if (!PyArg_ParseTupleAndKeywords(args, keywds, "|izO&:__init__", (char**) kwlist,
172-
&flags, &path, strv_converter, &files))
192+
if (!PyArg_ParseTupleAndKeywords(args, keywds, "|iO&O&:__init__", (char**) kwlist,
193+
&flags,
194+
null_converter, &_path,
195+
strv_converter, &files))
173196
return -1;
174197

175-
if (!!flags + !!path + !!files > 1) {
176-
PyErr_SetString(PyExc_ValueError, "cannot use more than one of flags, path, and files");
198+
if (!!flags + !!_path + !!files > 1) {
199+
PyErr_SetString(PyExc_ValueError,
200+
"cannot use more than one of flags, path, and files");
177201
return -1;
178202
}
179203

180-
if (!flags)
181-
flags = SD_JOURNAL_LOCAL_ONLY;
204+
if (_path) {
205+
if (long_Check(_path)) {
206+
long directory_fd = long_AsLong(_path);
207+
if (PyErr_Occurred())
208+
return -1;
182209

183-
Py_BEGIN_ALLOW_THREADS
184-
if (path)
185-
r = sd_journal_open_directory(&self->j, path, 0);
186-
else if (files) {
210+
if ((int) directory_fd != directory_fd) {
211+
PyErr_SetString(PyExc_OverflowError, "Value too large");
212+
return -1;
213+
}
214+
215+
#ifdef HAVE_JOURNAL_OPEN_DIRECTORY_FD
216+
Py_BEGIN_ALLOW_THREADS
217+
r = sd_journal_open_directory_fd(&self->j, (int) directory_fd, 0);
218+
Py_END_ALLOW_THREADS
219+
#else
220+
r = -ENOSYS;
221+
#endif
222+
} else {
223+
char *path = NULL;
224+
_cleanup_Py_DECREF_ PyObject *path_bytes = NULL;
225+
226+
path = convert_path(_path, &path_bytes);
227+
if (!path)
228+
return -1;
229+
230+
Py_BEGIN_ALLOW_THREADS
231+
r = sd_journal_open_directory(&self->j, path, 0);
232+
Py_END_ALLOW_THREADS
233+
}
234+
} else if (files) {
187235
#ifdef HAVE_JOURNAL_OPEN_FILES
236+
Py_BEGIN_ALLOW_THREADS
188237
r = sd_journal_open_files(&self->j, (const char**) files, 0);
238+
Py_END_ALLOW_THREADS
189239
#else
190240
r = -ENOSYS;
191241
#endif
192-
} else
242+
} else {
243+
if (!flags)
244+
flags = SD_JOURNAL_LOCAL_ONLY;
245+
246+
Py_BEGIN_ALLOW_THREADS
193247
r = sd_journal_open(&self->j, flags);
194-
Py_END_ALLOW_THREADS
248+
Py_END_ALLOW_THREADS
249+
}
195250

196-
return set_error(r, path, "Invalid flags or path");
251+
return set_error(r, NULL, "Opening the journal failed");
197252
}
198253

199254
PyDoc_STRVAR(Reader_fileno__doc__,

systemd/journal.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,8 +139,9 @@ def __init__(self, flags=0, path=None, files=None, converters=None):
139139
on local machine only; RUNTIME_ONLY opens only volatile journal files;
140140
and SYSTEM_ONLY opens only journal files of system services and the kernel.
141141
142-
Argument `path` is the directory of journal files. Note that `flags` and
143-
`path` are exclusive.
142+
Argument `path` is the directory of journal files, either a file system
143+
path or a file descriptor. Note that `flags`, `path`, and `files` are
144+
exclusive.
144145
145146
Argument `converters` is a dictionary which updates the
146147
DEFAULT_CONVERTERS to convert journal field values. Field names are used

systemd/test/test_journal.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import logging
22
import uuid
33
import errno
4+
import os
45
from systemd import journal, id128
56

67
import pytest
@@ -52,6 +53,21 @@ def test_reader_init_path(tmpdir):
5253
with pytest.raises(ValueError):
5354
journal.Reader(journal.LOCAL_ONLY, path=tmpdir.strpath)
5455

56+
def test_reader_init_path_invalid_fd():
57+
with pytest.raises(OSError):
58+
journal.Reader(0, path=-1)
59+
60+
def test_reader_init_path_nondirectory_fd():
61+
with pytest.raises(OSError):
62+
journal.Reader(0, path=0)
63+
64+
def test_reader_init_path_fd(tmpdir):
65+
fd = os.open(tmpdir.strpath, os.O_RDONLY)
66+
j = journal.Reader(path=fd)
67+
with pytest.raises(ValueError):
68+
journal.Reader(journal.LOCAL_ONLY, path=fd)
69+
assert list(j) == []
70+
5571
def test_reader_as_cm(tmpdir):
5672
j = journal.Reader(path=tmpdir.strpath)
5773
with j:

0 commit comments

Comments
 (0)