util/linux: add explicit FS_IMMUTABLE_FL helpers
The FS_IOC_{GET,SET}FLAGS ioctl numbers are not stable across different
architectures. Most of them use the asm-generic versions, but ALPHA and
SPARC in particular use completely different IOC number setups (see the
definition of _IOC, _IOR, _IOW, etc. in the kernel).
This commit moves the helpers for `FS_IMMUTABLE_FL` into
`osbuild/util/` and adds explicit tests. This will make sure that we
catch any ioctl mismatches as soon as possible when we run the osbuild
test-suite on other architectures. Until then, we will have to live with
this mismatch.
This commit is contained in:
parent
2cc9160099
commit
e5ff287f5a
2 changed files with 165 additions and 0 deletions
93
osbuild/util/linux.py
Normal file
93
osbuild/util/linux.py
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
"""Linux API Access
|
||||||
|
|
||||||
|
This module provides access to linux system-calls and other APIs, in particular
|
||||||
|
those not provided by the python standard library. The idea is to provide
|
||||||
|
universal wrappers with broad access to linux APIs. Convenience helpers and
|
||||||
|
higher-level abstractions are beyond the scope of this module.
|
||||||
|
|
||||||
|
In some cases it is overly complex to provide universal access to a specifc
|
||||||
|
API. Hence, the API might be restricted to a reduced subset of its
|
||||||
|
functionality, just to make sure we can actually implement the wrappers in a
|
||||||
|
reasonable manner.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
import array
|
||||||
|
import fcntl
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"ioctl_get_immutable",
|
||||||
|
"ioctl_toggle_immutable",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# NOTE: These are wrong on at least ALPHA and SPARC. They use different
|
||||||
|
# ioctl number setups. We should fix this, but this is really awkward
|
||||||
|
# in standard python.
|
||||||
|
# Our tests will catch this, so we will not accidentally run into this
|
||||||
|
# on those architectures.
|
||||||
|
FS_IOC_GETFLAGS = 0x80086601
|
||||||
|
FS_IOC_SETFLAGS = 0x40086602
|
||||||
|
|
||||||
|
FS_IMMUTABLE_FL = 0x00000010
|
||||||
|
|
||||||
|
|
||||||
|
def ioctl_get_immutable(fd: int):
|
||||||
|
"""Query FS_IMMUTABLE_FL
|
||||||
|
|
||||||
|
This queries the `FS_IMMUTABLE_FL` flag on a specified file.
|
||||||
|
|
||||||
|
Arguments
|
||||||
|
---------
|
||||||
|
fd
|
||||||
|
File-descriptor to operate on.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
bool
|
||||||
|
Whether the `FS_IMMUTABLE_FL` flag is set or not.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
------
|
||||||
|
OSError
|
||||||
|
If the underlying ioctl fails, a matching `OSError` will be raised.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not isinstance(fd, int) or fd < 0:
|
||||||
|
raise ValueError()
|
||||||
|
|
||||||
|
flags = array.array('L', [0])
|
||||||
|
fcntl.ioctl(fd, FS_IOC_GETFLAGS, flags, True)
|
||||||
|
return bool(flags[0] & FS_IMMUTABLE_FL)
|
||||||
|
|
||||||
|
|
||||||
|
def ioctl_toggle_immutable(fd: int, set_to: bool):
|
||||||
|
"""Toggle FS_IMMUTABLE_FL
|
||||||
|
|
||||||
|
This toggles the `FS_IMMUTABLE_FL` flag on a specified file. It can both set
|
||||||
|
and clear the flag.
|
||||||
|
|
||||||
|
Arguments
|
||||||
|
---------
|
||||||
|
fd
|
||||||
|
File-descriptor to operate on.
|
||||||
|
set_to
|
||||||
|
Whether to set the `FS_IMMUTABLE_FL` flag or not.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
------
|
||||||
|
OSError
|
||||||
|
If the underlying ioctl fails, a matching `OSError` will be raised.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not isinstance(fd, int) or fd < 0:
|
||||||
|
raise ValueError()
|
||||||
|
|
||||||
|
flags = array.array('L', [0])
|
||||||
|
fcntl.ioctl(fd, FS_IOC_GETFLAGS, flags, True)
|
||||||
|
if set_to:
|
||||||
|
flags[0] |= FS_IMMUTABLE_FL
|
||||||
|
else:
|
||||||
|
flags[0] &= ~FS_IMMUTABLE_FL
|
||||||
|
fcntl.ioctl(fd, FS_IOC_SETFLAGS, flags, False)
|
||||||
72
test/test_util_linux.py
Normal file
72
test/test_util_linux.py
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
#
|
||||||
|
# Tests for the `osbuild.util.linux` module.
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
import osbuild.util.linux as linux
|
||||||
|
|
||||||
|
|
||||||
|
class TestUtilLinux(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.vartmpdir = tempfile.TemporaryDirectory(dir="/var/tmp")
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
self.vartmpdir.cleanup()
|
||||||
|
|
||||||
|
def test_ioctl_get_immutable(self):
|
||||||
|
#
|
||||||
|
# Test the `ioctl_get_immutable()` helper and make sure it works
|
||||||
|
# as intended.
|
||||||
|
#
|
||||||
|
|
||||||
|
with open(f"{self.vartmpdir.name}/immutable", "x") as f:
|
||||||
|
assert not linux.ioctl_get_immutable(f.fileno())
|
||||||
|
|
||||||
|
@unittest.skipUnless(os.geteuid() == 0, "root-only")
|
||||||
|
def test_ioctl_toggle_immutable(self):
|
||||||
|
#
|
||||||
|
# Test the `ioctl_toggle_immutable()` helper and make sure it works
|
||||||
|
# as intended.
|
||||||
|
#
|
||||||
|
|
||||||
|
with open(f"{self.vartmpdir.name}/immutable", "x") as f:
|
||||||
|
# Check the file is mutable by default and if we clear it again.
|
||||||
|
assert not linux.ioctl_get_immutable(f.fileno())
|
||||||
|
linux.ioctl_toggle_immutable(f.fileno(), False)
|
||||||
|
assert not linux.ioctl_get_immutable(f.fileno())
|
||||||
|
|
||||||
|
# Set immutable and check for it. Try again to verify with flag set.
|
||||||
|
linux.ioctl_toggle_immutable(f.fileno(), True)
|
||||||
|
assert linux.ioctl_get_immutable(f.fileno())
|
||||||
|
linux.ioctl_toggle_immutable(f.fileno(), True)
|
||||||
|
assert linux.ioctl_get_immutable(f.fileno())
|
||||||
|
|
||||||
|
# Verify immutable files cannot be unlinked.
|
||||||
|
with self.assertRaises(OSError):
|
||||||
|
os.unlink(f"{self.vartmpdir.name}/immutable")
|
||||||
|
|
||||||
|
# Check again that clearing the flag works.
|
||||||
|
linux.ioctl_toggle_immutable(f.fileno(), False)
|
||||||
|
assert not linux.ioctl_get_immutable(f.fileno())
|
||||||
|
|
||||||
|
# This time, check that we actually set the same flag as `chattr`.
|
||||||
|
subprocess.run(["chattr", "+i",
|
||||||
|
f"{self.vartmpdir.name}/immutable"], check=True)
|
||||||
|
assert linux.ioctl_get_immutable(f.fileno())
|
||||||
|
|
||||||
|
# Same for clearing it.
|
||||||
|
subprocess.run(["chattr", "-i",
|
||||||
|
f"{self.vartmpdir.name}/immutable"], check=True)
|
||||||
|
assert not linux.ioctl_get_immutable(f.fileno())
|
||||||
|
|
||||||
|
# Verify we can unlink the file again, once the flag is cleared.
|
||||||
|
os.unlink(f"{self.vartmpdir.name}/immutable")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Loading…
Add table
Add a link
Reference in a new issue