summaryrefslogtreecommitdiffstats
path: root/libparted/cs/natmath.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--libparted/cs/natmath.c481
1 files changed, 481 insertions, 0 deletions
diff --git a/libparted/cs/natmath.c b/libparted/cs/natmath.c
new file mode 100644
index 0000000..ea53afc
--- /dev/null
+++ b/libparted/cs/natmath.c
@@ -0,0 +1,481 @@
+/*
+ libparted - a library for manipulating disk partitions
+ Copyright (C) 2000, 2007-2014, 2019-2023 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ * \file natmath.c
+ */
+
+/**
+ * \addtogroup PedAlignment
+ *
+ * \brief Alignment constraint model.
+ *
+ * This part of libparted models alignment constraints.
+ *
+ * @{
+ */
+
+#include <config.h>
+#include <stdlib.h>
+#include <parted/parted.h>
+#include <parted/debug.h>
+#include <parted/natmath.h>
+
+/* Arrrghhh! Why doesn't C have tuples? */
+typedef struct {
+ PedSector gcd; /* "converges" to the gcd */
+ PedSector x;
+ PedSector y;
+} EuclidTriple;
+
+static const PedAlignment _any = {
+ offset: 0,
+ grain_size: 1
+};
+
+const PedAlignment* ped_alignment_any = &_any;
+const PedAlignment* ped_alignment_none = NULL;
+
+/* This function returns "a mod b", the way C should have done it!
+ * Mathematicians prefer -3 mod 4 to be 3. Reason: division by N
+ * is all about adding or subtracting N, and we like our remainders
+ * to be between 0 and N - 1.
+ */
+static PedSector
+abs_mod (PedSector a, PedSector b)
+{
+ if (a < 0)
+ return a % b + b;
+ else
+ return a % b;
+}
+
+/* Rounds a number down to the closest number that is a multiple of
+ * grain_size.
+ */
+PedSector
+ped_round_down_to (PedSector sector, PedSector grain_size)
+{
+ return sector - abs_mod (sector, grain_size);
+}
+
+/* Rounds a number up to the closest number that is a multiple of
+ * grain_size.
+ */
+PedSector
+ped_round_up_to (PedSector sector, PedSector grain_size)
+{
+ if (sector % grain_size)
+ return ped_round_down_to (sector, grain_size) + grain_size;
+ else
+ return sector;
+}
+
+/* Rounds a number to the closest number that is a multiple of grain_size. */
+PedSector
+ped_round_to_nearest (PedSector sector, PedSector grain_size)
+{
+ if (sector % grain_size > grain_size/2)
+ return ped_round_up_to (sector, grain_size);
+ else
+ return ped_round_down_to (sector, grain_size);
+}
+
+/* This function returns the largest number that divides both a and b.
+ * It uses the ancient Euclidean algorithm.
+ */
+PedSector
+ped_greatest_common_divisor (PedSector a, PedSector b)
+{
+ PED_ASSERT (a >= 0);
+ PED_ASSERT (b >= 0);
+
+ /* Put the arguments in the "right" format. (Recursive calls made by
+ * this function are always in the right format.)
+ */
+ if (b > a)
+ return ped_greatest_common_divisor (b, a);
+
+ if (b)
+ return ped_greatest_common_divisor (b, a % b);
+ else
+ return a;
+}
+
+/**
+ * Initialize a preallocated piece of memory for an alignment object
+ * (used by PedConstraint).
+ *
+ * The object will represent all sectors \e s for which the equation
+ * <tt>s = offset + X * grain_size</tt> holds.
+ */
+int
+ped_alignment_init (PedAlignment* align, PedSector offset, PedSector grain_size)
+{
+ PED_ASSERT (align != NULL);
+
+ if (grain_size < 0)
+ return 0;
+
+ if (grain_size)
+ align->offset = abs_mod (offset, grain_size);
+ else
+ align->offset = offset;
+ align->grain_size = grain_size;
+
+ return 1;
+}
+
+/**
+ * Return an alignment object (used by PedConstraint), representing all
+ * PedSector's that are of the form <tt>offset + X * grain_size</tt>.
+ */
+PedAlignment*
+ped_alignment_new (PedSector offset, PedSector grain_size)
+{
+ PedAlignment* align;
+
+ align = (PedAlignment*) ped_malloc (sizeof (PedAlignment));
+ if (!align)
+ goto error;
+
+ if (!ped_alignment_init (align, offset, grain_size))
+ goto error_free_align;
+
+ return align;
+
+error_free_align:
+ free (align);
+error:
+ return NULL;
+}
+
+/**
+ * Free up memory associated with \p align.
+ */
+void
+ped_alignment_destroy (PedAlignment* align)
+{
+ free (align);
+}
+
+/**
+ * Return a duplicate of \p align.
+ */
+PedAlignment*
+ped_alignment_duplicate (const PedAlignment* align)
+{
+ if (!align)
+ return NULL;
+ return ped_alignment_new (align->offset, align->grain_size);
+}
+
+/* the extended Euclid algorithm.
+ *
+ * input:
+ * a and b, a > b
+ *
+ * output:
+ * gcd, x and y, such that:
+ *
+ * gcd = greatest common divisor of a and b
+ * gcd = x*a + y*b
+ */
+static EuclidTriple _GL_ATTRIBUTE_PURE
+extended_euclid (int a, int b)
+{
+ EuclidTriple result;
+ EuclidTriple tmp;
+
+ if (b == 0) {
+ result.gcd = a;
+ result.x = 1;
+ result.y = 0;
+ return result;
+ }
+
+ tmp = extended_euclid (b, a % b);
+ result.gcd = tmp.gcd;
+ result.x = tmp.y;
+ result.y = tmp.x - (a/b) * tmp.y;
+ return result;
+}
+
+/**
+ * This function computes a PedAlignment object that describes the
+ * intersection of two alignments. That is, a sector satisfies the
+ * new alignment object if and only if it satisfies both of the original
+ * ones. (See ped_alignment_is_aligned() for the meaning of "satisfies")
+ *
+ * Apart from the trivial cases (where one or both of the alignment objects
+ * constraints have no sectors that satisfy them), this is what we're trying to
+ * do:
+ * - two input constraints: \p a and \p b.
+ * - the new grain_size is going to be the lowest common multiple of
+ * \p a->grain_size and \p b->grain_size
+ * - hard part - solve the simultaneous equations, for offset, where offset,
+ * X and Y are variables. (Note: offset can be obtained from either X or Y,
+ * by substituing into either equation)
+ *
+ * \code
+ * offset = \p a->offset + X * \p a->grain_size (1)
+ * offset = \p b->offset + Y * \p b->grain_size (2)
+ * \endcode
+ *
+ * or, abbreviated:
+ *
+ * \code
+ * o = Ao + X*Ag (1)
+ * o = Bo + Y*Bg (2)
+ *
+ * => Ao + X*Ag = Bo + Y*Bg (1) = (2)
+ * X*Ag - Y*Bg = Bo - Ao (3)
+ * \endcode
+ *
+ * As it turns out, there only exists a solution if (Bo - Ao) is a multiple
+ * of the GCD of Ag and Bg. Reason: all linear combinations of Ag and Bg are
+ * multiples of the GCD.
+ *
+ * Proof:
+ *
+ * \code
+ * A * Ag + B * Bg
+ * = A * (\p a * gcd) + B * (\p b * gcd)
+ * = gcd * (A * \p a + B * \p b)
+ * \endcode
+ *
+ * gcd is a factor of the linear combination. QED
+ *
+ * Anyway, \p a * Ag + \p b * Bg = gcd can be solved (for \p a, \p b and gcd)
+ * with Euclid's extended algorithm. Then, we just multiply through by
+ * (Bo - Ao) / gcd to get (3).
+ *
+ * i.e.
+ * \code
+ * A * Ag + B * Bg = gcd
+ * A*(Bo-Ao)/gcd * Ag + B(Bo-Ao)/gcd * Bg = gcd * (Bo-Ao)/gcd
+ * X*Ag - Y*Bg = Bo - Ao (3)
+ *
+ * X = A*(Bo-Ao)/gcd
+ * Y = - B*(Bo-Ao)/gcd
+ * \endcode
+ *
+ * then:
+ * \code
+ * o = Ao + X*Ag (1)
+ * = Ao + A*(Bo-Ao)/gcd*Ag
+ * o = Bo + Y*Bg (2)
+ * = Bo - B*(Bo-Ao)/gcd*Ag
+ * \endcode
+ *
+ * Thanks go to Nathan Hurst (njh@hawthorn.csse.monash.edu.au) for figuring
+ * this algorithm out :-)
+ *
+ * \note Returned \c NULL is a valid PedAlignment object, and can be used
+ for ped_alignment_*() function.
+ *
+ * \return a PedAlignment on success, \c NULL on failure
+ */
+PedAlignment*
+ped_alignment_intersect (const PedAlignment* a, const PedAlignment* b)
+{
+ PedSector new_grain_size;
+ PedSector new_offset;
+ PedSector delta_on_gcd;
+ EuclidTriple gcd_factors;
+
+
+ if (!a || !b)
+ return NULL;
+
+ /*PED_DEBUG (0x10, "intersecting alignments (%d,%d) and (%d,%d)",
+ a->offset, a->grain_size, b->offset, b->grain_size);
+ */
+
+ if (a->grain_size < b->grain_size) {
+ const PedAlignment* tmp;
+ tmp = a; a = b; b = tmp;
+ }
+
+ /* weird/trivial case: where the solution space for "a" or "b" is
+ * either empty or contains exactly one solution
+ */
+ if (a->grain_size == 0 && b->grain_size == 0) {
+ if (a->offset == b->offset)
+ return ped_alignment_duplicate (a);
+ else
+ return NULL;
+ }
+
+ /* general case */
+ gcd_factors = extended_euclid (a->grain_size, b->grain_size);
+
+ delta_on_gcd = (b->offset - a->offset) / gcd_factors.gcd;
+ new_offset = a->offset + gcd_factors.x * delta_on_gcd * a->grain_size;
+ new_grain_size = a->grain_size * b->grain_size / gcd_factors.gcd;
+
+ /* inconsistency => no solution */
+ if (new_offset
+ != b->offset - gcd_factors.y * delta_on_gcd * b->grain_size)
+ return NULL;
+
+ return ped_alignment_new (new_offset, new_grain_size);
+}
+
+/* This function returns the sector closest to "sector" that lies inside
+ * geom and satisfies the alignment constraint.
+ */
+static PedSector _GL_ATTRIBUTE_PURE
+_closest_inside_geometry (const PedAlignment* align, const PedGeometry* geom,
+ PedSector sector)
+{
+ PED_ASSERT (align != NULL);
+
+ if (!align->grain_size) {
+ if (ped_alignment_is_aligned (align, geom, sector)
+ && (!geom || ped_geometry_test_sector_inside (geom,
+ sector)))
+ return sector;
+ else
+ return -1;
+ }
+
+ if (sector < geom->start)
+ sector += ped_round_up_to (geom->start - sector,
+ align->grain_size);
+ if (sector > geom->end)
+ sector -= ped_round_up_to (sector - geom->end,
+ align->grain_size);
+
+ if (!ped_geometry_test_sector_inside (geom, sector))
+ return -1;
+ return sector;
+}
+
+/**
+ * This function returns the closest sector to \p sector that lies inside
+ * \p geom that satisfies the given alignment constraint \p align. It prefers
+ * sectors that are beyond \p sector (are not smaller than \p sector),
+ * but does not guarantee that this.
+ *
+ * \return a PedSector on success, \c -1 on failure
+ */
+PedSector
+ped_alignment_align_up (const PedAlignment* align, const PedGeometry* geom,
+ PedSector sector)
+{
+ PedSector result;
+
+ PED_ASSERT (align != NULL);
+
+ if (!align->grain_size)
+ result = align->offset;
+ else
+ result = ped_round_up_to (sector - align->offset,
+ align->grain_size)
+ + align->offset;
+
+ if (geom)
+ result = _closest_inside_geometry (align, geom, result);
+ return result;
+}
+
+/**
+ * This function returns the closest sector to \p sector that lies inside
+ * \p geom that satisfies the given alignment constraint \p align. It prefers
+ * sectors that are before \p sector (are not larger than \p sector),
+ * but does not guarantee that this.
+ *
+ * \return a PedSector on success, \c -1 on failure
+ */
+PedSector
+ped_alignment_align_down (const PedAlignment* align, const PedGeometry* geom,
+ PedSector sector)
+{
+ PedSector result;
+
+ PED_ASSERT (align != NULL);
+
+ if (!align->grain_size)
+ result = align->offset;
+ else
+ result = ped_round_down_to (sector - align->offset,
+ align->grain_size)
+ + align->offset;
+
+ if (geom)
+ result = _closest_inside_geometry (align, geom, result);
+ return result;
+}
+
+/* Returns either a or b, depending on which is closest to "sector". */
+static PedSector
+closest (PedSector sector, PedSector a, PedSector b)
+{
+ if (a == -1)
+ return b;
+ if (b == -1)
+ return a;
+
+ if (llabs (sector - a) < llabs (sector - b))
+ return a;
+ else
+ return b;
+}
+
+/**
+ * This function returns the sector that is closest to \p sector,
+ * satisfies the \p align constraint and lies inside \p geom.
+ *
+ * \return a PedSector on success, \c -1 on failure
+ */
+PedSector
+ped_alignment_align_nearest (const PedAlignment* align, const PedGeometry* geom,
+ PedSector sector)
+{
+ PED_ASSERT (align != NULL);
+
+ return closest (sector, ped_alignment_align_up (align, geom, sector),
+ ped_alignment_align_down (align, geom, sector));
+}
+
+/**
+ * This function returns 1 if \p sector satisfies the alignment
+ * constraint \p align and lies inside \p geom.
+ *
+ * \return \c 1 on success, \c 0 on failure
+ */
+int
+ped_alignment_is_aligned (const PedAlignment* align, const PedGeometry* geom,
+ PedSector sector)
+{
+ if (!align)
+ return 0;
+
+ if (geom && !ped_geometry_test_sector_inside (geom, sector))
+ return 0;
+
+ if (align->grain_size)
+ return (sector - align->offset) % align->grain_size == 0;
+ else
+ return sector == align->offset;
+}
+
+/**
+ * @}
+ */