Пример #1
0
 def test_copy_blob_install_grub_to_mbr(self):
     # Install GRUB to MBR
     # TODO: this has to be represented in the gadget.yaml
     # NOTE: the boot.img has to be a part of the gadget snap itself
     # FIXME: embed a pointer to 2nd stage in bios-boot partition
     #
     # dd if=blobs/img.mbr of=img bs=446 count=1 conv=notrunc
     #
     # Start by creating a blob of the requested size.
     blob_file = os.path.join(self.tmpdir, 'mbr.blob')
     with open(blob_file, 'wb') as fp:
         fp.write(b'happyhappyjoyjoy' * 27)
         fp.write(b'happyhappyjoyj')
     self.assertEqual(os.stat(blob_file).st_size, 446)
     image = Image(self.img, MiB(1))
     image.copy_blob(blob_file, bs=446, count=1, conv='notrunc')
     # At the top of the image file, there should be 27 Stimpy
     # Exclamations, followed by a happyhappyjoyj.
     with open(image.path, 'rb') as fp:
         complete_stimpys = fp.read(432)
         partial_stimpys = fp.read(14)
         # Spot check.
         zeros = fp.read(108)
     self.assertEqual(complete_stimpys, b'happyhappyjoyjoy' * 27)
     self.assertEqual(partial_stimpys, b'happyhappyjoyj')
     # Stevens $4.13 - the extended file should read as zeros.
     self.assertEqual(zeros, b'\0' * 108)
Пример #2
0
 def test_copy_blob_install_grub_to_mbr(self):
     # Install GRUB to MBR
     # TODO: this has to be represented in the image.yaml
     # NOTE: the boot.img has to be a part of the gadget snap itself
     # FIXME: embed a pointer to 2nd stage in bios-boot partition
     #
     # dd if=blobs/img.mbr of=img bs=446 count=1 conv=notrunc
     #
     # Start by creating a blob of the requested size.
     blob_file = os.path.join(self.tmpdir, 'mbr.blob')
     with open(blob_file, 'wb') as fp:
         fp.write(b'happyhappyjoyjoy' * 27)
         fp.write(b'happyhappyjoyj')
     self.assertEqual(os.stat(blob_file).st_size, 446)
     image = Image(self.img, MiB(1))
     image.copy_blob(blob_file, bs=446, count=1, conv='notrunc')
     # At the top of the image file, there should be 27 Stimpy
     # Exclamations, followed by a happyhappyjoyj.
     with open(image.path, 'rb') as fp:
         complete_stimpys = fp.read(432)
         partial_stimpys = fp.read(14)
         # Spot check.
         zeros = fp.read(108)
     self.assertEqual(complete_stimpys, b'happyhappyjoyjoy' * 27)
     self.assertEqual(partial_stimpys, b'happyhappyjoyj')
     # Stevens $4.13 - the extended file should read as zeros.
     self.assertEqual(zeros, b'\0' * 108)
Пример #3
0
 def test_sector_conversion(self):
     # For empty non-partitioned images we default to a 512 sector size.
     image = Image(self.img, MiB(1))
     self.assertEqual(image.sector(10), 5120)
     # In case of using partitioning, be sure we use the sector size as
     # returned by pyparted.
     image = Image(self.img, MiB(5), VolumeSchema.mbr)
     self.assertEqual(image.sector(10), 10 * image.device.sectorSize)
Пример #4
0
 def _populate_one_volume(self, name, volume):
     for partnum, part in enumerate(volume.structures):
         part_img = volume.part_images[partnum]
         part_dir = os.path.join(volume.basedir, 'part{}'.format(partnum))
         if part.role is StructureRole.system_data:
             # The root partition needs to be ext4, which may or may not be
             # populated at creation time, depending on the version of
             # e2fsprogs.
             mkfs_ext4(part_img,
                       self.rootfs,
                       self.args.cmd,
                       part.filesystem_label,
                       preserve_ownership=True)
         elif part.filesystem is FileSystemType.none:
             image = Image(part_img, part.size)
             offset = 0
             for content in part.content:
                 src = os.path.join(self.unpackdir, 'gadget', content.image)
                 file_size = os.path.getsize(src)
                 assert content.size is None or content.size >= file_size, (
                     'Spec size {} < actual size {} of: {}'.format(
                         content.size, file_size, content.image))
                 if content.size is not None:
                     file_size = content.size
                 # TODO: We need to check for overlapping images.
                 if content.offset is not None:
                     offset = content.offset
                 end = offset + file_size
                 if end > part.size:
                     if part.name is None:
                         if part.role is None:
                             whats_wrong = part.type
                         else:
                             whats_wrong = part.role.value
                     else:
                         whats_wrong = part.name
                     part_path = 'volumes:<{}>:structure:<{}>'.format(
                         name, whats_wrong)
                     self.exitcode = 1
                     raise DoesNotFit(partnum, part_path, end - part.size)
                 image.copy_blob(src, bs=1, seek=offset, conv='notrunc')
                 offset += file_size
         elif part.filesystem is FileSystemType.vfat:
             sourcefiles = SPACE.join(
                 os.path.join(part_dir, filename)
                 for filename in os.listdir(part_dir))
             env = dict(MTOOLS_SKIP_CHECK='1')
             env.update(os.environ)
             run('mcopy -s -i {} {} ::'.format(part_img, sourcefiles),
                 env=env)
         elif part.filesystem is FileSystemType.ext4:
             mkfs_ext4(part_img, part_dir, self.args.cmd,
                       part.filesystem_label)
         else:
             raise AssertionError('Invalid part filesystem type: {}'.format(
                 part.filesystem))
Пример #5
0
 def test_write_value_at_offset(self):
     image = Image(self.img, MiB(2))
     image.write_value_at_offset(801, 130031)
     # Now open the path independently, seek to the given offset, and read
     # 4 bytes, then interpret it as a little-endian 32-bit integer.
     with open(image.path, 'rb') as fp:
         fp.seek(130031)
         # Unpack always returns a tuple, but there's only one item there.
         value, *ignore = unpack('<I', fp.read(4))
     self.assertEqual(value, 801)
Пример #6
0
 def test_write_value_at_offset(self):
     image = Image(self.img, MiB(2))
     image.write_value_at_offset(801, 130031)
     # Now open the path independently, seek to the given offset, and read
     # 4 bytes, then interpret it as a little-endian 32-bit integer.
     with open(image.path, 'rb') as fp:
         fp.seek(130031)
         # Unpack always returns a tuple, but there's only one item there.
         value, *ignore = unpack('<I', fp.read(4))
     self.assertEqual(value, 801)
Пример #7
0
 def _populate_one_volume(self, name, volume):
     for partnum, part in enumerate(volume.structures):
         part_img = volume.part_images[partnum]
         part_dir = os.path.join(volume.basedir, 'part{}'.format(partnum))
         if part.role is StructureRole.system_data:
             # The root partition needs to be ext4, which may or may not be
             # populated at creation time, depending on the version of
             # e2fsprogs.
             mkfs_ext4(part_img, self.rootfs, self.args.cmd,
                       part.filesystem_label, preserve_ownership=True)
         elif part.filesystem is FileSystemType.none:
             image = Image(part_img, part.size)
             offset = 0
             for content in part.content:
                 src = os.path.join(self.unpackdir, 'gadget', content.image)
                 file_size = os.path.getsize(src)
                 assert content.size is None or content.size >= file_size, (
                     'Spec size {} < actual size {} of: {}'.format(
                         content.size, file_size, content.image))
                 if content.size is not None:
                     file_size = content.size
                 # TODO: We need to check for overlapping images.
                 if content.offset is not None:
                     offset = content.offset
                 end = offset + file_size
                 if end > part.size:
                     if part.name is None:
                         if part.role is None:
                             whats_wrong = part.type
                         else:
                             whats_wrong = part.role.value
                     else:
                         whats_wrong = part.name
                     part_path = 'volumes:<{}>:structure:<{}>'.format(
                         name, whats_wrong)
                     self.exitcode = 1
                     raise DoesNotFit(partnum, part_path, end - part.size)
                 image.copy_blob(src, bs=1, seek=offset, conv='notrunc')
                 offset += file_size
         elif part.filesystem is FileSystemType.vfat:
             sourcefiles = SPACE.join(
                 os.path.join(part_dir, filename)
                 for filename in os.listdir(part_dir)
                 )
             env = dict(MTOOLS_SKIP_CHECK='1')
             env.update(os.environ)
             run('mcopy -s -i {} {} ::'.format(part_img, sourcefiles),
                 env=env)
         elif part.filesystem is FileSystemType.ext4:
             mkfs_ext4(part_img, part_dir, self.args.cmd,
                       part.filesystem_label)
         else:
             raise AssertionError('Invalid part filesystem type: {}'.format(
                 part.filesystem))
Пример #8
0
 def test_write_value_at_offsets_near_end(self):
     image = Image(self.img, 10000)
     # Attempt to write a bunch of values near the end of the file.  Since
     # the value will always be a 32-bit value, any positions farther out
     # than 4 bytes before the end will fail.
     results = set()
     for pos in range(9995, 10002):
         with suppress(ValueError):
             image.write_value_at_offset(801, pos)
             self.assertEqual(os.path.getsize(self.img), 10000)
             results.add(pos)
     self.assertEqual(results, {9995, 9996})
Пример #9
0
 def test_copy_blob_with_seek(self):
     # dd if=blobs/img.bios-boot of=img bs=1MiB seek=4 count=1 conv=notrunc
     blob_file = os.path.join(self.tmpdir, 'img.bios-boot')
     with open(blob_file, 'wb') as fp:
         fp.write(b'x' * 100)
     image = Image(self.img, MiB(2))
     image.copy_blob(blob_file, bs=773, seek=4, count=1, conv='notrunc')
     # The seek=4 skipped 4 blocks of 773 bytes.
     with open(image.path, 'rb') as fp:
         self.assertEqual(fp.read(3092), b'\0' * 3092)
         self.assertEqual(fp.read(100), b'x' * 100)
         self.assertEqual(fp.read(25), b'\0' * 25)
Пример #10
0
 def test_set_partition_type_gpt(self):
     image = Image(self.img, MiB(6), VolumeSchema.gpt)
     image.partition(offset=MiB(1), size=MiB(1))
     self.assertEqual(len(image.disk.partitions), 1)
     image.set_parition_type(1, '21686148-6449-6E6F-744E-656564454649')
     disk_info = image.diagnostics()
     self.assertEqual(disk_info['partitiontable']['partitions'][0]['type'],
                      '21686148-6449-6E6F-744E-656564454649')
     image.set_parition_type(1, '00000000-0000-0000-0000-0000DEADBEEF')
     disk_info = image.diagnostics()
     self.assertEqual(disk_info['partitiontable']['partitions'][0]['type'],
                      '00000000-0000-0000-0000-0000DEADBEEF')
Пример #11
0
 def test_set_partition_type_mbr(self):
     image = Image(self.img, MiB(6), VolumeSchema.mbr)
     image.partition(offset=MiB(1), size=MiB(1))
     self.assertEqual(len(image.disk.partitions), 1)
     image.set_parition_type(1, '83')
     disk_info = image.diagnostics()
     self.assertEqual(disk_info['partitiontable']['partitions'][0]['type'],
                      '83')
     image.set_parition_type(1, 'da')
     disk_info = image.diagnostics()
     self.assertEqual(disk_info['partitiontable']['partitions'][0]['type'],
                      'da')
Пример #12
0
 def test_copy_blob_with_seek(self):
     # dd if=blobs/img.bios-boot of=img bs=1MiB seek=4 count=1 conv=notrunc
     blob_file = os.path.join(self.tmpdir, 'img.bios-boot')
     with open(blob_file, 'wb') as fp:
         fp.write(b'x' * 100)
     image = Image(self.img, MiB(2))
     image.copy_blob(blob_file, bs=773, seek=4, count=1, conv='notrunc')
     # The seek=4 skipped 4 blocks of 773 bytes.
     with open(image.path, 'rb') as fp:
         self.assertEqual(fp.read(3092), b'\0' * 3092)
         self.assertEqual(fp.read(100), b'x' * 100)
         self.assertEqual(fp.read(25), b'\0' * 25)
Пример #13
0
 def test_write_value_at_offsets_near_end(self):
     image = Image(self.img, 10000)
     # Attempt to write a bunch of values near the end of the file.  Since
     # the value will always be a 32-bit value, any positions farther out
     # than 4 bytes before the end will fail.
     results = set()
     for pos in range(9995, 10002):
         with suppress(ValueError):
             image.write_value_at_offset(801, pos)
             self.assertEqual(os.path.getsize(self.img), 10000)
             results.add(pos)
     self.assertEqual(results, {9995, 9996})
Пример #14
0
 def test_small_partition_size_and_offset(self):
     # LP: #1630709 - structure parts with size and offset < 1MB.
     image = Image(self.img, MiB(2), VolumeSchema.mbr)
     image.partition(offset=256, size=512)
     disk_info = image.diagnostics()
     # Even though the offset and size are set at 256 bytes and 512 bytes
     # respectively, the minimum granularity is one sector (i.e. 512
     # bytes).  The start and size returned by diagnostics() are in sector
     # units.
     self.assertEqual(disk_info['partitiontable']['partitions'][0]['start'],
                      1)
     self.assertEqual(disk_info['partitiontable']['partitions'][0]['size'],
                      1)
Пример #15
0
 def _make_one_disk(self, imgfile, name, volume):
     part_id = 1
     # Create the image object for the selected volume schema
     image = Image(imgfile, volume.image_size, volume.schema)
     offset_writes = []
     part_offsets = {}
     # We first create all the needed partitions.
     # For regular core16 and core18 builds, this means creating all of the
     # defined partitions.  For core20 (the so called 'seeded images'), we
     # only create all the role-less partitions and mbr + system-seed.
     # The rest is created dynamically by snapd on first boot.
     for part in volume.structures:
         if part.name is not None:
             part_offsets[part.name] = part.offset
         if part.offset_write is not None:
             offset_writes.append((part.offset, part.offset_write))
         if (part.role is StructureRole.mbr or part.type == 'bare' or
                 self._should_skip_partition(part)):
             continue
         activate = False
         if (volume.schema is VolumeSchema.mbr and
                 part.role is StructureRole.system_boot):
             activate = True
         elif (volume.schema is VolumeSchema.gpt and
                 part.role is StructureRole.system_data and
                 part.name is None):
             part.name = 'writable'
         image.partition(part.offset, part.size, part.name, activate)
     # Now since we're done, we need to do a second pass to copy the data
     # and set all the partition types.  This needs to be done like this as
     # libparted's commit() operation resets type GUIDs to defaults and
     # clobbers things like hybrid MBR partitions.
     part_id = 1
     for i, part in enumerate(volume.structures):
         if self._should_skip_partition(part):
             continue
         image.copy_blob(volume.part_images[i],
                         bs=image.sector_size,
                         seek=part.offset // image.sector_size,
                         count=ceil(part.size / image.sector_size),
                         conv='notrunc')
         if part.role is StructureRole.mbr or part.type == 'bare':
             continue
         image.set_parition_type(part_id, part.type)
         part_id += 1
     for value, dest in offset_writes:
         # Decipher non-numeric offset_write values.
         if isinstance(dest, tuple):
             dest = part_offsets[dest[0]] + dest[1]
         image.write_value_at_offset(value // image.sector_size, dest)
Пример #16
0
 def test_small_partition_size_and_offset(self):
     # LP: #1630709 - structure parts with size and offset < 1MB.
     image = Image(self.img, MiB(2), VolumeSchema.mbr)
     image.partition(offset=256, size=512)
     disk_info = image.diagnostics()
     # Even though the offset and size are set at 256 bytes and 512 bytes
     # respectively, the minimum granularity is one sector (i.e. 512
     # bytes).  The start and size returned by diagnostics() are in sector
     # units.
     self.assertEqual(
         disk_info['partitiontable']['partitions'][0]['start'],
         1)
     self.assertEqual(
         disk_info['partitiontable']['partitions'][0]['size'],
         1)
Пример #17
0
 def test_set_partition_type_mbr(self):
     image = Image(self.img, MiB(6), VolumeSchema.mbr)
     image.partition(offset=MiB(1), size=MiB(1))
     self.assertEqual(len(image.disk.partitions), 1)
     image.set_parition_type(1, '83')
     disk_info = image.diagnostics()
     self.assertEqual(disk_info['partitiontable']['partitions'][0]['type'],
                      '83')
     image.set_parition_type(1, 'da')
     disk_info = image.diagnostics()
     self.assertEqual(disk_info['partitiontable']['partitions'][0]['type'],
                      'da')
Пример #18
0
 def test_set_partition_type_gpt(self):
     image = Image(self.img, MiB(6), VolumeSchema.gpt)
     image.partition(offset=MiB(1), size=MiB(1))
     self.assertEqual(len(image.disk.partitions), 1)
     image.set_parition_type(1, '21686148-6449-6E6F-744E-656564454649')
     disk_info = image.diagnostics()
     self.assertEqual(disk_info['partitiontable']['partitions'][0]['type'],
                      '21686148-6449-6E6F-744E-656564454649')
     image.set_parition_type(1, '00000000-0000-0000-0000-0000DEADBEEF')
     disk_info = image.diagnostics()
     self.assertEqual(disk_info['partitiontable']['partitions'][0]['type'],
                      '00000000-0000-0000-0000-0000DEADBEEF')
Пример #19
0
 def test_initialize_partition_table_mbr_too_small(self):
     # Creating a super small image should fail as there's no place for
     # a partition table.
     self.assertRaises(IOException, Image, self.img, 25, VolumeSchema.gpt)
     # ...but we can create an Image object without the partition table.
     image = Image(self.img, 25, None)
     self.assertTrue(os.path.exists(image.path))
     self.assertEqual(os.stat(image.path).st_size, 25)
Пример #20
0
 def test_partition(self):
     # Create BIOS boot partition
     #
     # The partition is 1MiB in size, as recommended by various
     # partitioning guides.  The actual required size is much, much
     # smaller.
     image = Image(self.img, MiB(10))
     image.partition(new='1:4MiB:+1MiB')
     image.partition(typecode='1:21686148-6449-6E6F-744E-656564454649')
     image.partition(change_name='1:grub')
     mbr = image.diagnostics(Diagnostics.mbr)
     # We should see that the disk size is 10MiB.
     self.assertRegex(mbr, '10.0 MiB')
     gpt = image.diagnostics(Diagnostics.gpt)
     # We should see that there is 1 partition named grub.
     self.assertRegex(gpt, 'grub')
Пример #21
0
 def test_gpt_image_partitions(self):
     image = Image(self.img, MiB(10), VolumeSchema.gpt)
     image.partition(offset=MiB(4), size=MiB(1), name='grub')
     self.assertEqual(len(image.disk.partitions), 1)
     image.partition(offset=MiB(5), size=MiB(4))
     self.assertEqual(len(image.disk.partitions), 2)
     image.set_parition_type(1, '21686148-6449-6E6F-744E-656564454649')
     image.set_parition_type(2, '0FC63DAF-8483-4772-8E79-3D69D8477DE4')
     # Use an external tool for checking the partition table to be sure
     # that it's indeed correct as suspected.
     disk_info = image.diagnostics()
     partitions = disk_info['partitiontable']
     # The device id is unpredictable.
     partitions.pop('id')
     # Newer sfdisk displays an additional field of 'sectorsize' that
     # we're not really interested in.
     partitions.pop('sectorsize', None)
     # The partition uuids as well.
     [p.pop('uuid') for p in partitions['partitions']]
     self.maxDiff = None
     self.assertEqual(
         partitions, {
             'label':
             'gpt',
             'device':
             self.img,
             'unit':
             'sectors',
             'firstlba':
             34,
             'lastlba':
             20446,
             'partitions': [{
                 'node': '{}1'.format(self.img),
                 'start': 8192,
                 'size': 2048,
                 'type': '21686148-6449-6E6F-744E-656564454649',
                 'name': 'grub',
             }, {
                 'node': '{}2'.format(self.img),
                 'start': 10240,
                 'size': 8192,
                 'type': '0FC63DAF-8483-4772-8E79-3D69D8477DE4',
             }],
         })
Пример #22
0
    def populate_filesystems(self):
        volumes = self.gadget.volumes.values()
        assert len(volumes) == 1, 'For now, only one volume is allowed'
        volume = list(volumes)[0]
        for partnum, part in enumerate(volume.structures):
            part_img = self.boot_images[partnum]
            part_dir = os.path.join(self.workdir, 'part{}'.format(partnum))
            if part.filesystem is FileSystemType.none:  # pragma: nocover
                image = Image(part_img, part.size)
                offset = 0
                for file in part.content:
                    src = os.path.join(self.unpackdir, 'gadget', file.image)
                    file_size = os.path.getsize(src)
                    if file.size is not None and file.size < file_size:
                        raise AssertionError('Size {} < size of {}'.format(
                            file.size, file.image))
                    if file.size is not None:
                        file_size = file.size
                    # XXX: We need to check for overlapping images.
                    if file.offset is not None:
                        offset = file.offset
                    # XXX: We must check offset+size vs. the target image.
                    image.copy_blob(src, bs=1, seek=offset, conv='notrunc')
                    offset += file_size

            elif part.filesystem is FileSystemType.vfat:  # pragma: nobranch
                sourcefiles = SPACE.join(
                    os.path.join(part_dir, filename)
                    for filename in os.listdir(part_dir))
                env = dict(MTOOLS_SKIP_CHECK='1')
                env.update(os.environ)
                run('mcopy -s -i {} {} ::'.format(part_img, sourcefiles),
                    env=env)
            elif part.filesystem is FileSystemType.ext4:  # pragma: nocover
                _mkfs_ext4(self.part_img, part_dir, part.filesystem_label)
        # The root partition needs to be ext4, which may or may not be
        # populated at creation time, depending on the version of e2fsprogs.
        _mkfs_ext4(self.root_img, self.rootfs)
        self._next.append(self.make_disk)
Пример #23
0
 def test_sector_conversion(self):
     # For empty non-partitioned images we default to a 512 sector size.
     image = Image(self.img, MiB(1))
     self.assertEqual(image.sector(10), 5120)
     # In case of using partitioning, be sure we use the sector size as
     # returned by pyparted.
     image = Image(self.img, MiB(5), VolumeSchema.mbr)
     self.assertEqual(image.sector(10), 10 * image.device.sectorSize)
Пример #24
0
 def _make_one_disk(self, imgfile, name, volume):
     part_id = 1
     # Create the image object for the selected volume schema
     image = Image(imgfile, volume.image_size, volume.schema)
     offset_writes = []
     part_offsets = {}
     # We first create all the partitions.
     for part in volume.structures:
         if part.name is not None:
             part_offsets[part.name] = part.offset
         if part.offset_write is not None:
             offset_writes.append((part.offset, part.offset_write))
         if part.role is StructureRole.mbr or part.type == 'bare':
             continue
         activate = False
         if (volume.schema is VolumeSchema.mbr and
                 part.role is StructureRole.system_boot):
             activate = True
         elif (volume.schema is VolumeSchema.gpt and
                 part.role is StructureRole.system_data and
                 part.name is None):
             part.name = 'writable'
         image.partition(part.offset, part.size, part.name, activate)
     # Now since we're done, we need to do a second pass to copy the data
     # and set all the partition types.  This needs to be done like this as
     # libparted's commit() operation resets type GUIDs to defaults and
     # clobbers things like hybrid MBR partitions.
     part_id = 1
     for i, part in enumerate(volume.structures):
         image.copy_blob(volume.part_images[i],
                         bs=image.sector_size,
                         seek=part.offset // image.sector_size,
                         count=ceil(part.size / image.sector_size),
                         conv='notrunc')
         if part.role is StructureRole.mbr or part.type == 'bare':
             continue
         image.set_parition_type(part_id, part.type)
         part_id += 1
     for value, dest in offset_writes:
         # Decipher non-numeric offset_write values.
         if isinstance(dest, tuple):
             dest = part_offsets[dest[0]] + dest[1]
         image.write_value_at_offset(value // image.sector_size, dest)
Пример #25
0
 def test_gpt_image_partitions(self):
     image = Image(self.img, MiB(10), VolumeSchema.gpt)
     image.partition(offset=MiB(4), size=MiB(1), name='grub')
     self.assertEqual(len(image.disk.partitions), 1)
     image.partition(offset=MiB(5), size=MiB(4))
     self.assertEqual(len(image.disk.partitions), 2)
     image.set_parition_type(1, '21686148-6449-6E6F-744E-656564454649')
     image.set_parition_type(2, '0FC63DAF-8483-4772-8E79-3D69D8477DE4')
     # Use an external tool for checking the partition table to be sure
     # that it's indeed correct as suspected.
     disk_info = image.diagnostics()
     partitions = disk_info['partitiontable']
     # The device id is unpredictable.
     partitions.pop('id')
     # The partition uuids as well.
     [p.pop('uuid') for p in partitions['partitions']]
     self.maxDiff = None
     self.assertEqual(partitions, {
         'label': 'gpt',
         'device': self.img,
         'unit': 'sectors',
         'firstlba': 34,
         'lastlba': 20446,
         'partitions': [{
             'node': '{}1'.format(self.img),
             'start': 8192,
             'size': 2048,
             'type': '21686148-6449-6E6F-744E-656564454649',
             'name': 'grub',
             }, {
             'node': '{}2'.format(self.img),
             'start': 10240,
             'size': 8192,
             'type': '0FC63DAF-8483-4772-8E79-3D69D8477DE4',
             }],
         })
Пример #26
0
#!/usr/bin/python3

from ubuntu_image.image import Diagnostics, GiB, Image

# Create empty image of a fixed size
# TODO: compute rough good size after seeing all the required snaps.

image = Image('img', GiB(4))

# Install GRUB to MBR
# TODO: this has to be represented in the image.yaml
# NOTE: the boot.img has to be a part of the gadget snap itself
# FIXME: embed a pointer to 2nd stage in bios-boot partition
image.copy_blob('blogs/img.mbr', bs=446, count=1, conv='notrunc')

# Create BIOS boot partition
#
# The partition is 1MiB in size, as recommended by various partitioning guides.
# The actual required size is much, much smaller.
#
# https://www.gnu.org/software/grub/manual/html_node/BIOS-installation.html#BIOS-installation
image.partition(new='1:4MiB:+1MiB')
image.partition(typecode='1:21686148-6449-6E6F-744E-656564454649')
image.partition(change_name='1:grub')
image.copy_blob('blobs/img.bios-boot',
                bs='1MiB',
                seek=4,
                count=1,
                conv='notrunc')

# Create EFI system partition
Пример #27
0
 def _populate_one_volume(self, name, volume):
     # For the LK bootloader we need to copy boot.img and snapbootsel.bin to
     # the gadget folder so they can be used as partition content. The first
     # one comes from the kernel snap, while the second one is modified by
     # 'snap prepare-image' to set the right core and kernel for the kernel
     # command line.
     if volume.bootloader is BootLoader.lk:
         boot = os.path.join(self.unpackdir, 'image', 'boot', 'lk')
         gadget = os.path.join(self.unpackdir, 'gadget')
         if os.path.isdir(boot):
             os.makedirs(gadget, exist_ok=True)
             for filename in os.listdir(boot):
                 src = os.path.join(boot, filename)
                 dst = os.path.join(gadget, filename)
                 shutil.copy(src, dst)
     for partnum, part in enumerate(volume.structures):
         part_img = volume.part_images[partnum]
         # In seeded images, the system-seed partition is basically the
         # rootfs partition - at least from the ubuntu-image POV.
         if part.role is StructureRole.system_seed:
             part_dir = self.rootfs
         else:
             part_dir = os.path.join(volume.basedir,
                                     'part{}'.format(partnum))
         if part.role is StructureRole.system_data:
             # The root partition needs to be ext4, which may or may not be
             # populated at creation time, depending on the version of
             # e2fsprogs.
             mkfs_ext4(part_img,
                       self.rootfs,
                       self.args.cmd,
                       part.filesystem_label,
                       preserve_ownership=True)
         elif part.filesystem is FileSystemType.none:
             image = Image(part_img, part.size)
             offset = 0
             for content in part.content:
                 src = os.path.join(self.unpackdir, 'gadget', content.image)
                 file_size = os.path.getsize(src)
                 assert content.size is None or content.size >= file_size, (
                     'Spec size {} < actual size {} of: {}'.format(
                         content.size, file_size, content.image))
                 if content.size is not None:
                     file_size = content.size
                 # TODO: We need to check for overlapping images.
                 if content.offset is not None:
                     offset = content.offset
                 end = offset + file_size
                 if end > part.size:
                     if part.name is None:
                         if part.role is None:
                             whats_wrong = part.type
                         else:
                             whats_wrong = part.role.value
                     else:
                         whats_wrong = part.name
                     part_path = 'volumes:<{}>:structure:<{}>'.format(
                         name, whats_wrong)
                     self.exitcode = 1
                     raise DoesNotFit(partnum, part_path, end - part.size)
                 image.copy_blob(src, bs=1, seek=offset, conv='notrunc')
                 offset += file_size
         elif part.filesystem is FileSystemType.vfat:
             sourcefiles = SPACE.join(
                 os.path.join(part_dir, filename)
                 for filename in os.listdir(part_dir))
             env = dict(MTOOLS_SKIP_CHECK='1')
             env.update(os.environ)
             run('mcopy -s -i {} {} ::'.format(part_img, sourcefiles),
                 env=env)
         elif part.filesystem is FileSystemType.ext4:
             mkfs_ext4(part_img, part_dir, self.args.cmd,
                       part.filesystem_label)
         else:
             raise AssertionError('Invalid part filesystem type: {}'.format(
                 part.filesystem))
Пример #28
0
 def test_initialize_partition_table_mbr(self):
     image = Image(self.img, MiB(10), VolumeSchema.mbr)
     self.assertTrue(os.path.exists(image.path))
     self.assertEqual(os.stat(image.path).st_size, 10485760)
     self.assertEqual(image.disk.type, 'msdos')
     self.assertEqual(image.schema, VolumeSchema.mbr)
Пример #29
0
 def test_initialize_smaller(self):
     image = Image(self.img, MiB(4.5))
     self.assertTrue(os.path.exists(image.path))
     # MiB == 1024**2; 4.5MiB == 4718592 bytes.
     self.assertEqual(os.stat(image.path).st_size, 4718592)
Пример #30
0
 def test_device_schema_required(self):
     # With no schema, the device cannot be partitioned.
     image = Image(self.img, MiB(1))
     self.assertRaises(TypeError, image.partition, 256, 512)
Пример #31
0
 def make_disk(self):
     self.disk_img = os.path.join(self.images, 'disk.img')
     image = Image(self.disk_img, GiB(4))
     # Create BIOS boot partition
     #
     # The partition is 1MiB in size, as recommended by various
     # partitioning guides.  The actual required size is much, much
     # smaller.
     #
     # https://www.gnu.org/software/grub/manual/html_node/BIOS-installation.html#BIOS-installation
     # image.partition(new='1:4MiB:+1MiB')
     # image.partition(typecode='1:21686148-6449-6E6F-744E-656564454649')
     # image.partition(change_name='1:grub')
     # image.copy_blob(self.boot_img,
     #                 bs='1MiB', seek=4, count=1, conv='notrunc')
     # Create EFI system partition
     #
     # TODO: switch to 512MiB as recommended by the standard
     image.partition(new='2:5MiB:+64MiB')
     image.partition(typecode='2:C12A7328-F81F-11D2-BA4B-00A0C93EC93B')
     image.partition(change_name='2:system-boot')
     image.copy_blob(self.boot_img,
                     bs='1MB', seek=4, count=64, conv='notrunc')
     # Create main snappy writable partition
     image.partition(new='3:72MiB:+3646MiB')
     image.partition(typecode='3:0FC63DAF-8483-4772-8E79-3D69D8477DE4')
     image.partition(change_name='3:writable')
     image.copy_blob(self.root_img,
                     bs='1MiB', seek=72, count=3646, conv='notrunc')
     self._next.append(self.finish)
Пример #32
0
#!/usr/bin/python3

from ubuntu_image.image import Diagnostics, GiB, Image

# Create empty image of a fixed size
# TODO: compute rough good size after seeing all the required snaps.

image = Image('img', GiB(4))

# Install GRUB to MBR
# TODO: this has to be represented in the image.yaml
# NOTE: the boot.img has to be a part of the gadget snap itself
# FIXME: embed a pointer to 2nd stage in bios-boot partition
image.copy_blob('blogs/img.mbr', bs=446, count=1, conv='notrunc')

# Create BIOS boot partition
#
# The partition is 1MiB in size, as recommended by various partitioning guides.
# The actual required size is much, much smaller.
#
# https://www.gnu.org/software/grub/manual/html_node/BIOS-installation.html#BIOS-installation
image.partition(new='1:4MiB:+1MiB')
image.partition(typecode='1:21686148-6449-6E6F-744E-656564454649')
image.partition(change_name='1:grub')
image.copy_blob('blobs/img.bios-boot',
                bs='1MiB', seek=4, count=1, conv='notrunc')

# Create EFI system partition
#
# TODO: switch to 512MiB as recommended by the standard
image.partition(new='2:5MiB:+64MiB')
Пример #33
0
 def test_mbr_image_partitions(self):
     image = Image(self.img, MiB(2), VolumeSchema.mbr)
     # Create the first partition.
     image.partition(offset=image.sector(33),
                     size=image.sector(3000),
                     is_bootable=True)
     self.assertEqual(len(image.disk.partitions), 1)
     # Append the next one.
     image.partition(offset=image.sector(3033), size=image.sector(1000))
     self.assertEqual(len(image.disk.partitions), 2)
     image.set_parition_type(1, '83')
     image.set_parition_type(2, 'dd')
     disk_info = image.diagnostics()
     partitions = disk_info['partitiontable']
     # The device id is unpredictable.
     partitions.pop('id')
     # XXX: In later versions of pyparted the partitiontable structure
     #  added a 'grain' entry that we're not really interested in.
     #  Remove it so we can have the tests working for all series.
     if 'grain' in partitions:
         partitions.pop('grain')
     # Newer sfdisk displays an additional field of 'sectorsize' that
     # we're not really interested in.
     partitions.pop('sectorsize', None)
     self.assertEqual(
         partitions, {
             'label':
             'dos',
             'device':
             self.img,
             'unit':
             'sectors',
             'partitions': [{
                 'node': '{}1'.format(self.img),
                 'start': 33,
                 'size': 3000,
                 'type': '83',
                 'bootable': True,
             }, {
                 'node': '{}2'.format(self.img),
                 'start': 3033,
                 'size': 1000,
                 'type': 'dd',
             }],
         })
Пример #34
0
 def test_initialize(self):
     image = Image(self.img, GiB(1.25))
     self.assertTrue(os.path.exists(image.path))
     # GiB == 1024**3; 1.25GiB == 1342177280 bytes.
     self.assertEqual(os.stat(image.path).st_size, 1342177280)
Пример #35
0
 def test_write_value_at_offset_past_end(self):
     image = Image(self.img, 10000)
     self.assertRaises(ValueError, image.write_value_at_offset, 801, 130031)
     # And the file's size hasn't changed.
     self.assertEqual(os.path.getsize(self.img), 10000)
Пример #36
0
 def test_mbr_image_partitions(self):
     image = Image(self.img, MiB(2), VolumeSchema.mbr)
     # Create the first partition.
     image.partition(offset=image.sector(33),
                     size=image.sector(3000),
                     is_bootable=True)
     self.assertEqual(len(image.disk.partitions), 1)
     # Append the next one.
     image.partition(offset=image.sector(3033),
                     size=image.sector(1000))
     self.assertEqual(len(image.disk.partitions), 2)
     image.set_parition_type(1, '83')
     image.set_parition_type(2, 'dd')
     disk_info = image.diagnostics()
     partitions = disk_info['partitiontable']
     # The device id is unpredictable.
     partitions.pop('id')
     # XXX: In later versions of pyparted the partitiontable structure
     #  added a 'grain' entry that we're not really interested in.
     #  Remove it so we can have the tests working for all series.
     if 'grain' in partitions:
         partitions.pop('grain')
     self.assertEqual(partitions, {
         'label': 'dos',
         'device': self.img,
         'unit': 'sectors',
         'partitions': [{
             'node': '{}1'.format(self.img),
             'start': 33,
             'size': 3000,
             'type': '83',
             'bootable': True,
             }, {
             'node': '{}2'.format(self.img),
             'start': 3033,
             'size': 1000,
             'type': 'dd',
             }],
         })
Пример #37
0
    def make_disk(self):
        self.disk_img = os.path.join(self.images, 'disk.img')
        part_id = 1
        # Walk through all partitions and write them to the disk image at the
        # lowest permissible offset.  We should not have any overlapping
        # partitions, the parser should have already rejected such as invalid.
        #
        # XXX: The parser should sort these partitions for us in disk order as
        # part of checking for overlaps, so we should not need to sort them
        # here.
        volumes = self.gadget.volumes.values()
        assert len(volumes) == 1, 'For now, only one volume is allowed'
        volume = list(volumes)[0]
        # XXX: This ought to be a single constructor that figures out the
        # class for us when we pass in the schema.
        if volume.schema == VolumeSchema.mbr:
            image = MBRImage(self.disk_img, 4000000000)
        else:
            image = Image(self.disk_img, 4000000000)

        structures = sorted(volume.structures, key=attrgetter('offset'))
        offset_writes = []
        part_offsets = {}
        for i, part in enumerate(structures):
            if part.name:  # pragma: nocover
                part_offsets[part.name] = part.offset
            if part.offset_write:  # pragma: nocover
                offset_writes.append((part.offset, part.offset_write))
            image.copy_blob(self.boot_images[i],
                            bs='1M',
                            seek=part.offset // MiB(1),
                            count=ceil(part.size / MiB(1)),
                            conv='notrunc')
            if part.type == 'mbr':
                continue  # pragma: nocover
            # sgdisk takes either a sector or a KiB/MiB argument; assume
            # that the offset and size are always multiples of 1MiB.
            partdef = '{}M:+{}M'.format(part.offset // MiB(1),
                                        part.size // MiB(1))
            part_args = {}
            part_args['new'] = partdef
            part_args['typecode'] = part.type
            # XXX: special-casing.
            if (volume.schema == VolumeSchema.mbr
                    and part.filesystem_label == 'system-boot'):
                part_args['activate'] = True
            if part.name is not None:  # pragma: nobranch
                part_args['change_name'] = part.name
            image.partition(part_id, **part_args)
            part_id += 1
            next_offset = (part.offset + part.size) // MiB(1)
        # Create main snappy writable partition
        image.partition(part_id,
                        new='{}M:+{}M'.format(next_offset, self.rootfs_size),
                        typecode=('83',
                                  '0FC63DAF-8483-4772-8E79-3D69D8477DE4'))
        if volume.schema == VolumeSchema.gpt:
            image.partition(part_id, change_name='writable')
        image.copy_blob(self.root_img,
                        bs='1M',
                        seek=next_offset,
                        count=self.rootfs_size,
                        conv='notrunc')
        for value, dest in offset_writes:  # pragma: nobranch
            # decipher non-numeric offset_write values
            if isinstance(dest, tuple):  # pragma: nocover
                dest = part_offsets[dest[0]] + dest[1]
            # XXX: Hard-coding of 512-byte sectors.
            image.write_value_at_offset(value // 512, dest)
        self._next.append(self.finish)
Пример #38
0
 def test_mbr_image_partitions(self):
     image = Image(self.img, MiB(2), VolumeSchema.mbr)
     # Create the first partition.
     image.partition(offset=image.sector(33),
                     size=image.sector(3000),
                     is_bootable=True)
     self.assertEqual(len(image.disk.partitions), 1)
     # Append the next one.
     image.partition(offset=image.sector(3033), size=image.sector(1000))
     self.assertEqual(len(image.disk.partitions), 2)
     image.set_parition_type(1, '83')
     image.set_parition_type(2, 'dd')
     disk_info = image.diagnostics()
     partitions = disk_info['partitiontable']
     # The device id is unpredictable.
     partitions.pop('id')
     self.assertEqual(
         partitions, {
             'label':
             'dos',
             'device':
             self.img,
             'unit':
             'sectors',
             'partitions': [{
                 'node': '{}1'.format(self.img),
                 'start': 33,
                 'size': 3000,
                 'type': '83',
                 'bootable': True,
             }, {
                 'node': '{}2'.format(self.img),
                 'start': 3033,
                 'size': 1000,
                 'type': 'dd',
             }],
         })