Skip to content

Commit d1117bd

Browse files
committed
Initial commit
0 parents  commit d1117bd

File tree

10 files changed

+523
-0
lines changed

10 files changed

+523
-0
lines changed

.gitignore

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
2+
# Created by https://www.gitignore.io/api/python
3+
4+
### Python ###
5+
# Byte-compiled / optimized / DLL files
6+
__pycache__/
7+
*.py[cod]
8+
*$py.class
9+
10+
# C extensions
11+
*.so
12+
13+
# Distribution / packaging
14+
.Python
15+
env/
16+
build/
17+
develop-eggs/
18+
dist/
19+
downloads/
20+
eggs/
21+
.eggs/
22+
lib/
23+
lib64/
24+
parts/
25+
sdist/
26+
var/
27+
*.egg-info/
28+
.installed.cfg
29+
*.egg
30+
31+
# PyInstaller
32+
# Usually these files are written by a python script from a template
33+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
34+
*.manifest
35+
*.spec
36+
37+
# Installer logs
38+
pip-log.txt
39+
pip-delete-this-directory.txt
40+
41+
# Unit test / coverage reports
42+
htmlcov/
43+
.tox/
44+
.coverage
45+
.coverage.*
46+
.cache
47+
nosetests.xml
48+
coverage.xml
49+
*,cover
50+
.hypothesis/
51+
52+
# Translations
53+
*.mo
54+
*.pot
55+
56+
# Django stuff:
57+
*.log
58+
local_settings.py
59+
60+
# Flask stuff:
61+
instance/
62+
.webassets-cache
63+
64+
# Scrapy stuff:
65+
.scrapy
66+
67+
# Sphinx documentation
68+
docs/_build/
69+
70+
# PyBuilder
71+
target/
72+
73+
# Jupyter Notebook
74+
.ipynb_checkpoints
75+
76+
# pyenv
77+
.python-version
78+
79+
# celery beat schedule file
80+
celerybeat-schedule
81+
82+
# dotenv
83+
.env
84+
85+
# virtualenv
86+
.venv/
87+
venv/
88+
ENV/
89+
90+
# Spyder project settings
91+
.spyderproject
92+
93+
# Rope project settings
94+
.ropeproject

LICENSE

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
BSD 2-Clause License
2+
3+
Copyright (c) 2016, Anatolii Aniskovych
4+
All rights reserved.
5+
6+
Redistribution and use in source and binary forms, with or without
7+
modification, are permitted provided that the following conditions are met:
8+
9+
* Redistributions of source code must retain the above copyright notice, this
10+
list of conditions and the following disclaimer.
11+
12+
* Redistributions in binary form must reproduce the above copyright notice,
13+
this list of conditions and the following disclaimer in the documentation
14+
and/or other materials provided with the distribution.
15+
16+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
20+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
22+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
23+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

README.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
===============
2+
Python getdents
3+
===============
4+
5+
TBD

getdents/__init__.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import os
2+
3+
from ._getdents import ( # noqa: ignore=F401
4+
DT_BLK,
5+
DT_CHR,
6+
DT_DIR,
7+
DT_FIFO,
8+
DT_LNK,
9+
DT_REG,
10+
DT_SOCK,
11+
DT_UNKNOWN,
12+
MIN_GETDENTS_BUFF_SIZE,
13+
O_GETDENTS,
14+
getdents_raw,
15+
)
16+
17+
18+
def getdents(path, buff_size=32768, close_fd=False):
19+
if hasattr(path, 'fileno'):
20+
fd = path.fileno()
21+
elif isinstance(path, str):
22+
fd = os.open(path, O_GETDENTS)
23+
close_fd = True
24+
elif isinstance(path, int):
25+
fd = path
26+
else:
27+
raise TypeError('Unsupported type: %s', type(path))
28+
29+
try:
30+
yield from (
31+
(inode, type, name)
32+
for inode, type, name in getdents_raw(fd, buff_size)
33+
if not(type == DT_UNKNOWN or inode == 0 or name in ('.', '..'))
34+
)
35+
finally:
36+
if close_fd:
37+
os.close(fd)

getdents/_getdents.c

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
#include <Python.h>
2+
#include <dirent.h>
3+
#include <fcntl.h>
4+
#include <stddef.h>
5+
#include <stdbool.h>
6+
#include <stdio.h>
7+
#include <unistd.h>
8+
#include <stdlib.h>
9+
#include <sys/stat.h>
10+
#include <sys/syscall.h>
11+
12+
struct linux_dirent64 {
13+
uint64_t d_ino;
14+
int64_t d_off;
15+
unsigned short d_reclen;
16+
unsigned char d_type;
17+
char d_name[];
18+
};
19+
20+
struct getdents_state {
21+
PyObject_HEAD
22+
char *buff;
23+
int bpos;
24+
int fd;
25+
int nread;
26+
size_t buff_size;
27+
bool ready_for_next_batch;
28+
};
29+
30+
31+
#ifndef O_GETDENTS
32+
# define O_GETDENTS (O_DIRECTORY | O_RDONLY | O_NONBLOCK | O_CLOEXEC)
33+
#endif
34+
35+
#ifndef MIN_GETDENTS_BUFF_SIZE
36+
# define MIN_GETDENTS_BUFF_SIZE (MAXNAMLEN + sizeof(struct linux_dirent64))
37+
#endif
38+
39+
static PyObject *
40+
getdents_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
41+
{
42+
size_t buff_size;
43+
int fd;
44+
45+
if (!PyArg_ParseTuple(args, "in", &fd, &buff_size))
46+
return NULL;
47+
48+
if (!(fcntl(fd, F_GETFL) & O_DIRECTORY)) {
49+
PyErr_SetString(
50+
PyExc_NotADirectoryError,
51+
"fd must be opened with O_DIRECTORY flag"
52+
);
53+
return NULL;
54+
}
55+
56+
if (buff_size < MAXNAMLEN + sizeof(struct linux_dirent64)) {
57+
PyErr_SetString(
58+
PyExc_ValueError,
59+
"buff_size is too small"
60+
);
61+
return NULL;
62+
}
63+
64+
struct getdents_state *state = (void *) type->tp_alloc(type, 0);
65+
66+
if (!state)
67+
return NULL;
68+
69+
void *buff = malloc(buff_size);
70+
71+
if (!buff)
72+
return PyErr_NoMemory();
73+
74+
state->buff = buff;
75+
state->buff_size = buff_size;
76+
state->fd = fd;
77+
state->bpos = 0;
78+
state->nread = 0;
79+
state->ready_for_next_batch = true;
80+
return (PyObject *) state;
81+
}
82+
83+
static void
84+
getdents_dealloc(struct getdents_state *state)
85+
{
86+
free(state->buff);
87+
Py_TYPE(state)->tp_free(state);
88+
}
89+
90+
static PyObject *
91+
getdents_next(struct getdents_state *s)
92+
{
93+
s->ready_for_next_batch = s->bpos >= s->nread;
94+
95+
if (s->ready_for_next_batch) {
96+
s->bpos = 0;
97+
s->nread = syscall(SYS_getdents64, s->fd, s->buff, s->buff_size);
98+
99+
if (s->nread == 0)
100+
return NULL;
101+
102+
if (s->nread == -1) {
103+
PyErr_SetString(PyExc_OSError, "getdents64");
104+
return NULL;
105+
}
106+
}
107+
108+
struct linux_dirent64 *d = (struct linux_dirent64 *)(s->buff + s->bpos);
109+
110+
PyObject *py_name = PyUnicode_DecodeFSDefault(d->d_name);
111+
112+
PyObject *result = Py_BuildValue("KbO", d->d_ino, d->d_type, py_name);
113+
114+
s->bpos += d->d_reclen;
115+
116+
return result;
117+
}
118+
119+
PyTypeObject getdents_type = {
120+
PyVarObject_HEAD_INIT(NULL, 0)
121+
"getdents_raw", /* tp_name */
122+
sizeof(struct getdents_state), /* tp_basicsize */
123+
0, /* tp_itemsize */
124+
(destructor) getdents_dealloc, /* tp_dealloc */
125+
0, /* tp_print */
126+
0, /* tp_getattr */
127+
0, /* tp_setattr */
128+
0, /* tp_reserved */
129+
0, /* tp_repr */
130+
0, /* tp_as_number */
131+
0, /* tp_as_sequence */
132+
0, /* tp_as_mapping */
133+
0, /* tp_hash */
134+
0, /* tp_call */
135+
0, /* tp_str */
136+
0, /* tp_getattro */
137+
0, /* tp_setattro */
138+
0, /* tp_as_buffer */
139+
Py_TPFLAGS_DEFAULT, /* tp_flags */
140+
0, /* tp_doc */
141+
0, /* tp_traverse */
142+
0, /* tp_clear */
143+
0, /* tp_richcompare */
144+
0, /* tp_weaklistoffset */
145+
PyObject_SelfIter, /* tp_iter */
146+
(iternextfunc) getdents_next, /* tp_iternext */
147+
0, /* tp_methods */
148+
0, /* tp_members */
149+
0, /* tp_getset */
150+
0, /* tp_base */
151+
0, /* tp_dict */
152+
0, /* tp_descr_get */
153+
0, /* tp_descr_set */
154+
0, /* tp_dictoffset */
155+
0, /* tp_init */
156+
PyType_GenericAlloc, /* tp_alloc */
157+
getdents_new, /* tp_new */
158+
};
159+
160+
static struct PyModuleDef getdents_module = {
161+
PyModuleDef_HEAD_INIT,
162+
"getdents", /* m_name */
163+
"", /* m_doc */
164+
-1, /* m_size */
165+
};
166+
167+
PyMODINIT_FUNC
168+
PyInit__getdents(void)
169+
{
170+
if (PyType_Ready(&getdents_type) < 0)
171+
return NULL;
172+
173+
PyObject *module = PyModule_Create(&getdents_module);
174+
175+
if (!module)
176+
return NULL;
177+
178+
Py_INCREF(&getdents_type);
179+
PyModule_AddObject(module, "getdents_raw", (PyObject *) &getdents_type);
180+
PyModule_AddIntMacro(module, DT_BLK);
181+
PyModule_AddIntMacro(module, DT_CHR);
182+
PyModule_AddIntMacro(module, DT_DIR);
183+
PyModule_AddIntMacro(module, DT_FIFO);
184+
PyModule_AddIntMacro(module, DT_LNK);
185+
PyModule_AddIntMacro(module, DT_REG);
186+
PyModule_AddIntMacro(module, DT_SOCK);
187+
PyModule_AddIntMacro(module, DT_UNKNOWN);
188+
PyModule_AddIntMacro(module, O_GETDENTS);
189+
PyModule_AddIntMacro(module, MIN_GETDENTS_BUFF_SIZE);
190+
return module;
191+
}

pytest.ini

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[pytest]
2+
testpaths = tests

setup.cfg

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[aliases]
2+
test=pytest

setup.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#!/usr/bin/env python
2+
3+
from setuptools import setup
4+
5+
from distutils.core import Extension
6+
7+
8+
setup(
9+
name='getdents',
10+
version='0.1',
11+
description='Python binding to linux syscall getdents64.',
12+
long_description=open('README.rst').read(),
13+
classifiers=[
14+
'License :: OSI Approved :: BSD License',
15+
'Operating System :: POSIX :: Linux',
16+
'Programming Language :: Python :: 3',
17+
'Programming Language :: Python :: Implementation :: CPython',
18+
'Topic :: System :: Filesystems',
19+
],
20+
keywords='getdents',
21+
author='Anatolii Aniskovych',
22+
author_email='[email protected]',
23+
url='http://github.com/ZipFile/python-getdents',
24+
license='BSD-2-Clause',
25+
packages=['getdents'],
26+
include_package_data=True,
27+
zip_safe=False,
28+
ext_modules = [
29+
Extension('getdents._getdents', sources=['getdents/_getdents.c']),
30+
],
31+
install_requires=[
32+
'setuptools',
33+
],
34+
setup_requires=['pytest-runner'],
35+
tests_require=['pytest'],
36+
)

0 commit comments

Comments
 (0)