Source code for parted.constraint

# Copyright (c) 2023 Adolfo Gómez
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

"""
This module contains the Constraint class, which is used to specify
the constraints on a Partition.

:author: Adolfo Gómez, dkmaster at dkmon dot com
"""
import logging
import enum
import typing

from . import _parted  # type: ignore
from . import alignment, device, exceptions, geom
from .util import ensure_obj, make_destroyable

if typing.TYPE_CHECKING:
    import cffi

logger = logging.getLogger(__name__)

[docs]class Constraint: """Wrapper for PedConstraint object This class represents a constraint on a partition. A constraint is a set of rules that a partition must follow. A constraint is composes of: - start_align: the alignment of the start of the partition - end_align: the alignment of the end of the partition - start_range: the range of the start of the partition - end_range: the range of the end of the partition - min_size: the minimum size of the partition - max_size: the maximum size of the partition From parted documentation:: Constraints are used to communicate restrictions on operations Constraints are restrictions on the location and alignment of the start and end of a partition, and the minimum and maximum size. Constraints are closed under intersection (for the proof see the source code). For background information see the Chinese Remainder Theorem. This interface consists of construction constraints, finding the intersection of constraints, and finding solutions to constraints. The constraint solver allows you to specify constraints on where a partition or file system (or any PedGeometry) may be placed/resized/etc. For example, you might want to make sure that a file system is at least 10 Gb, or that it starts at the beginning of new cylinder. """ _constraint: typing.Any = None _destroyable: bool = True def __init__( self, constraint: typing.Optional['cffi.FFI.CData'] = None, ) -> None: """Creates a new constraint object Args: constraint (cffi.FFI.CData, optional): Underlying PedConstraint object. Defaults to None. """ self._destroyable = False self._constraint = constraint if constraint else _parted.ffi.NULL def __del__(self) -> None: # Note: "init" method of library is not supported (nor needed in fact) # so we can safely destroy the constraint if destroyable if self._destroyable and self._constraint: _parted.lib.ped_constraint_destroy(self._constraint) def __bool__(self) -> bool: """Returns True if the constraint is valid, False otherwise Returns: bool: True if the constraint is valid, False otherwise """ return bool(self._constraint) @property def obj(self) -> 'cffi.FFI.CData': """Wrapped ``PedConstraint*`` object""" return self._constraint @property def start_align(self) -> 'alignment.Alignment': """Returns the alignment of the start of the region. Returns: alignment.Alignment: The alignment of the start of the region. """ if not self._constraint: return alignment.Alignment.none() return alignment.Alignment(self._constraint.start_align) @property def end_align(self) -> 'alignment.Alignment': """Returns the alignment of the end of the region. Returns: alignment.Alignment: The alignment of the end of the region. """ if not self._constraint: return alignment.Alignment.none() return alignment.Alignment(self._constraint.end_align) @property def start_range(self) -> 'geom.Geometry': """Returns the start range of the constraint. Returns: geom.Geometry: The start range of the constraint. """ if not self._constraint: return geom.Geometry(_parted.ffi.NULL) return geom.Geometry(self._constraint.start_range) @property def end_range(self) -> 'geom.Geometry': """Returns the end range of the constraint. Returns: geom.Geometry: The end range of the constraint. """ if not self._constraint: return geom.Geometry(_parted.ffi.NULL) return geom.Geometry(self._constraint.end_range) @property def min_size(self) -> int: """Minimum size of the partition in bytes Returns: int: minimum size of the partition in bytes """ if not self._constraint: return 0 return self._constraint.min_size @property def max_size(self) -> int: """The max size of the constraint Returns: int: The max size of the constraint """ if not self._constraint: return 0 return self._constraint.max_size
[docs] @ensure_obj def duplicate(self) -> 'Constraint': """Returns a new constraint that is a duplicate of this constraint. Returns: Constraint: A new constraint that is a duplicate of this constraint. Raises: exceptions.InvalidObjectError: if self is not a valid constraint """ return make_destroyable(Constraint(_parted.lib.ped_constraint_duplicate(self._constraint)))
[docs] @ensure_obj def intersect(self, constraint: 'Constraint') -> 'Constraint': """Returns a new constraint that is the intersection of this constraint and the given constraint. The intersection of two constraints is the largest constraint that is contained by both of them. This is calculated: - intersection of start_alignments. If no intersection, returns empty constraint - intersection of end_alignments. If no intersection, returns empty constraint - intersection of start_ranges. If no intersection, returns empty constraint - intersection of end_ranges. If no intersection, returns empty constraint - max of min_sizes - min of max_sizes Args: constraint (Constraint): The constraint to intersect with this constraint. Returns: Constraint: The intersection of this constraint and the given constraint. Raises: exceptions.InvalidObjectError: if self is not a valid constraint """ return make_destroyable(Constraint(_parted.lib.ped_constraint_intersect(self._constraint, constraint.obj)))
[docs] @ensure_obj def is_solution(self, geom: 'geom.Geometry') -> bool: """Check whether geom satisfies the given constraint. Will satisfied it if: - geom.start is aligned to start_align - geom.end is aligned to end_align - geom.start is in start_range - geom.end is in end_range - geom.length is in the range [min_size, max_size] Args: geom (geom.Geometry): The geometry to check Returns: bool: True if geom satisfies the given constraint, False otherwise Raises: exceptions.InvalidObjectError: if self is not a valid constraint """ return bool(_parted.lib.ped_constraint_is_solution(self._constraint, geom.obj))
[docs] @ensure_obj def solve_max(self) -> 'geom.Geometry': """Find the largest region that satisfies a constraint. Returns: geom.Geometry: The largest region that satisfies the constraint. Raises: exceptions.InvalidObjectError: if self is not a valid constraint """ return geom.Geometry(_parted.lib.ped_constraint_solve_max(self._constraint))
[docs] @ensure_obj def solve_nearest(self, geometry: 'geom.Geometry') -> 'geom.Geometry': """Return the nearest region to geometry that satisfy a constraint. This is the region that, if geometry is moved to it, geometry will be as close as possible to the original geometry, and still satisfy the constraint. Args: geometry (geom.Geometry): The geometry to solve Returns: geom.Geometry: The nearest geometry that satisfies the constraint Raises: exceptions.InvalidObjectError: if self is not a valid constraint """ return geom.Geometry(_parted.lib.ped_constraint_solve_nearest(self._constraint, geometry.obj))
[docs] @staticmethod def any(dev: 'device.Device') -> 'Constraint': """Return a constraint that any region on the given device will satisfy. - start_align = aligment.Alignment.any() - end_align = aligment.Alignment.any() - start_range = geom.Geometry(0, dev.length) - end_range = geom.Geometry(0, dev.length) - min_size = 1 - max_size = dev.length Args: dev (device.Device): The device to create the constraint for Returns: Constraint: A constraint that any region on the given device will satisfy. """ if not dev: return Constraint() return make_destroyable(Constraint(_parted.lib.ped_constraint_any(dev.obj)))
[docs] @staticmethod def exact(geom: 'geom.Geometry') -> 'Constraint': """Return a constraint that only the given region will satisfy. - start_align = alignment.Alignment(geom.start, 0) - end_align = alignment.Alignment(geom.end, 0) - start_range = geom.Geometry(geom.start, 1) - end_range = geom.Geometry(geom.end, 1) Args: geom (geom.Geometry): The geometry to constrain to. Returns: Constraint: A constraint that only the given region will satisfy. """ return make_destroyable(Constraint(_parted.lib.ped_constraint_exact(geom.obj)))
[docs] @staticmethod def align(dev: 'device.Device', align: int) -> 'Constraint': """Return a constraint that will enforce an alignment on the start and end of a region. - start_align = alignment.Alignment(boundary, 0) - end_align = alignment.Alignment(boundary, 0) - start_range = geom.Geometry(0, dev.length) - end_range = geom.Geometry(0, dev.length) Args: dev (device.Device): The device to create the constraint for alignment (int): The alignment boundary in Kbytes. will be rounded up to the nearest multiple of dev.sector_size Returns: Constraint: A constraint that will enforce an alignment on the start and end of a region. Examples: >>> import parted >>> dev = parted.getDevice('/dev/sda') >>> constraint = parted.Constraint.bound(dev, 1<<10) # 1 MiB """ # Ensure boundary is multiple of dev.sector_size, and convert to sectors align = (align * 1024 + dev.sector_size - 1) // dev.sector_size return Constraint.new( start_align=alignment.Alignment(0, align), end_align=alignment.Alignment(-1, align), start_range=geom.Geometry(dev, 0, dev.length), end_range=geom.Geometry(dev, 0, dev.length), min_size_sector = align, max_size_sector = dev.length )
[docs] @staticmethod def new( start_align: 'alignment.Alignment', end_align: 'alignment.Alignment', start_range: 'geom.Geometry', end_range: 'geom.Geometry', min_size_sector: int, max_size_sector: int, ) -> 'Constraint': """Create a new constraint. Args: start_align (alignment.Alignment): The alignment of the start of the region. end_align (alignment.Alignment): The alignment of the end of the region. start_range (geom.Geometry): The range of the start of the region. end_range (geom.Geometry): The range of the end of the region. min_size (int): The minimum size of the region. max_size (int): The maximum size of the region. Returns: Constraint: A newly created contraint object. """ # Sanity checks if min_size_sector <= 0: raise exceptions.PartedException('Invalid min_size (must be > 0)') if max_size_sector <= 0: raise exceptions.PartedException('Invalid max_size (must be > 0)') return make_destroyable( Constraint( _parted.lib.ped_constraint_new( start_align.obj, end_align.obj, start_range.obj, end_range.obj, min_size_sector, max_size_sector, ) ) )
[docs] @staticmethod def new_from_min(min: 'geom.Geometry') -> 'Constraint': """Return a constraint that requires a region to entirely contain min. Args: min (geom.Geometry): The geometry to constrain to. Returns: Constraint: A constraint that requires a region to entirely contain min. """ return make_destroyable(Constraint(_parted.lib.ped_constraint_new_from_min(min.obj)))
[docs] @staticmethod def new_from_min_max(min: 'geom.Geometry', max: 'geom.Geometry') -> 'Constraint': """Return a constraint that requires a region to be entirely contained inside max, and to entirely contain min. Args: min (geom.Geometry): The minimum geometry to constrain to. max (geom.Geometry): The maximum geometry to constrain to. Returns: Constraint: The constraint. """ # if min is not inside max, raise exceptions.PartedException if not min in max: raise exceptions.PartedException("min is not inside max, min: {}, max: {}".format(min, max)) return make_destroyable( Constraint( _parted.lib.ped_constraint_new_from_min_max(min.obj, max.obj), ) )
[docs] @staticmethod def new_from_max(max: 'geom.Geometry') -> 'Constraint': """Return a constraint that requires a region to be entirely contained inside max. Args: max (geom.Geometry): The geometry to constrain to. Returns: Constraint: The new created constraint. """ return make_destroyable(Constraint(_parted.lib.ped_constraint_new_from_max(max.obj)))
def __str__(self) -> str: return 'Constraint({}, {}, {}, {}, {}, {})'.format( self.start_align, self.end_align, self.start_range, self.end_range, self.min_size, self.max_size ) def __repr__(self) -> str: return self.__str__()