The `True if <> else False` ternary is unnecessary and the return value
can be simplified to the bool result of checking if the chip object
is not `None`.
The underlying `Chip` object does not have an explicit `__bool__`
implementation so the check only proved that the object existed which
is preserved.
Signed-off-by: Vincent Fazio <vfazio@gmail.com>
Link: https://patch.msgid.link/20261020003728.13669-8-vfazio@gmail.com
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
The `True if <> else False` ternary is unnecessary and the return value
can be simplified to the bool result of checking if the request object
is not `None`.
The underlying `Request` object does not have an explicit `__bool__`
implementation so the check only proved that the object existed which
is preserved.
Signed-off-by: Vincent Fazio <vfazio@gmail.com>
Link: https://patch.msgid.link/20261020003728.13669-7-vfazio@gmail.com
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
Previously, a number of places in setup.py did not use a context manager
to manage the lifetime of the underlying file descriptor for reading and
writing the `libgpiod-version.txt` file.
While the previous method works, it relies on the returned file object
being assigned to a temporary Python object that has its refcount
decremented immediately after statement execution. This then relies on
the file object's finalizer to close the underlying handle which can
emit ResourceWarnings:
/bindings/python/setup.py:193: ResourceWarning: unclosed file <_io.TextIOWrapper name='libgpiod-version.txt' mode='w' encoding='UTF-8'>
open("libgpiod-version.txt", "w").write(LIBGPIOD_VERSION)
Now, we specifically use a context manager to close the file descriptor
when done to explicitly manage its lifetime.
Signed-off-by: Vincent Fazio <vfazio@gmail.com>
Link: https://patch.msgid.link/20261020003728.13669-5-vfazio@gmail.com
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
The two isinstance checks can be combined to a single call that uses a
tuple of types to check against.
When 8c9d5b4 was prototyped, it originally used a single call but it
was falsely micro-optimized to use two separate calls due to the single
call being slower.
The performance overhead was likely due to the tuple of types being
created on each iteration of the loop. Using a module variable with the
proper types avoids this overhead.
$ python3 -m timeit -s "x = 42; T = (int, str)" "isinstance(x, T)"
20000000 loops, best of 5: 13.9 nsec per loop
$ python3 -m timeit -s "x = 42" "isinstance(x, int) or isinstance(x, str)"
20000000 loops, best of 5: 14.6 nsec per loop
$ python3 -m timeit -s "x = 42" "isinstance(x, (int, str))"
10000000 loops, best of 5: 30 nsec per loop
Fixes: 8c9d5b4 ("bindings: python: make config iteration consistent")
Signed-off-by: Vincent Fazio <vfazio@gmail.com>
Link: https://patch.msgid.link/20261020003728.13669-4-vfazio@gmail.com
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
With C++ enabled, meson currently emits the following warning:
WARNING: add_languages is missing native:, assuming languages are wanted for both host and build.
We only need C++ for target executables, so make that clear in
meson.build.
Fixes: 4946370d06 ("build: do not require a C++ compiler if it is not needed")
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
Languages specified in the meson project() function are considered hard
dependencies. When trying to build libgpiod in an environment lacking a
C++ compiler, the build fails with an obscure error message ("ERROR:
Unable to get gcc pre-processor defines").
As the C++ compiler is only needed for building the C++ bindings, do not
require it unless the option bindings-cxx is enabled.
Fixes: 97bab4cc87 ("build: replace autotools with meson & ninja")
Signed-off-by: Edgar Bonet <bonet@grenoble.cnrs.fr>
Link: https://patch.msgid.link/0eb5c26d-731c-4ebb-be34-76466cf57720@grenoble.cnrs.fr
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
If both C++ bindings and the test suite are allowed but catch2 is absent,
meson setup would fail the build with error(). Follow the same *_enabled
graceful handling pattern established for all other optional components:
compute cxx_tests_enabled based on whether catch2 is actually found,
emit a warning() instead of error() when it's missing, and gate the C++
tests subdir on the new flag.
Also: enter the catch2 discovery block based on tests_enabled (instead of
opt_tests.allowed()) so it only runs once the test-suite dependencies
(threads, libgpiosim) are confirmed present.
Add a "C++ tests" line to the Bindings summary section, visible only when
C++ bindings are enabled.
Closes: https://github.com/brgl/libgpiod/discussions/190
Fixes: 97bab4cc87 ("build: replace autotools with meson & ninja")
Link: https://patch.msgid.link/20260622-build-catch2-issue-v1-1-be95e28f310b@oss.qualcomm.com
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
The top-level meson.build descends into tests/ and dbus/ only when the
computed tests_enabled and dbus_enabled gates are true, so the test
helper libraries and D-Bus executables are only defined in those cases.
The per-component subdirectories instead re-checked opt_*.allowed(),
which is true under the default 'auto' even when the dependencies are
missing, and then referenced those undefined variables - failing the
configuration outright instead of quietly disabling the component.
Gate every subdirectory on the corresponding top-level *_enabled
variable for correct and consistent behavior.
Closes: https://github.com/brgl/libgpiod/discussions/190
Fixes: 97bab4cc87 ("build: replace autotools with meson & ninja")
Link: https://patch.msgid.link/20260617-meson-enabled-checks-v1-1-2ff45c7691fe@oss.qualcomm.com
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
CHANGELOG.md must now be updated on every python bindings release just
like what we do with the top-level NEWS file. As the existing
description applies to v2.5.0, set the version now that this release is
tagged.
Fixes: 3076847a6c ("bindings: python: gpiod v2.5.0")
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
Changelog:
- add support for free-threaded (no-GIL) CPython
- generate wheels for CPython 3.14t (free-threaded)
- drop support for Python 3.9
- migrate the C extension and test modules to multi-phase module
initialization
- release the GIL in chip_get_info()
- fix heap-buffer overflow bugs when setting/getting line values
- check for errors after PyIter_Next() when setting output values
- check for errors after PyIter_Next() when reading request values
- fix reference count imbalances (missing Py_None and line-name
increments, erroneous decref on PyList_SetItem() failure)
- allow closing a Chip multiple times without raising an error
- allow releasing a LineRequest multiple times without raising an error
- remove a duplicated edge-detection setting
- add a CHANGELOG.md to track changes between releases
- copy the license file into source and binary distributions
- require setuptools v77 or newer
- remove wheel from the build dependencies
- add multi-threaded tests
- add a dedicated lint dependency group and update linter configuration
- apply linter recommendations and add type annotations across examples,
setup and helper scripts
- modernize the C extension using upgrade_pythoncapi.py suggestions
- minor internal cleanups and performance improvements
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
Since commit a62c732936 ("core: change the behavior of
gpiod_api_version()"), the libgpiod build expects the
GPIOD_API_VERSION_STR external define to contain the API version string.
The setup.py script in python bindings was not updated so it fails to
build the library sources into the generated wheel. Use the correct
define.
Fixes: a62c732936 ("core: change the behavior of gpiod_api_version()")
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
The interactive 'sleep' command stored the result of parse_period() in
an int. parse_period() returns a long long number of microseconds, so a
period above INT_MAX microseconds (~36 minutes, e.g. "sleep 40m")
overflowed: it could wrap to a negative value and be wrongly rejected as
an invalid period, or to an incorrect positive value producing the wrong
sleep duration.
Declare period_us as long long to match parse_period()'s return type.
The error check (period_us < 0) and the sleep_us() call, which takes an
unsigned long long, both remain correct. The non-interactive hold and
toggle period paths already use unsigned long long and were unaffected.
Add a test that issues "sleep 40m" at the interactive prompt and verifies
the tool starts sleeping rather than emitting an "invalid period" error
Fixes: c34a572c53 ("tools: add minutes as a new supported time unit")
Assisted-by: Claude Opus 4.8
Link: https://patch.msgid.link/20260608-more-pre-2-3-fixes-v1-5-577a5ba426a5@oss.qualcomm.com
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
parse_period() consumed an optional unit suffix ('us', 'ms', 's', 'm')
but never verified that the suffix was the end of the string. As a
result inputs with trailing garbage such as "100msx", "100usx" or
"100sx" were silently accepted and parsed as if the garbage were not
there, while inconsistently "100mx" was already rejected.
Require that the whole string be consumed by checking *end == '\0'
after the unit suffix has been processed, mirroring what parse_uint()
already does. All valid forms still leave end pointing at the
terminating NUL at that point, so only malformed input is rejected.
Add a test that passes an invalid suffix and verifies it's rejected.
Fixes: 6702eed1a5 ("tools: fix an integer overflow bug in parse_period()")
Assisted-by: Claude Opus 4.8
Link: https://patch.msgid.link/20260608-more-pre-2-3-fixes-v1-4-577a5ba426a5@oss.qualcomm.com
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
Commit 0de77e2abf ("tools: gpionotify: don't leak info returned by
gpiod_chip_watch_line_info()") moved the call to
gpiod_chip_watch_line_info() out of the condition that guarded it. The
original code relied on the short-circuit evaluation of the logical AND
to only request a watch when the line actually belonged to the chip
currently being processed:
if ((resolver->lines[j].chip_num == i) &&
!gpiod_chip_watch_line_info(chip, resolver->lines[j].offset))
die_perror(...);
After the refactor the watch is requested unconditionally for every
resolved line on every chip, using the line's offset as-is. When lines
are spread across multiple chips this installs watches at foreign
offsets on the wrong chip. Such a watch typically succeeds (the offset
usually exists on the other chip too), persists - freeing the returned
gpiod_line_info does not remove it - and makes gpionotify report
spurious info events for lines the user never asked to watch.
Restore the guard so that each line is only ever watched on the chip it
belongs to, while keeping the gpiod_line_info_free() leak fix. As a side
effect a watch failure on the correct chip is now always treated as
fatal instead of only when it coincided with a matching chip_num.
Add a test-case that requests two lines with distinct offsets on two
different chips and then toggles one line's offset on the other chip. No
event must be reported for that, as the offset is not watched there.
Fixes: 0de77e2abf ("tools: gpionotify: don't leak info returned by gpiod_chip_watch_line_info()")
Assisted-by: Claude Opus 4.8
Link: https://patch.msgid.link/20260608-more-pre-2-3-fixes-v1-3-577a5ba426a5@oss.qualcomm.com
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
gpiod_edge_event_buffer_read_fd() is documented to read at most
max_events events and to return the number of events read. When called
with max_events == 0 it nonetheless issued a read() for 0 bytes, which
returns 0, and the subsequent short-read check
((unsigned int)rd < sizeof(*event_data)) then treated this as an I/O
error, setting errno to EIO and returning -1.
Requesting zero events is not an error - it is a no-op that reads
nothing. Handle it explicitly by clearing num_events and returning 0
before attempting the read.
Add a test case verifying the behavior.
Fixes: b7ba732e6a ("treewide: libgpiod v2 implementation")
Assisted-by: Claude Opus 4.8
Link: https://patch.msgid.link/20260608-more-pre-2-3-fixes-v1-2-577a5ba426a5@oss.qualcomm.com
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
When releasing a no-longer-referenced settings node in
gpiod_line_config_add_line_settings(), the embedded settings object was
freed with a raw free() instead of gpiod_line_settings_free(), unlike
every other release site (e.g. free_refs()).
This is currently harmless because gpiod_line_settings_free() is just a
free(), but bypassing the type's destructor is a latent leak should the
struct ever gain heap-allocated members. Use the proper destructor for
consistency.
Fixes: 2560a857ce ("core: keep memory usage of struct line_config in check")
Assisted-by: Claude Opus 4.8
Link: https://patch.msgid.link/20260608-more-pre-2-3-fixes-v1-1-577a5ba426a5@oss.qualcomm.com
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
The Event struct wraps a single *mut gpiod_edge_event pointer and is cast
from raw pointers in event_buffer.rs. Without #[repr(transparent)], the
layout compatibility is not guaranteed by the language specification.
Add #[repr(transparent)] to make the pointer cast sound, and replace
try_into().unwrap() with a simple 'as' cast for the sequence number
getters since c_ulong and usize are the same width on Linux.
Fixes: 91f9373c65 ("bindings: rust: Add libgpiod crate")
Assisted-by: Claude Opus 4.6
Reviewed-by: Vincent Fazio <vfazio@xes-inc.com>
Tested-by: Vincent Fazio <vfazio@xes-inc.com>
Link: https://patch.msgid.link/20260604-pre-2-3-fixes-v2-11-781bcaf47475@oss.qualcomm.com
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
chip::Info already implements PartialEq and PartialOrd (both based on
comparing chip names as UTF-8 strings), but not their total-order
counterparts Eq and Ord. The omission is inconsistent: types with a
total order should implement Ord so callers can use the ergonomic sort()
/ min() / max() APIs.
Add the missing impls. Ord::cmp() delegates to partial_cmp() and falls
back to Ordering::Equal for the unreachable case where a chip name is
not valid UTF-8. Update gpiochip_devices() to use cmp() directly now
that Ord is available.
Assisted-by: Claude Sonnet 4.6
Acked-by: Vincent Fazio <vfazio@xes-inc.com>
Reviewed-by: Vincent Fazio <vfazio@xes-inc.com>
Tested-by: Vincent Fazio <vfazio@xes-inc.com>
Link: https://patch.msgid.link/20260604-pre-2-3-fixes-v2-10-781bcaf47475@oss.qualcomm.com
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
The direction() method correctly uses get_mapped_value() which converts
std::out_of_range to the project's bad_mapping exception, but bias(),
drive(), edge_detection(), and event_clock() call .at() directly. This
causes them to throw std::out_of_range instead of the expected
bad_mapping exception on unexpected values.
Use get_mapped_value() consistently for all enum mapping lookups.
Fixes: 78020eae9d ("bindings: cxx: remove duplicate mapping helper")
Assisted-by: Claude Opus 4.6
Reviewed-by: Vincent Fazio <vfazio@xes-inc.com>
Tested-by: Vincent Fazio <vfazio@xes-inc.com>
Link: https://patch.msgid.link/20260604-pre-2-3-fixes-v2-8-781bcaf47475@oss.qualcomm.com
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
Every other tool (gpioinfo, gpiomon, gpioset …) uses the value returned
by parse_config() to advance argc/argv past the parsed options:
i = parse_config(argc, argv, &cfg);
argc -= i;
argv += i;
gpionotify was directly using the global optind variable instead of i,
which is equivalent today because parse_config() returns optind, but is
inconsistent and would silently break if the function were ever changed
to return something else.
Assisted-by: Claude Sonnet 4.6
Acked-by: Vincent Fazio <vfazio@xes-inc.com>
Reviewed-by: Vincent Fazio <vfazio@xes-inc.com>
Tested-by: Vincent Fazio <vfazio@xes-inc.com>
Link: https://patch.msgid.link/20260604-pre-2-3-fixes-v2-4-781bcaf47475@oss.qualcomm.com
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
Both gpiod_chip_info_from_uapi() and gpiod_line_info_from_uapi() copy
kernel UAPI name strings with strncpy() into buffers that are one byte
larger than GPIO_MAX_NAME_SIZE. If the source string is exactly
GPIO_MAX_NAME_SIZE bytes long strncpy() will not append a NUL, so the
strings are only guaranteed to be terminated because the structs are
zeroed with memset() beforehand.
Make the termination unconditional and independent of the zeroing by
writing the NUL byte explicitly after each strncpy() call.
Fixes: c040d4e15e ("core: fix 1-byte buffer over-read bugs in gpiod_chip_info_from_uapi()")
Fixes: 5533f277aa ("core: fix line-info strings termination")
Assisted-by: Claude Sonnet 4.6
Acked-by: Vincent Fazio <vfazio@xes-inc.com>
Reviewed-by: Vincent Fazio <vfazio@xes-inc.com>
Tested-by: Vincent Fazio <vfazio@xes-inc.com>
Link: https://patch.msgid.link/20260604-pre-2-3-fixes-v2-3-781bcaf47475@oss.qualcomm.com
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
The local variable used to store the return value of
gpiod_line_request_get_values_subset() was declared as unsigned int
while the function returns int (-1 on error). The check for the error
condition worked by accident because assigning -1 to an unsigned type
produces UINT_MAX, which is non-zero, but using the wrong type is
misleading and technically invoking implementation-defined behaviour.
Use int throughout.
Fixes: b7ba732e6a ("treewide: libgpiod v2 implementation")
Assisted-by: Claude Sonnet 4.6
Acked-by: Vincent Fazio <vfazio@xes-inc.com>
Reviewed-by: Vincent Fazio <vfazio@xes-inc.com>
Tested-by: Vincent Fazio <vfazio@xes-inc.com>
Link: https://patch.msgid.link/20260604-pre-2-3-fixes-v2-1-781bcaf47475@oss.qualcomm.com
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
We have some .txt files that contain SPDX headers to make `reuse lint`
happy. Let's move them into REUSE.toml instead for better readability of
their content.
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
It's useful to know which options ended up really being selected by
meson (for instance for --auto-features) so use the dedicated meson
function to display the summary.
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
gpiod_line_config_add_line_settings() checks the remaining capacity
using the raw num_offsets from the caller. If the config is already at
the 64-line limit, any call - even one that would only update settings
for already configured offsets - would fail with E2BIG making it
impossible to reconfigure a line once the config is full.
Extend gpiod_line_config_add_line_settings() to only count offsets that
would require a new config slot and use this number to check if the
line-config object still has capacity.
Add a test case allowing to verify the fix.
Fixes: b7ba732e6a ("treewide: libgpiod v2 implementation")
Link: https://patch.msgid.link/20260522-line-config-full-config-bug-v1-1-b138958bf05d@oss.qualcomm.com
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>