summaryrefslogtreecommitdiff
path: root/extensions/partitioning.py
blob: 1c2956975c2f7317bb61f7fdea050f861b3ba8ef (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
#!/usr/bin/python
# Copyright (C) 2015  Codethink Limited
#
# 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; version 2 of the License.
#
# 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/>.


"""A module providing Baserock-specific partitioning functions"""

import os
import pyfdisk
import re
import subprocess
import writeexts

def do_partitioning(location, disk_size, temp_root, part_spec):
    '''Perform partitioning

    Perform partitioning using the pyfdisk.py module. Documentation
    for this, and guidance on how to create a partition specification can
    be found in extensions/pyfdisk.README

    This function also validates essential parts of the partition layout

    Args:
        location: Path to the target device or image
        temp_root: Location of the unpacked Baserock rootfs
        part_spec: Path to a YAML formatted partition specification
    Returns:
        A pyfdisk.py Device object
    Raises:
        writeexts.ExtensionError
    '''
    # Create partition table and filesystems
    try:
        dev = pyfdisk.load_yaml(location, disk_size, part_spec)
        writeexts.Extension.status(msg='Loaded partition specification: %s' %
                                        part_spec)

        # FIXME: GPT currently not fully supported due to missing tools
        if dev.partition_table_format.lower() == 'gpt':
            writeexts.Extension.status(msg='WARNING: GPT partition tables '
                                           'are not currently supported, '
                                           'when using the extlinux '
                                           'bootloader')

        writeexts.Extension.status(msg='Summary:\n' + str(dev.partitionlist))
        writeexts.Extension.status(msg='Writing partition table')
        dev.commit()
        dev.create_filesystems(skip=('/'))
    except (pyfdisk.PartitioningError, pyfdisk.FdiskError) as e:
        raise writeexts.ExtensionError(e.msg)

    mountpoints = set(part.mountpoint for part in dev.partitionlist
                                      if hasattr(part, 'mountpoint'))
    if '/' not in mountpoints:
        raise writeexts.ExtensionError('No partition with root '
                                       'mountpoint, please specify a '
                                       'partition with \'mountpoint: /\' '
                                       'in the partition specification')

    mounted_partitions = set(part for part in dev.partitionlist
                             if hasattr(part, 'mountpoint'))

    for part in mounted_partitions:
        if not hasattr(part, 'filesystem'):
            raise writeexts.ExtensionError('Cannot mount a partition '
                                 'without filesystem, please specify one '
                                 'for \'%s\' partition in the partition '
                                 'specification' % part.mountpoint)
        if part.mountpoint == '/':
            # Check that bootable flag is set for MBR devices
            if (hasattr(part, 'boot')
                  and str(part.boot).lower() not in ('yes', 'true')
                  and dev.partition_table_format.lower() == 'mbr'):
                writeexts.Extension.status(msg='WARNING: Boot partition '
                                               'needs bootable flag set to '
                                               'boot with extlinux/syslinux')

    return dev

def process_raw_files(dev, temp_root):
    if hasattr(dev, 'raw_files'):
        write_raw_files(dev.location, temp_root, dev)
    for part in dev.partitionlist:
        if hasattr(part, 'raw_files'):
            # dd seek=n is used, which skips n blocks before writing,
            # so we must skip n-1 sectors before writing in order to
            # start writing files to the first block of the partition
            write_raw_files(dev.location, temp_root, part,
                            (part.extent.start - 1) * dev.sector_size)

def write_raw_files(location, temp_root, dev_or_part, start_offset=0):
    '''Write files with `dd`'''
    offset = 0
    for raw_args in dev_or_part.raw_files:
        r = RawFile(temp_root, start_offset, offset, **raw_args)
        offset = r.next_offset
        r.dd(location)


class RawFile(object):
    '''A class to hold information about a raw file to write to a device'''

    def __init__(self, source_root,
                 start_offset=0, wr_offset=0,
                 sector_size=512, **kwargs):
        '''Initialisation function

        Args:
            source_root: Base path for filenames
            wr_offset: Offset to write to (and offset per-file offsets by)
            sector_size: Device sector size (default: 512)
            **kwargs:
                file: A path to the file to write (combined with source_root)
                offset_sectors: An offset to write to in sectors (optional)
                offset_bytes: An offset to write to in bytes (optional)
        '''
        if 'file' not in kwargs:
            raise writeexts.ExtensionError('Missing file name or path')
        self.path = os.path.join(source_root,
                                 re.sub('^/', '', kwargs['file']))

        if not os.path.exists(self.path):
            raise writeexts.ExtensionError('File not found: %s' % self.path)
        elif os.path.isdir(self.path):
            raise writeexts.ExtensionError('Can only dd regular files')
        else:
            self.size = os.stat(self.path).st_size

        self.offset = start_offset
        if 'offset_bytes' in kwargs:
            self.offset += pyfdisk.human_size(kwargs['offset_bytes'])
        elif 'offset_sectors' in kwargs:
            self.offset += kwargs['offset_sectors'] * sector_size
        else:
            self.offset += wr_offset

        self.skip = pyfdisk.human_size(kwargs.get('skip_bytes', 0))
        self.count = pyfdisk.human_size(kwargs.get('count_bytes', self.size))

        # Offset of the first free byte after this file (first byte of next)
        self.next_offset = self.size + self.offset

    def dd(self, location):
        writeexts.Extension.status(msg='Writing %s at %d bytes' %
                                       (self.path, self.offset))
        subprocess.check_call(['dd', 'if=%s' % self.path,
                                     'of=%s' % location, 'bs=1',
                                     'seek=%d' % self.offset,
                                     'skip=%d' % self.skip,
                                     'count=%d' % self.count,
                                     'conv=notrunc'])
        subprocess.check_call('sync')