diff options
author | Tudor Florea <tudor.florea@enea.com> | 2014-10-16 03:05:19 +0200 |
---|---|---|
committer | Tudor Florea <tudor.florea@enea.com> | 2014-10-16 03:05:19 +0200 |
commit | c527fd1f14c27855a37f2e8ac5346ce8d940ced2 (patch) | |
tree | bb002c1fdf011c41dbd2f0927bed23ecb5f83c97 /scripts/lib/mic/utils/partitionedfs.py | |
download | poky-c527fd1f14c27855a37f2e8ac5346ce8d940ced2.tar.gz |
initial commit for Enea Linux 4.0-140929daisy-140929
Migrated from the internal git server on the daisy-enea-point-release branch
Signed-off-by: Tudor Florea <tudor.florea@enea.com>
Diffstat (limited to 'scripts/lib/mic/utils/partitionedfs.py')
-rw-r--r-- | scripts/lib/mic/utils/partitionedfs.py | 782 |
1 files changed, 782 insertions, 0 deletions
diff --git a/scripts/lib/mic/utils/partitionedfs.py b/scripts/lib/mic/utils/partitionedfs.py new file mode 100644 index 0000000000..6607466a83 --- /dev/null +++ b/scripts/lib/mic/utils/partitionedfs.py | |||
@@ -0,0 +1,782 @@ | |||
1 | #!/usr/bin/python -tt | ||
2 | # | ||
3 | # Copyright (c) 2009, 2010, 2011 Intel, Inc. | ||
4 | # Copyright (c) 2007, 2008 Red Hat, Inc. | ||
5 | # Copyright (c) 2008 Daniel P. Berrange | ||
6 | # Copyright (c) 2008 David P. Huff | ||
7 | # | ||
8 | # This program is free software; you can redistribute it and/or modify it | ||
9 | # under the terms of the GNU General Public License as published by the Free | ||
10 | # Software Foundation; version 2 of the License | ||
11 | # | ||
12 | # This program is distributed in the hope that it will be useful, but | ||
13 | # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY | ||
14 | # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | ||
15 | # for more details. | ||
16 | # | ||
17 | # You should have received a copy of the GNU General Public License along | ||
18 | # with this program; if not, write to the Free Software Foundation, Inc., 59 | ||
19 | # Temple Place - Suite 330, Boston, MA 02111-1307, USA. | ||
20 | |||
21 | import os | ||
22 | |||
23 | from mic import msger | ||
24 | from mic.utils import runner | ||
25 | from mic.utils.errors import MountError | ||
26 | from mic.utils.fs_related import * | ||
27 | from mic.utils.gpt_parser import GptParser | ||
28 | from mic.utils.oe.misc import * | ||
29 | |||
30 | # Overhead of the MBR partitioning scheme (just one sector) | ||
31 | MBR_OVERHEAD = 1 | ||
32 | # Overhead of the GPT partitioning scheme | ||
33 | GPT_OVERHEAD = 34 | ||
34 | |||
35 | # Size of a sector in bytes | ||
36 | SECTOR_SIZE = 512 | ||
37 | |||
38 | class PartitionedMount(Mount): | ||
39 | def __init__(self, mountdir, skipformat = False): | ||
40 | Mount.__init__(self, mountdir) | ||
41 | self.disks = {} | ||
42 | self.partitions = [] | ||
43 | self.subvolumes = [] | ||
44 | self.mapped = False | ||
45 | self.mountOrder = [] | ||
46 | self.unmountOrder = [] | ||
47 | self.parted = find_binary_path("parted") | ||
48 | self.btrfscmd=None | ||
49 | self.skipformat = skipformat | ||
50 | self.snapshot_created = self.skipformat | ||
51 | # Size of a sector used in calculations | ||
52 | self.sector_size = SECTOR_SIZE | ||
53 | self._partitions_layed_out = False | ||
54 | |||
55 | def __add_disk(self, disk_name): | ||
56 | """ Add a disk 'disk_name' to the internal list of disks. Note, | ||
57 | 'disk_name' is the name of the disk in the target system | ||
58 | (e.g., sdb). """ | ||
59 | |||
60 | if disk_name in self.disks: | ||
61 | # We already have this disk | ||
62 | return | ||
63 | |||
64 | assert not self._partitions_layed_out | ||
65 | |||
66 | self.disks[disk_name] = \ | ||
67 | { 'disk': None, # Disk object | ||
68 | 'mapped': False, # True if kpartx mapping exists | ||
69 | 'numpart': 0, # Number of allocate partitions | ||
70 | 'partitions': [], # Indexes to self.partitions | ||
71 | 'offset': 0, # Offset of next partition (in sectors) | ||
72 | # Minimum required disk size to fit all partitions (in bytes) | ||
73 | 'min_size': 0, | ||
74 | 'ptable_format': "msdos" } # Partition table format | ||
75 | |||
76 | def add_disk(self, disk_name, disk_obj): | ||
77 | """ Add a disk object which have to be partitioned. More than one disk | ||
78 | can be added. In case of multiple disks, disk partitions have to be | ||
79 | added for each disk separately with 'add_partition()". """ | ||
80 | |||
81 | self.__add_disk(disk_name) | ||
82 | self.disks[disk_name]['disk'] = disk_obj | ||
83 | |||
84 | def __add_partition(self, part): | ||
85 | """ This is a helper function for 'add_partition()' which adds a | ||
86 | partition to the internal list of partitions. """ | ||
87 | |||
88 | assert not self._partitions_layed_out | ||
89 | |||
90 | self.partitions.append(part) | ||
91 | self.__add_disk(part['disk_name']) | ||
92 | |||
93 | def add_partition(self, size, disk_name, mountpoint, source_file = None, fstype = None, | ||
94 | label=None, fsopts = None, boot = False, align = None, | ||
95 | part_type = None): | ||
96 | """ Add the next partition. Prtitions have to be added in the | ||
97 | first-to-last order. """ | ||
98 | |||
99 | ks_pnum = len(self.partitions) | ||
100 | |||
101 | # Converting MB to sectors for parted | ||
102 | size = size * 1024 * 1024 / self.sector_size | ||
103 | |||
104 | # We need to handle subvolumes for btrfs | ||
105 | if fstype == "btrfs" and fsopts and fsopts.find("subvol=") != -1: | ||
106 | self.btrfscmd=find_binary_path("btrfs") | ||
107 | subvol = None | ||
108 | opts = fsopts.split(",") | ||
109 | for opt in opts: | ||
110 | if opt.find("subvol=") != -1: | ||
111 | subvol = opt.replace("subvol=", "").strip() | ||
112 | break | ||
113 | if not subvol: | ||
114 | raise MountError("No subvolume: %s" % fsopts) | ||
115 | self.subvolumes.append({'size': size, # In sectors | ||
116 | 'mountpoint': mountpoint, # Mount relative to chroot | ||
117 | 'fstype': fstype, # Filesystem type | ||
118 | 'fsopts': fsopts, # Filesystem mount options | ||
119 | 'disk_name': disk_name, # physical disk name holding partition | ||
120 | 'device': None, # kpartx device node for partition | ||
121 | 'mount': None, # Mount object | ||
122 | 'subvol': subvol, # Subvolume name | ||
123 | 'boot': boot, # Bootable flag | ||
124 | 'mounted': False # Mount flag | ||
125 | }) | ||
126 | |||
127 | # We still need partition for "/" or non-subvolume | ||
128 | if mountpoint == "/" or not fsopts or fsopts.find("subvol=") == -1: | ||
129 | # Don't need subvolume for "/" because it will be set as default subvolume | ||
130 | if fsopts and fsopts.find("subvol=") != -1: | ||
131 | opts = fsopts.split(",") | ||
132 | for opt in opts: | ||
133 | if opt.strip().startswith("subvol="): | ||
134 | opts.remove(opt) | ||
135 | break | ||
136 | fsopts = ",".join(opts) | ||
137 | |||
138 | part = { 'ks_pnum' : ks_pnum, # Partition number in the KS file | ||
139 | 'size': size, # In sectors | ||
140 | 'mountpoint': mountpoint, # Mount relative to chroot | ||
141 | 'source_file': source_file, # partition contents | ||
142 | 'fstype': fstype, # Filesystem type | ||
143 | 'fsopts': fsopts, # Filesystem mount options | ||
144 | 'label': label, # Partition label | ||
145 | 'disk_name': disk_name, # physical disk name holding partition | ||
146 | 'device': None, # kpartx device node for partition | ||
147 | 'mount': None, # Mount object | ||
148 | 'num': None, # Partition number | ||
149 | 'boot': boot, # Bootable flag | ||
150 | 'align': align, # Partition alignment | ||
151 | 'part_type' : part_type, # Partition type | ||
152 | 'partuuid': None } # Partition UUID (GPT-only) | ||
153 | |||
154 | self.__add_partition(part) | ||
155 | |||
156 | def layout_partitions(self, ptable_format = "msdos"): | ||
157 | """ Layout the partitions, meaning calculate the position of every | ||
158 | partition on the disk. The 'ptable_format' parameter defines the | ||
159 | partition table format, and may be either "msdos" or "gpt". """ | ||
160 | |||
161 | msger.debug("Assigning %s partitions to disks" % ptable_format) | ||
162 | |||
163 | if ptable_format not in ('msdos', 'gpt'): | ||
164 | raise MountError("Unknown partition table format '%s', supported " \ | ||
165 | "formats are: 'msdos' and 'gpt'" % ptable_format) | ||
166 | |||
167 | if self._partitions_layed_out: | ||
168 | return | ||
169 | |||
170 | self._partitions_layed_out = True | ||
171 | |||
172 | # Go through partitions in the order they are added in .ks file | ||
173 | for n in range(len(self.partitions)): | ||
174 | p = self.partitions[n] | ||
175 | |||
176 | if not self.disks.has_key(p['disk_name']): | ||
177 | raise MountError("No disk %s for partition %s" \ | ||
178 | % (p['disk_name'], p['mountpoint'])) | ||
179 | |||
180 | if p['part_type'] and ptable_format != 'gpt': | ||
181 | # The --part-type can also be implemented for MBR partitions, | ||
182 | # in which case it would map to the 1-byte "partition type" | ||
183 | # filed at offset 3 of the partition entry. | ||
184 | raise MountError("setting custom partition type is only " \ | ||
185 | "imlemented for GPT partitions") | ||
186 | |||
187 | # Get the disk where the partition is located | ||
188 | d = self.disks[p['disk_name']] | ||
189 | d['numpart'] += 1 | ||
190 | d['ptable_format'] = ptable_format | ||
191 | |||
192 | if d['numpart'] == 1: | ||
193 | if ptable_format == "msdos": | ||
194 | overhead = MBR_OVERHEAD | ||
195 | else: | ||
196 | overhead = GPT_OVERHEAD | ||
197 | |||
198 | # Skip one sector required for the partitioning scheme overhead | ||
199 | d['offset'] += overhead | ||
200 | # Steal few sectors from the first partition to offset for the | ||
201 | # partitioning overhead | ||
202 | p['size'] -= overhead | ||
203 | |||
204 | if p['align']: | ||
205 | # If not first partition and we do have alignment set we need | ||
206 | # to align the partition. | ||
207 | # FIXME: This leaves a empty spaces to the disk. To fill the | ||
208 | # gaps we could enlargea the previous partition? | ||
209 | |||
210 | # Calc how much the alignment is off. | ||
211 | align_sectors = d['offset'] % (p['align'] * 1024 / self.sector_size) | ||
212 | # We need to move forward to the next alignment point | ||
213 | align_sectors = (p['align'] * 1024 / self.sector_size) - align_sectors | ||
214 | |||
215 | msger.debug("Realignment for %s%s with %s sectors, original" | ||
216 | " offset %s, target alignment is %sK." % | ||
217 | (p['disk_name'], d['numpart'], align_sectors, | ||
218 | d['offset'], p['align'])) | ||
219 | |||
220 | # increase the offset so we actually start the partition on right alignment | ||
221 | d['offset'] += align_sectors | ||
222 | |||
223 | p['start'] = d['offset'] | ||
224 | d['offset'] += p['size'] | ||
225 | |||
226 | p['type'] = 'primary' | ||
227 | p['num'] = d['numpart'] | ||
228 | |||
229 | if d['ptable_format'] == "msdos": | ||
230 | if d['numpart'] > 2: | ||
231 | # Every logical partition requires an additional sector for | ||
232 | # the EBR, so steal the last sector from the end of each | ||
233 | # partition starting from the 3rd one for the EBR. This | ||
234 | # will make sure the logical partitions are aligned | ||
235 | # correctly. | ||
236 | p['size'] -= 1 | ||
237 | |||
238 | if d['numpart'] > 3: | ||
239 | p['type'] = 'logical' | ||
240 | p['num'] = d['numpart'] + 1 | ||
241 | |||
242 | d['partitions'].append(n) | ||
243 | msger.debug("Assigned %s to %s%d, sectors range %d-%d size %d " | ||
244 | "sectors (%d bytes)." \ | ||
245 | % (p['mountpoint'], p['disk_name'], p['num'], | ||
246 | p['start'], p['start'] + p['size'] - 1, | ||
247 | p['size'], p['size'] * self.sector_size)) | ||
248 | |||
249 | # Once all the partitions have been layed out, we can calculate the | ||
250 | # minumim disk sizes. | ||
251 | for disk_name, d in self.disks.items(): | ||
252 | d['min_size'] = d['offset'] | ||
253 | if d['ptable_format'] == 'gpt': | ||
254 | # Account for the backup partition table at the end of the disk | ||
255 | d['min_size'] += GPT_OVERHEAD | ||
256 | |||
257 | d['min_size'] *= self.sector_size | ||
258 | |||
259 | def __run_parted(self, args): | ||
260 | """ Run parted with arguments specified in the 'args' list. """ | ||
261 | |||
262 | args.insert(0, self.parted) | ||
263 | msger.debug(args) | ||
264 | |||
265 | rc, out = runner.runtool(args, catch = 3) | ||
266 | out = out.strip() | ||
267 | if out: | ||
268 | msger.debug('"parted" output: %s' % out) | ||
269 | |||
270 | if rc != 0: | ||
271 | # We don't throw exception when return code is not 0, because | ||
272 | # parted always fails to reload part table with loop devices. This | ||
273 | # prevents us from distinguishing real errors based on return | ||
274 | # code. | ||
275 | msger.debug("WARNING: parted returned '%s' instead of 0" % rc) | ||
276 | |||
277 | def __create_partition(self, device, parttype, fstype, start, size): | ||
278 | """ Create a partition on an image described by the 'device' object. """ | ||
279 | |||
280 | # Start is included to the size so we need to substract one from the end. | ||
281 | end = start + size - 1 | ||
282 | msger.debug("Added '%s' partition, sectors %d-%d, size %d sectors" % | ||
283 | (parttype, start, end, size)) | ||
284 | |||
285 | args = ["-s", device, "unit", "s", "mkpart", parttype] | ||
286 | if fstype: | ||
287 | args.extend([fstype]) | ||
288 | args.extend(["%d" % start, "%d" % end]) | ||
289 | |||
290 | return self.__run_parted(args) | ||
291 | |||
292 | def __format_disks(self): | ||
293 | self.layout_partitions() | ||
294 | |||
295 | if self.skipformat: | ||
296 | msger.debug("Skipping disk format, because skipformat flag is set.") | ||
297 | return | ||
298 | |||
299 | for dev in self.disks.keys(): | ||
300 | d = self.disks[dev] | ||
301 | msger.debug("Initializing partition table for %s" % \ | ||
302 | (d['disk'].device)) | ||
303 | self.__run_parted(["-s", d['disk'].device, "mklabel", | ||
304 | d['ptable_format']]) | ||
305 | |||
306 | msger.debug("Creating partitions") | ||
307 | |||
308 | for p in self.partitions: | ||
309 | d = self.disks[p['disk_name']] | ||
310 | if d['ptable_format'] == "msdos" and p['num'] == 5: | ||
311 | # The last sector of the 3rd partition was reserved for the EBR | ||
312 | # of the first _logical_ partition. This is why the extended | ||
313 | # partition should start one sector before the first logical | ||
314 | # partition. | ||
315 | self.__create_partition(d['disk'].device, "extended", | ||
316 | None, p['start'] - 1, | ||
317 | d['offset'] - p['start']) | ||
318 | |||
319 | if p['fstype'] == "swap": | ||
320 | parted_fs_type = "linux-swap" | ||
321 | elif p['fstype'] == "vfat": | ||
322 | parted_fs_type = "fat32" | ||
323 | elif p['fstype'] == "msdos": | ||
324 | parted_fs_type = "fat16" | ||
325 | else: | ||
326 | # Type for ext2/ext3/ext4/btrfs | ||
327 | parted_fs_type = "ext2" | ||
328 | |||
329 | # Boot ROM of OMAP boards require vfat boot partition to have an | ||
330 | # even number of sectors. | ||
331 | if p['mountpoint'] == "/boot" and p['fstype'] in ["vfat", "msdos"] \ | ||
332 | and p['size'] % 2: | ||
333 | msger.debug("Substracting one sector from '%s' partition to " \ | ||
334 | "get even number of sectors for the partition" % \ | ||
335 | p['mountpoint']) | ||
336 | p['size'] -= 1 | ||
337 | |||
338 | self.__create_partition(d['disk'].device, p['type'], | ||
339 | parted_fs_type, p['start'], p['size']) | ||
340 | |||
341 | if p['boot']: | ||
342 | if d['ptable_format'] == 'gpt': | ||
343 | flag_name = "legacy_boot" | ||
344 | else: | ||
345 | flag_name = "boot" | ||
346 | msger.debug("Set '%s' flag for partition '%s' on disk '%s'" % \ | ||
347 | (flag_name, p['num'], d['disk'].device)) | ||
348 | self.__run_parted(["-s", d['disk'].device, "set", | ||
349 | "%d" % p['num'], flag_name, "on"]) | ||
350 | |||
351 | # Parted defaults to enabling the lba flag for fat16 partitions, | ||
352 | # which causes compatibility issues with some firmware (and really | ||
353 | # isn't necessary). | ||
354 | if parted_fs_type == "fat16": | ||
355 | if d['ptable_format'] == 'msdos': | ||
356 | msger.debug("Disable 'lba' flag for partition '%s' on disk '%s'" % \ | ||
357 | (p['num'], d['disk'].device)) | ||
358 | self.__run_parted(["-s", d['disk'].device, "set", | ||
359 | "%d" % p['num'], "lba", "off"]) | ||
360 | |||
361 | # If the partition table format is "gpt", find out PARTUUIDs for all | ||
362 | # the partitions. And if users specified custom parition type UUIDs, | ||
363 | # set them. | ||
364 | for disk_name, disk in self.disks.items(): | ||
365 | if disk['ptable_format'] != 'gpt': | ||
366 | continue | ||
367 | |||
368 | pnum = 0 | ||
369 | gpt_parser = GptParser(d['disk'].device, SECTOR_SIZE) | ||
370 | # Iterate over all GPT partitions on this disk | ||
371 | for entry in gpt_parser.get_partitions(): | ||
372 | pnum += 1 | ||
373 | # Find the matching partition in the 'self.partitions' list | ||
374 | for n in d['partitions']: | ||
375 | p = self.partitions[n] | ||
376 | if p['num'] == pnum: | ||
377 | # Found, fetch PARTUUID (partition's unique ID) | ||
378 | p['partuuid'] = entry['part_uuid'] | ||
379 | msger.debug("PARTUUID for partition %d on disk '%s' " \ | ||
380 | "(mount point '%s') is '%s'" % (pnum, \ | ||
381 | disk_name, p['mountpoint'], p['partuuid'])) | ||
382 | if p['part_type']: | ||
383 | entry['type_uuid'] = p['part_type'] | ||
384 | msger.debug("Change type of partition %d on disk " \ | ||
385 | "'%s' (mount point '%s') to '%s'" % \ | ||
386 | (pnum, disk_name, p['mountpoint'], | ||
387 | p['part_type'])) | ||
388 | gpt_parser.change_partition(entry) | ||
389 | |||
390 | del gpt_parser | ||
391 | |||
392 | def __map_partitions(self): | ||
393 | """Load it if dm_snapshot isn't loaded. """ | ||
394 | load_module("dm_snapshot") | ||
395 | |||
396 | for dev in self.disks.keys(): | ||
397 | d = self.disks[dev] | ||
398 | if d['mapped']: | ||
399 | continue | ||
400 | |||
401 | msger.debug("Running kpartx on %s" % d['disk'].device ) | ||
402 | rc, kpartxOutput = runner.runtool([self.kpartx, "-l", "-v", d['disk'].device]) | ||
403 | kpartxOutput = kpartxOutput.splitlines() | ||
404 | |||
405 | if rc != 0: | ||
406 | raise MountError("Failed to query partition mapping for '%s'" % | ||
407 | d['disk'].device) | ||
408 | |||
409 | # Strip trailing blank and mask verbose output | ||
410 | i = 0 | ||
411 | while i < len(kpartxOutput) and kpartxOutput[i][0:4] != "loop": | ||
412 | i = i + 1 | ||
413 | kpartxOutput = kpartxOutput[i:] | ||
414 | |||
415 | # Make sure kpartx reported the right count of partitions | ||
416 | if len(kpartxOutput) != d['numpart']: | ||
417 | # If this disk has more than 3 partitions, then in case of MBR | ||
418 | # paritions there is an extended parition. Different versions | ||
419 | # of kpartx behave differently WRT the extended partition - | ||
420 | # some map it, some ignore it. This is why we do the below hack | ||
421 | # - if kpartx reported one more partition and the partition | ||
422 | # table type is "msdos" and the amount of partitions is more | ||
423 | # than 3, we just assume kpartx mapped the extended parition | ||
424 | # and we remove it. | ||
425 | if len(kpartxOutput) == d['numpart'] + 1 \ | ||
426 | and d['ptable_format'] == 'msdos' and len(kpartxOutput) > 3: | ||
427 | kpartxOutput.pop(3) | ||
428 | else: | ||
429 | raise MountError("Unexpected number of partitions from " \ | ||
430 | "kpartx: %d != %d" % \ | ||
431 | (len(kpartxOutput), d['numpart'])) | ||
432 | |||
433 | for i in range(len(kpartxOutput)): | ||
434 | line = kpartxOutput[i] | ||
435 | newdev = line.split()[0] | ||
436 | mapperdev = "/dev/mapper/" + newdev | ||
437 | loopdev = d['disk'].device + newdev[-1] | ||
438 | |||
439 | msger.debug("Dev %s: %s -> %s" % (newdev, loopdev, mapperdev)) | ||
440 | pnum = d['partitions'][i] | ||
441 | self.partitions[pnum]['device'] = loopdev | ||
442 | |||
443 | # grub's install wants partitions to be named | ||
444 | # to match their parent device + partition num | ||
445 | # kpartx doesn't work like this, so we add compat | ||
446 | # symlinks to point to /dev/mapper | ||
447 | if os.path.lexists(loopdev): | ||
448 | os.unlink(loopdev) | ||
449 | os.symlink(mapperdev, loopdev) | ||
450 | |||
451 | msger.debug("Adding partx mapping for %s" % d['disk'].device) | ||
452 | rc = runner.show([self.kpartx, "-v", "-a", d['disk'].device]) | ||
453 | |||
454 | if rc != 0: | ||
455 | # Make sure that the device maps are also removed on error case. | ||
456 | # The d['mapped'] isn't set to True if the kpartx fails so | ||
457 | # failed mapping will not be cleaned on cleanup either. | ||
458 | runner.quiet([self.kpartx, "-d", d['disk'].device]) | ||
459 | raise MountError("Failed to map partitions for '%s'" % | ||
460 | d['disk'].device) | ||
461 | |||
462 | # FIXME: there is a bit delay for multipath device setup, | ||
463 | # wait 10ms for the setup | ||
464 | import time | ||
465 | time.sleep(10) | ||
466 | d['mapped'] = True | ||
467 | |||
468 | def __unmap_partitions(self): | ||
469 | for dev in self.disks.keys(): | ||
470 | d = self.disks[dev] | ||
471 | if not d['mapped']: | ||
472 | continue | ||
473 | |||
474 | msger.debug("Removing compat symlinks") | ||
475 | for pnum in d['partitions']: | ||
476 | if self.partitions[pnum]['device'] != None: | ||
477 | os.unlink(self.partitions[pnum]['device']) | ||
478 | self.partitions[pnum]['device'] = None | ||
479 | |||
480 | msger.debug("Unmapping %s" % d['disk'].device) | ||
481 | rc = runner.quiet([self.kpartx, "-d", d['disk'].device]) | ||
482 | if rc != 0: | ||
483 | raise MountError("Failed to unmap partitions for '%s'" % | ||
484 | d['disk'].device) | ||
485 | |||
486 | d['mapped'] = False | ||
487 | |||
488 | def __calculate_mountorder(self): | ||
489 | msger.debug("Calculating mount order") | ||
490 | for p in self.partitions: | ||
491 | if p['mountpoint']: | ||
492 | self.mountOrder.append(p['mountpoint']) | ||
493 | self.unmountOrder.append(p['mountpoint']) | ||
494 | |||
495 | self.mountOrder.sort() | ||
496 | self.unmountOrder.sort() | ||
497 | self.unmountOrder.reverse() | ||
498 | |||
499 | def cleanup(self): | ||
500 | Mount.cleanup(self) | ||
501 | if self.disks: | ||
502 | self.__unmap_partitions() | ||
503 | for dev in self.disks.keys(): | ||
504 | d = self.disks[dev] | ||
505 | try: | ||
506 | d['disk'].cleanup() | ||
507 | except: | ||
508 | pass | ||
509 | |||
510 | def unmount(self): | ||
511 | self.__unmount_subvolumes() | ||
512 | for mp in self.unmountOrder: | ||
513 | if mp == 'swap': | ||
514 | continue | ||
515 | p = None | ||
516 | for p1 in self.partitions: | ||
517 | if p1['mountpoint'] == mp: | ||
518 | p = p1 | ||
519 | break | ||
520 | |||
521 | if p['mount'] != None: | ||
522 | try: | ||
523 | # Create subvolume snapshot here | ||
524 | if p['fstype'] == "btrfs" and p['mountpoint'] == "/" and not self.snapshot_created: | ||
525 | self.__create_subvolume_snapshots(p, p["mount"]) | ||
526 | p['mount'].cleanup() | ||
527 | except: | ||
528 | pass | ||
529 | p['mount'] = None | ||
530 | |||
531 | # Only for btrfs | ||
532 | def __get_subvolume_id(self, rootpath, subvol): | ||
533 | if not self.btrfscmd: | ||
534 | self.btrfscmd=find_binary_path("btrfs") | ||
535 | argv = [ self.btrfscmd, "subvolume", "list", rootpath ] | ||
536 | |||
537 | rc, out = runner.runtool(argv) | ||
538 | msger.debug(out) | ||
539 | |||
540 | if rc != 0: | ||
541 | raise MountError("Failed to get subvolume id from %s', return code: %d." % (rootpath, rc)) | ||
542 | |||
543 | subvolid = -1 | ||
544 | for line in out.splitlines(): | ||
545 | if line.endswith(" path %s" % subvol): | ||
546 | subvolid = line.split()[1] | ||
547 | if not subvolid.isdigit(): | ||
548 | raise MountError("Invalid subvolume id: %s" % subvolid) | ||
549 | subvolid = int(subvolid) | ||
550 | break | ||
551 | return subvolid | ||
552 | |||
553 | def __create_subvolume_metadata(self, p, pdisk): | ||
554 | if len(self.subvolumes) == 0: | ||
555 | return | ||
556 | |||
557 | argv = [ self.btrfscmd, "subvolume", "list", pdisk.mountdir ] | ||
558 | rc, out = runner.runtool(argv) | ||
559 | msger.debug(out) | ||
560 | |||
561 | if rc != 0: | ||
562 | raise MountError("Failed to get subvolume id from %s', return code: %d." % (pdisk.mountdir, rc)) | ||
563 | |||
564 | subvolid_items = out.splitlines() | ||
565 | subvolume_metadata = "" | ||
566 | for subvol in self.subvolumes: | ||
567 | for line in subvolid_items: | ||
568 | if line.endswith(" path %s" % subvol["subvol"]): | ||
569 | subvolid = line.split()[1] | ||
570 | if not subvolid.isdigit(): | ||
571 | raise MountError("Invalid subvolume id: %s" % subvolid) | ||
572 | |||
573 | subvolid = int(subvolid) | ||
574 | opts = subvol["fsopts"].split(",") | ||
575 | for opt in opts: | ||
576 | if opt.strip().startswith("subvol="): | ||
577 | opts.remove(opt) | ||
578 | break | ||
579 | fsopts = ",".join(opts) | ||
580 | subvolume_metadata += "%d\t%s\t%s\t%s\n" % (subvolid, subvol["subvol"], subvol['mountpoint'], fsopts) | ||
581 | |||
582 | if subvolume_metadata: | ||
583 | fd = open("%s/.subvolume_metadata" % pdisk.mountdir, "w") | ||
584 | fd.write(subvolume_metadata) | ||
585 | fd.close() | ||
586 | |||
587 | def __get_subvolume_metadata(self, p, pdisk): | ||
588 | subvolume_metadata_file = "%s/.subvolume_metadata" % pdisk.mountdir | ||
589 | if not os.path.exists(subvolume_metadata_file): | ||
590 | return | ||
591 | |||
592 | fd = open(subvolume_metadata_file, "r") | ||
593 | content = fd.read() | ||
594 | fd.close() | ||
595 | |||
596 | for line in content.splitlines(): | ||
597 | items = line.split("\t") | ||
598 | if items and len(items) == 4: | ||
599 | self.subvolumes.append({'size': 0, # In sectors | ||
600 | 'mountpoint': items[2], # Mount relative to chroot | ||
601 | 'fstype': "btrfs", # Filesystem type | ||
602 | 'fsopts': items[3] + ",subvol=%s" % items[1], # Filesystem mount options | ||
603 | 'disk_name': p['disk_name'], # physical disk name holding partition | ||
604 | 'device': None, # kpartx device node for partition | ||
605 | 'mount': None, # Mount object | ||
606 | 'subvol': items[1], # Subvolume name | ||
607 | 'boot': False, # Bootable flag | ||
608 | 'mounted': False # Mount flag | ||
609 | }) | ||
610 | |||
611 | def __create_subvolumes(self, p, pdisk): | ||
612 | """ Create all the subvolumes. """ | ||
613 | |||
614 | for subvol in self.subvolumes: | ||
615 | argv = [ self.btrfscmd, "subvolume", "create", pdisk.mountdir + "/" + subvol["subvol"]] | ||
616 | |||
617 | rc = runner.show(argv) | ||
618 | if rc != 0: | ||
619 | raise MountError("Failed to create subvolume '%s', return code: %d." % (subvol["subvol"], rc)) | ||
620 | |||
621 | # Set default subvolume, subvolume for "/" is default | ||
622 | subvol = None | ||
623 | for subvolume in self.subvolumes: | ||
624 | if subvolume["mountpoint"] == "/" and p['disk_name'] == subvolume['disk_name']: | ||
625 | subvol = subvolume | ||
626 | break | ||
627 | |||
628 | if subvol: | ||
629 | # Get default subvolume id | ||
630 | subvolid = self. __get_subvolume_id(pdisk.mountdir, subvol["subvol"]) | ||
631 | # Set default subvolume | ||
632 | if subvolid != -1: | ||
633 | rc = runner.show([ self.btrfscmd, "subvolume", "set-default", "%d" % subvolid, pdisk.mountdir]) | ||
634 | if rc != 0: | ||
635 | raise MountError("Failed to set default subvolume id: %d', return code: %d." % (subvolid, rc)) | ||
636 | |||
637 | self.__create_subvolume_metadata(p, pdisk) | ||
638 | |||
639 | def __mount_subvolumes(self, p, pdisk): | ||
640 | if self.skipformat: | ||
641 | # Get subvolume info | ||
642 | self.__get_subvolume_metadata(p, pdisk) | ||
643 | # Set default mount options | ||
644 | if len(self.subvolumes) != 0: | ||
645 | for subvol in self.subvolumes: | ||
646 | if subvol["mountpoint"] == p["mountpoint"] == "/": | ||
647 | opts = subvol["fsopts"].split(",") | ||
648 | for opt in opts: | ||
649 | if opt.strip().startswith("subvol="): | ||
650 | opts.remove(opt) | ||
651 | break | ||
652 | pdisk.fsopts = ",".join(opts) | ||
653 | break | ||
654 | |||
655 | if len(self.subvolumes) == 0: | ||
656 | # Return directly if no subvolumes | ||
657 | return | ||
658 | |||
659 | # Remount to make default subvolume mounted | ||
660 | rc = runner.show([self.umountcmd, pdisk.mountdir]) | ||
661 | if rc != 0: | ||
662 | raise MountError("Failed to umount %s" % pdisk.mountdir) | ||
663 | |||
664 | rc = runner.show([self.mountcmd, "-o", pdisk.fsopts, pdisk.disk.device, pdisk.mountdir]) | ||
665 | if rc != 0: | ||
666 | raise MountError("Failed to umount %s" % pdisk.mountdir) | ||
667 | |||
668 | for subvol in self.subvolumes: | ||
669 | if subvol["mountpoint"] == "/": | ||
670 | continue | ||
671 | subvolid = self. __get_subvolume_id(pdisk.mountdir, subvol["subvol"]) | ||
672 | if subvolid == -1: | ||
673 | msger.debug("WARNING: invalid subvolume %s" % subvol["subvol"]) | ||
674 | continue | ||
675 | # Replace subvolume name with subvolume ID | ||
676 | opts = subvol["fsopts"].split(",") | ||
677 | for opt in opts: | ||
678 | if opt.strip().startswith("subvol="): | ||
679 | opts.remove(opt) | ||
680 | break | ||
681 | |||
682 | opts.extend(["subvolrootid=0", "subvol=%s" % subvol["subvol"]]) | ||
683 | fsopts = ",".join(opts) | ||
684 | subvol['fsopts'] = fsopts | ||
685 | mountpoint = self.mountdir + subvol['mountpoint'] | ||
686 | makedirs(mountpoint) | ||
687 | rc = runner.show([self.mountcmd, "-o", fsopts, pdisk.disk.device, mountpoint]) | ||
688 | if rc != 0: | ||
689 | raise MountError("Failed to mount subvolume %s to %s" % (subvol["subvol"], mountpoint)) | ||
690 | subvol["mounted"] = True | ||
691 | |||
692 | def __unmount_subvolumes(self): | ||
693 | """ It may be called multiple times, so we need to chekc if it is still mounted. """ | ||
694 | for subvol in self.subvolumes: | ||
695 | if subvol["mountpoint"] == "/": | ||
696 | continue | ||
697 | if not subvol["mounted"]: | ||
698 | continue | ||
699 | mountpoint = self.mountdir + subvol['mountpoint'] | ||
700 | rc = runner.show([self.umountcmd, mountpoint]) | ||
701 | if rc != 0: | ||
702 | raise MountError("Failed to unmount subvolume %s from %s" % (subvol["subvol"], mountpoint)) | ||
703 | subvol["mounted"] = False | ||
704 | |||
705 | def __create_subvolume_snapshots(self, p, pdisk): | ||
706 | import time | ||
707 | |||
708 | if self.snapshot_created: | ||
709 | return | ||
710 | |||
711 | # Remount with subvolid=0 | ||
712 | rc = runner.show([self.umountcmd, pdisk.mountdir]) | ||
713 | if rc != 0: | ||
714 | raise MountError("Failed to umount %s" % pdisk.mountdir) | ||
715 | if pdisk.fsopts: | ||
716 | mountopts = pdisk.fsopts + ",subvolid=0" | ||
717 | else: | ||
718 | mountopts = "subvolid=0" | ||
719 | rc = runner.show([self.mountcmd, "-o", mountopts, pdisk.disk.device, pdisk.mountdir]) | ||
720 | if rc != 0: | ||
721 | raise MountError("Failed to umount %s" % pdisk.mountdir) | ||
722 | |||
723 | # Create all the subvolume snapshots | ||
724 | snapshotts = time.strftime("%Y%m%d-%H%M") | ||
725 | for subvol in self.subvolumes: | ||
726 | subvolpath = pdisk.mountdir + "/" + subvol["subvol"] | ||
727 | snapshotpath = subvolpath + "_%s-1" % snapshotts | ||
728 | rc = runner.show([ self.btrfscmd, "subvolume", "snapshot", subvolpath, snapshotpath ]) | ||
729 | if rc != 0: | ||
730 | raise MountError("Failed to create subvolume snapshot '%s' for '%s', return code: %d." % (snapshotpath, subvolpath, rc)) | ||
731 | |||
732 | self.snapshot_created = True | ||
733 | |||
734 | def __install_partition(self, num, source_file, start, size): | ||
735 | """ | ||
736 | Install source_file contents into a partition. | ||
737 | """ | ||
738 | if not source_file: # nothing to install | ||
739 | return | ||
740 | |||
741 | # Start is included in the size so need to substract one from the end. | ||
742 | end = start + size - 1 | ||
743 | msger.debug("Installed %s in partition %d, sectors %d-%d, size %d sectors" % (source_file, num, start, end, size)) | ||
744 | |||
745 | dd_cmd = "dd if=%s of=%s bs=%d seek=%d count=%d conv=notrunc" % \ | ||
746 | (source_file, self.image_file, self.sector_size, start, size) | ||
747 | rc, out = exec_cmd(dd_cmd) | ||
748 | |||
749 | |||
750 | def install(self, image_file): | ||
751 | msger.debug("Installing partitions") | ||
752 | |||
753 | self.image_file = image_file | ||
754 | |||
755 | for p in self.partitions: | ||
756 | d = self.disks[p['disk_name']] | ||
757 | if d['ptable_format'] == "msdos" and p['num'] == 5: | ||
758 | # The last sector of the 3rd partition was reserved for the EBR | ||
759 | # of the first _logical_ partition. This is why the extended | ||
760 | # partition should start one sector before the first logical | ||
761 | # partition. | ||
762 | self.__install_partition(p['num'], p['source_file'], | ||
763 | p['start'] - 1, | ||
764 | d['offset'] - p['start']) | ||
765 | |||
766 | self.__install_partition(p['num'], p['source_file'], | ||
767 | p['start'], p['size']) | ||
768 | |||
769 | def mount(self): | ||
770 | for dev in self.disks.keys(): | ||
771 | d = self.disks[dev] | ||
772 | d['disk'].create() | ||
773 | |||
774 | self.__format_disks() | ||
775 | |||
776 | self.__calculate_mountorder() | ||
777 | |||
778 | return | ||
779 | |||
780 | def resparse(self, size = None): | ||
781 | # Can't re-sparse a disk image - too hard | ||
782 | pass | ||