diff --git a/docs/libblockdev-sections.txt b/docs/libblockdev-sections.txt index 55e8b664b..6244074ef 100644 --- a/docs/libblockdev-sections.txt +++ b/docs/libblockdev-sections.txt @@ -263,6 +263,7 @@ bd_loop_setup bd_loop_setup_from_fd bd_loop_teardown bd_loop_set_autoclear +bd_loop_set_capacity BDLoopTech BDLoopTechMode bd_loop_is_tech_avail diff --git a/src/lib/plugin_apis/loop.api b/src/lib/plugin_apis/loop.api index 4fcc79964..41e19a53b 100644 --- a/src/lib/plugin_apis/loop.api +++ b/src/lib/plugin_apis/loop.api @@ -185,4 +185,18 @@ gboolean bd_loop_teardown (const gchar *loop, GError **error); */ gboolean bd_loop_set_autoclear (const gchar *loop, gboolean autoclear, GError **error); +/** + * bd_loop_set_capacity: + * @loop: path or name of the loop device + * @error: (out) (optional): place to store error (if any) + * + * Force the loop driver to reread the size of the file associated with the + * specified @loop device. + * + * Returns: whether the LOOP_SET_CAPACITY ioctl was successfully issued or not. + * + * Tech category: %BD_LOOP_TECH_LOOP-%BD_LOOP_TECH_MODE_MODIFY + */ +gboolean bd_loop_set_capacity (const gchar *loop, GError **error); + #endif /* BD_LOOP_API */ diff --git a/src/plugins/loop.c b/src/plugins/loop.c index 835be8122..2c738675e 100644 --- a/src/plugins/loop.c +++ b/src/plugins/loop.c @@ -517,3 +517,64 @@ gboolean bd_loop_set_autoclear (const gchar *loop, gboolean autoclear, GError ** bd_utils_report_finished (progress_id, "Completed"); return TRUE; } + +/** + * bd_loop_set_capacity: + * @loop: path or name of the loop device + * @error: (out) (optional): place to store error (if any) + * + * Force the loop driver to reread the size of the file associated with the + * specified @loop device. + * + * Returns: whether the LOOP_SET_CAPACITY ioctl was successfully issued or not. + * + * Tech category: %BD_LOOP_TECH_LOOP-%BD_LOOP_TECH_MODE_MODIFY + */ +gboolean bd_loop_set_capacity (const gchar *loop, GError **error) { + gchar *dev_loop = NULL; + gint fd = -1; + guint64 progress_id = 0; + gchar *msg = NULL; + guint n_try = 0; + gint status = 0; + GError *l_error = NULL; + + if (!g_str_has_prefix (loop, "/dev/")) + dev_loop = g_strdup_printf ("/dev/%s", loop); + + msg = g_strdup_printf ("Started setting up capacity on the %s device", + dev_loop ? dev_loop : loop); + progress_id = bd_utils_report_started (msg); + g_free (msg); + + fd = open (dev_loop ? dev_loop : loop, O_RDWR); + g_free (dev_loop); + if (fd < 0) { + g_set_error (&l_error, BD_LOOP_ERROR, BD_LOOP_ERROR_DEVICE, + "Failed to open device %s: %m", loop); + bd_utils_report_finished (progress_id, l_error->message); + g_propagate_error (error, l_error); + return FALSE; + } + + for (n_try=10, status=-1; (status != 0) && (n_try > 0); n_try--) { + status = ioctl (fd, LOOP_SET_CAPACITY, 0); + if (status < 0 && errno == EAGAIN) + g_usleep (100 * 1000); /* microseconds */ + else + break; + } + + if (status != 0) { + g_set_error (&l_error, BD_LOOP_ERROR, BD_LOOP_ERROR_FAIL, + "Failed to set capacity of the device %s: %m", loop); + close (fd); + bd_utils_report_finished (progress_id, l_error->message); + g_propagate_error (error, l_error); + return FALSE; + } + + close (fd); + bd_utils_report_finished (progress_id, "Completed"); + return TRUE; +} diff --git a/src/plugins/loop.h b/src/plugins/loop.h index 51c5cbd6e..0dab4bea3 100644 --- a/src/plugins/loop.h +++ b/src/plugins/loop.h @@ -66,5 +66,6 @@ gboolean bd_loop_setup_from_fd (gint fd, guint64 offset, guint64 size, gboolean gboolean bd_loop_teardown (const gchar *loop, GError **error); gboolean bd_loop_set_autoclear (const gchar *loop, gboolean autoclear, GError **error); +gboolean bd_loop_set_capacity (const gchar *loop, GError **error); #endif /* BD_LOOP */ diff --git a/tests/loop_test.py b/tests/loop_test.py index 406f71d7c..a6521c846 100644 --- a/tests/loop_test.py +++ b/tests/loop_test.py @@ -2,7 +2,7 @@ import unittest import overrides_hack -from utils import create_sparse_tempfile, TestTags, tag_test, required_plugins +from utils import create_sparse_tempfile, create_sparse_file, TestTags, tag_test, required_plugins import gi gi.require_version('BlockDev', '3.0') @@ -11,6 +11,7 @@ @required_plugins(("loop",)) class LoopTestCase(unittest.TestCase): + _loop_size = 100 * 1024**2 requested_plugins = BlockDev.plugin_specs_from_names(("loop",)) @@ -23,7 +24,7 @@ def setUpClass(cls): def setUp(self): self.addCleanup(self._clean_up) - self.dev_file = create_sparse_tempfile("loop_test", 1024**3) + self.dev_file = create_sparse_tempfile("loop_test", self._loop_size) self.loop = None def _clean_up(self): @@ -33,6 +34,10 @@ def _clean_up(self): pass os.unlink(self.dev_file) + def _get_loop_size(self): + with open("/sys/block/%s/size" % self.loop, "r") as f: + return int(f.read()) * 512 + class LoopPluginVersionCase(LoopTestCase): @tag_test(TestTags.NOSTORAGE) def test_plugin_version(self): @@ -75,9 +80,7 @@ def test_loop_setup_with_offset(self): self.assertEqual(info.offset, 10 * 1024**2) # should have smaller size due to the offset - with open("/sys/block/%s/size" % self.loop, "r") as f: - size = int(f.read()) * 512 - self.assertEqual(size, 1024**3 - 10 * 1024 **2) + self.assertEqual(self._get_loop_size(), self._loop_size - 10 * 1024 **2) succ = BlockDev.loop_teardown(self.loop) self.assertTrue(succ) @@ -93,9 +96,7 @@ def test_loop_setup_with_offset_and_size(self): self.assertTrue(self.loop) # should have size as specified - with open("/sys/block/%s/size" % self.loop, "r") as f: - size = int(f.read()) * 512 - self.assertEqual(size, 50 * 1024**2) + self.assertEqual(self._get_loop_size(), 50 * 1024**2) succ = BlockDev.loop_teardown(self.loop) self.assertTrue(succ) @@ -219,3 +220,23 @@ def test_loop_get_set_autoclear(self): info = BlockDev.loop_info(self.loop) self.assertIsNotNone(info) self.assertFalse(info.autoclear) + + +class LoopTestSetCapacity(LoopTestCase): + def test_loop_set_capacity(self): + succ, self.loop = BlockDev.loop_setup(self.dev_file) + self.assertTrue(succ) + self.assertTrue(self.loop) + self.assertEqual(self._get_loop_size(), self._loop_size) + + # enlarge the backing file + create_sparse_file(self.dev_file, self._loop_size * 2) + + # size shouldn't change without forcing re-read + self.assertEqual(self._get_loop_size(), self._loop_size) + + succ = BlockDev.loop_set_capacity(self.loop) + self.assertTrue(succ) + + # now the size should be updated + self.assertEqual(self._get_loop_size(), self._loop_size * 2)