← Back to team overview

curtin-dev team mailing list archive

[Merge] ~dbungert/curtin:ntfs into curtin:master

 

Dan Bungert has proposed merging ~dbungert/curtin:ntfs into curtin:master.

Commit message:
block/v2: resize of ntfs

Requested reviews:
  curtin developers (curtin-dev)

For more details, see:
https://code.launchpad.net/~dbungert/curtin/+git/curtin/+merge/420513
-- 
Your team curtin developers is requested to review the proposed merge of ~dbungert/curtin:ntfs into curtin:master.
diff --git a/curtin/commands/block_meta_v2.py b/curtin/commands/block_meta_v2.py
index c1e3630..2efff25 100644
--- a/curtin/commands/block_meta_v2.py
+++ b/curtin/commands/block_meta_v2.py
@@ -60,6 +60,10 @@ def resize_ext(path, size):
     util.subp(['resize2fs', path, f'{size_k}k'])
 
 
+def resize_ntfs(path, size):
+    util.subp(['ntfsresize', '-s', str(size), path])
+
+
 def perform_resize(kname, size, direction):
     path = block.kname_to_path(kname)
     fstype = _get_volume_fstype(path)
@@ -73,6 +77,7 @@ resizers = {
     'ext2': resize_ext,
     'ext3': resize_ext,
     'ext4': resize_ext,
+    'ntfs': resize_ntfs,
 }
 
 
diff --git a/doc/topics/storage.rst b/doc/topics/storage.rst
index e6dd13a..aa5ad13 100644
--- a/doc/topics/storage.rst
+++ b/doc/topics/storage.rst
@@ -445,7 +445,7 @@ If the ``preserve`` flag is set to true, curtin will adjust the size of the
 partition to the new size.  When adjusting smaller, the size of the contents
 must permit that.  When adjusting larger, there must already be a gap beyond
 the partition in question.
-Resize is supported on filesystems of types ext2, ext3, ext4.
+Resize is supported on filesystems of types ext2, ext3, ext4, ntfs.
 
 **name**: *<name>*
 
diff --git a/tests/integration/test_block_meta.py b/tests/integration/test_block_meta.py
index be69bc0..76a974b 100644
--- a/tests/integration/test_block_meta.py
+++ b/tests/integration/test_block_meta.py
@@ -6,9 +6,11 @@ import json
 import sys
 import yaml
 import os
+import re
 
 from curtin import block, udev, util
 
+from curtin.commands.block_meta import _get_volume_fstype
 from curtin.commands.block_meta_v2 import ONE_MIB_BYTES
 
 from tests.unittests.helpers import CiTestCase
@@ -35,9 +37,7 @@ def loop_dev(image, sector_size=512):
 PartData = namedtuple("PartData", ('number', 'offset', 'size'))
 
 
-def _get_filesystem_size(dev, part_action, fstype='ext4'):
-    if fstype not in ('ext2', 'ext3', 'ext4'):
-        raise Exception(f'_get_filesystem_size: no support for {fstype}')
+def _get_ext_size(dev, part_action):
     num = part_action['number']
     cmd = ['dumpe2fs', '-h', f'{dev}p{num}']
     out = util.subp(cmd, capture=True)[0]
@@ -49,6 +49,37 @@ def _get_filesystem_size(dev, part_action, fstype='ext4'):
     return int(block_count) * int(block_size)
 
 
+def _get_ntfs_size(dev, part_action):
+    num = part_action['number']
+    cmd = ['ntfsresize',
+           '--no-action',
+           '--force',  # needed post-resize, which otherwise demands a CHKDSK
+           '--info', f'{dev}p{num}']
+    out = util.subp(cmd, capture=True)[0]
+    # Sample input:
+    # Current volume size: 41939456 bytes (42 MB)
+    volsize_matcher = re.compile(r'^Current volume size: ([0-9]+) bytes')
+    for line in out.splitlines():
+        m = volsize_matcher.match(line)
+        if m:
+            return int(m.group(1))
+    raise Exception('ntfs volume size not found')
+
+
+_get_fs_sizers = {
+    'ext2': _get_ext_size,
+    'ext3': _get_ext_size,
+    'ext4': _get_ext_size,
+    'ntfs': _get_ntfs_size,
+}
+
+
+def _get_filesystem_size(dev, part_action, fstype='ext4'):
+    if fstype not in _get_fs_sizers.keys():
+        raise Exception(f'_get_filesystem_size: no support for {fstype}')
+    return _get_fs_sizers[fstype](dev, part_action)
+
+
 def _get_extended_partition_size(dev, num):
     # sysfs reports extended partitions as having 1K size
     # sfdisk seems to have a better idea
@@ -139,6 +170,17 @@ class TestBlockMeta(IntegrationTestCase):
         with self.open_file_on_part(dev, part_action, 'r') as fp:
             self.assertEqual(self.data, fp.read())
 
+    def check_fssize(self, dev, part_action, fstype, expected):
+        tolerance = 0
+        if fstype == 'ntfs':
+            # Per ntfsresize manpage, the actual fs size is at least one sector
+            # less than requested.
+            # In these tests it has been consistently 7 sectors fewer.
+            tolerance = 512 * 10
+        actual_fssize = _get_filesystem_size(dev, part_action, fstype)
+        diff = expected - actual_fssize
+        self.assertTrue(0 <= diff <= tolerance, f'difference of {diff}')
+
     def run_bm(self, config, *args, **kwargs):
         config_path = self.tmp_path('config.yaml')
         with open(config_path, 'w') as fp:
@@ -481,13 +523,13 @@ class TestBlockMeta(IntegrationTestCase):
                              fstype=fstype)
         self.run_bm(config.render())
         with loop_dev(img) as dev:
+            self.assertEqual(fstype, _get_volume_fstype(f'{dev}p1'))
             self.create_data(dev, p1)
             self.assertEqual(
                 summarize_partitions(dev), [
                     PartData(number=1, offset=1 << 20, size=start),
                 ])
-            fs_size = _get_filesystem_size(dev, p1, fstype)
-            self.assertEqual(start, fs_size)
+            self.check_fssize(dev, p1, fstype, start)
 
         config.set_preserve()
         p1['resize'] = True
@@ -499,8 +541,7 @@ class TestBlockMeta(IntegrationTestCase):
                 summarize_partitions(dev), [
                     PartData(number=1, offset=1 << 20, size=end),
                 ])
-            fs_size = _get_filesystem_size(dev, p1, fstype)
-            self.assertEqual(end, fs_size)
+            self.check_fssize(dev, p1, fstype, end)
 
     def test_resize_up_ext2(self):
         self._do_test_resize(40, 80, 'ext2')
@@ -520,6 +561,12 @@ class TestBlockMeta(IntegrationTestCase):
     def test_resize_down_ext4(self):
         self._do_test_resize(80, 40, 'ext4')
 
+    def test_resize_up_ntfs(self):
+        self._do_test_resize(40, 80, 'ntfs')
+
+    def test_resize_down_ntfs(self):
+        self._do_test_resize(80, 40, 'ntfs')
+
     def test_resize_logical(self):
         img = self.tmp_path('image.img')
         config = StorageConfigBuilder(version=2)

Follow ups