OSDN Git Service

2020.05.25 update
[rebornos/cnchi-gnome-osdn.git] / Cnchi / auto_partition.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 #
4 # auto_partition.py
5 #
6 # Copyright © 2013-2018 Antergos
7 #
8 # Modify by Rafael from RebornOS in 2020
9 #
10 # This file is part of Cnchi.
11 #
12 # Cnchi is free software; you can redistribute it and/or modify
13 # it under the terms of the GNU General Public License as published by
14 # the Free Software Foundation; either version 3 of the License, or
15 # (at your option) any later version.
16 #
17 # Cnchi is distributed in the hope that it will be useful,
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20 # GNU General Public License for more details.
21 #
22 # The following additional terms are in effect as per Section 7 of the license:
23 #
24 # The preservation of all legal notices and author attributions in
25 # the material or in the Appropriate Legal Notices displayed
26 # by works containing it is required.
27 #
28 # You should have received a copy of the GNU General Public License
29 # along with Cnchi; If not, see <http://www.gnu.org/licenses/>.
30
31 """ AutoPartition module, used by automatic installation """
32
33 import os
34 import logging
35 import math
36
37 from misc.extra import InstallError
38 from misc.run_cmd import call
39 import misc.events as events
40 import parted3.fs_module as fs
41
42 from installation import luks
43 from installation import mount
44 from installation import wrapper
45
46 # When testing, no _() is available
47 try:
48     _("")
49 except NameError as err:
50     def _(message):
51         return message
52
53 # NOTE: Exceptions in this file
54 # On a warning situation, Cnchi should try to continue, so we need to catch
55 # the exception here. If we don't catch the exception here, it will be caught
56 # in process.py and managed as a fatal error. On the other hand, if we want
57 # to clarify the exception message we can catch it here and then raise an
58 # InstallError exception.
59
60 # Partition sizes are in MiB
61 MAX_ROOT_SIZE = 30000
62
63 # KDE (with all features) needs 8 GB for its files (including pacman cache xz files).
64 # Vbox, by default, creates disks of 8GB. We should limit to this so vbox installations do not fail
65 # (if installing kde and not enough free space is available is their fault, not ours)
66 MIN_ROOT_SIZE = 8000
67
68
69 class AutoPartition():
70     """ Class used by the automatic installation method """
71
72     LUKS_KEY_FILES = [".keyfile-root", ".keyfile-home"]
73
74     def __init__(self, dest_dir, auto_device, settings, callback_queue):
75         """ Class initialization """
76
77         self.dest_dir = dest_dir
78         self.auto_device = auto_device
79         self.temp = settings.get('temp')
80
81         # Use LUKS encryption
82         self.luks = settings.get("use_luks")
83         self.luks_password = settings.get("luks_root_password")
84
85         # Use LVM
86         self.lvm = settings.get("use_lvm")
87
88         # Make home a different partition or if using LVM, a different volume
89         self.home = settings.get("use_home")
90
91         self.bootloader = settings.get("bootloader").lower()
92
93         # Will use these queue to show progress info to the user
94         self.events = events.Events(callback_queue)
95
96         if os.path.exists("/sys/firmware/efi"):
97             # If UEFI use GPT
98             self.uefi = True
99             self.gpt = True
100         else:
101             # If BIOS, use MBR
102             self.uefi = False
103             self.gpt = False
104
105     def mkfs(self, device, fs_type, mount_point, label_name, fs_options="", btrfs_devices=""):
106         """ We have two main cases: "swap" and everything else. """
107         logging.debug("Will format device %s as %s", device, fs_type)
108         if fs_type == "swap":
109             err_msg = "Can't activate swap in {0}".format(device)
110             swap_devices = call(["swapon", "-s"], msg=err_msg)
111             if device in swap_devices:
112                 call(["swapoff", device], msg=err_msg)
113             cmd = ["mkswap", "-L", label_name, device]
114             call(cmd, msg=err_msg)
115             cmd = ["swapon", device]
116             call(cmd, msg=err_msg)
117         else:
118             mkfs = {
119                 "xfs": "mkfs.xfs {0} -L {1} -f {2}".format(
120                     fs_options, label_name, device),
121                 "jfs": "yes | mkfs.jfs {0} -L {1} {2}".format(
122                     fs_options, label_name, device),
123                 "reiserfs": "yes | mkreiserfs {0} -l {1} {2}".format(
124                     fs_options, label_name, device),
125                 "ext2": "mkfs.ext2 -q {0} -F -L {1} {2}".format(
126                     fs_options, label_name, device),
127                 "ext3": "mkfs.ext3 -q {0} -F -L {1} {2}".format(
128                     fs_options, label_name, device),
129                 "ext4": "mkfs.ext4 -q {0} -F -L {1} {2}".format(
130                     fs_options, label_name, device),
131                 "btrfs": "mkfs.btrfs {0} -L {1} {2}".format(
132                     fs_options, label_name, btrfs_devices),
133                 "nilfs2": "mkfs.nilfs2 {0} -L {1} {2}".format(
134                     fs_options, label_name, device),
135                 "ntfs-3g": "mkfs.ntfs {0} -f -L {1} {2}".format(
136                     fs_options, label_name, device),
137                 "vfat": "mkfs.vfat {0} -F 32 -n {1} {2}".format(
138                     fs_options, label_name, device),
139                 "fat32": "mkfs.vfat {0} -F 32 -n {1} {2}".format(
140                     fs_options, label_name, device),
141                 "f2fs": "mkfs.f2fs {0} -l {1} {2}".format(
142                     fs_options, label_name, device)}
143
144             # Make sure the fs type is one we can handle
145             if fs_type not in mkfs.keys():
146                 txt = _("Unknown filesystem type {0}").format(fs_type)
147                 raise InstallError(txt)
148
149             command = mkfs[fs_type]
150
151             err_msg = "Can't create filesystem {0}".format(fs_type)
152             call(command.split(), msg=err_msg, fatal=True)
153
154             # Flush filesystem buffers
155             call(["sync"])
156
157             # Create our mount directory
158             path = self.dest_dir + mount_point
159             os.makedirs(path, mode=0o755, exist_ok=True)
160
161             # Mount our new filesystem
162
163             mopts = "rw,relatime"
164             if fs_type == "ext4":
165                 mopts = "rw,relatime,data=ordered"
166             elif fs_type == "btrfs":
167                 mopts = 'rw,relatime,space_cache,autodefrag,inode_cache'
168
169             err_msg = "Error trying to mount {0} in {1}".format(device, path)
170             cmd = ["mount", "-t", fs_type, "-o", mopts, device, path]
171             call(cmd, msg=err_msg, fatal=True)
172
173             # Change permission of base directories to avoid btrfs issues
174             if mount_point == "/tmp":
175                 mode = 0o1777
176             elif mount_point == "/root":
177                 mode = 0o750
178             else:
179                 mode = 0o755
180             os.chmod(path, mode)
181
182         fs_uuid = fs.get_uuid(device)
183         fs_label = fs.get_label(device)
184         msg = "Device details: %s UUID=%s LABEL=%s"
185         logging.debug(msg, device, fs_uuid, fs_label)
186
187     @staticmethod
188     def get_partition_path(device, part_num):
189         """ This is awful and prone to fail. We should do some
190             type of test here """
191
192         # Remove /dev/
193         path = device.replace('/dev/', '')
194         partials = [
195             'rd/', 'ida/', 'cciss/', 'sx8/', 'mapper/', 'mmcblk', 'md', 'nvme']
196         found = [p for p in partials if path.startswith(p)]
197         if found:
198             return "{0}p{1}".format(device, part_num)
199         return "{0}{1}".format(device, part_num)
200
201     def get_devices(self):
202         """ Set (and return) all partitions on the device """
203         devices = {}
204         device = self.auto_device
205         logging.debug(device)
206
207         # device is of type /dev/sdX or /dev/hdX or /dev/mmcblkX
208
209         if self.gpt:
210             if not self.uefi:
211                 # Skip BIOS Boot Partition
212                 # We'll never get here as we use UEFI+GPT or BIOS+MBR
213                 part_num = 2
214             else:
215                 part_num = 1
216
217             if self.bootloader == "grub2":
218                 devices['efi'] = self.get_partition_path(device, part_num)
219                 part_num += 1
220
221             devices['boot'] = self.get_partition_path(device, part_num)
222             part_num += 1
223             devices['root'] = self.get_partition_path(device, part_num)
224             part_num += 1
225             if self.home:
226                 devices['home'] = self.get_partition_path(device, part_num)
227                 part_num += 1
228             devices['swap'] = self.get_partition_path(device, part_num)
229         else:
230             devices['boot'] = self.get_partition_path(device, 1)
231             devices['root'] = self.get_partition_path(device, 2)
232             if self.home:
233                 devices['home'] = self.get_partition_path(device, 3)
234             devices['swap'] = self.get_partition_path(device, 5)
235
236         if self.luks:
237             if self.lvm:
238                 # LUKS and LVM
239                 devices['luks_root'] = devices['root']
240                 devices['lvm'] = "/dev/mapper/cryptRebornOS"
241             else:
242                 # LUKS and no LVM
243                 devices['luks_root'] = devices['root']
244                 devices['root'] = "/dev/mapper/cryptRebornOS"
245                 if self.home:
246                     # In this case we'll have two LUKS devices, one for root
247                     # and the other one for /home
248                     devices['luks_home'] = devices['home']
249                     devices['home'] = "/dev/mapper/cryptRebornOSHome"
250         elif self.lvm:
251             # No LUKS but using LVM
252             devices['lvm'] = devices['root']
253
254         if self.lvm:
255             devices['root'] = "/dev/RebornOSVG/RebornOSRoot"
256             devices['swap'] = "/dev/RebornOSVG/RebornOSSwap"
257             if self.home:
258                 devices['home'] = "/dev/RebornOSVG/RebornOSHome"
259
260         return devices
261
262     def get_mount_devices(self):
263         """ Specify for each mount point which device we must mount there """
264
265         devices = self.get_devices()
266         mount_devices = {}
267
268         if self.gpt and self.bootloader == "grub2":
269             mount_devices['/boot/efi'] = devices['efi']
270
271         mount_devices['/boot'] = devices['boot']
272         mount_devices['/'] = devices['root']
273
274         if self.home:
275             mount_devices['/home'] = devices['home']
276
277         if self.luks:
278             mount_devices['/'] = devices['luks_root']
279             if self.home and not self.lvm:
280                 mount_devices['/home'] = devices['luks_home']
281
282         mount_devices['swap'] = devices['swap']
283
284         for mount_device in mount_devices:
285             logging.debug(
286                 "%s assigned to be mounted in %s",
287                 mount_devices[mount_device],
288                 mount_device)
289
290         return mount_devices
291
292     def get_fs_devices(self):
293         """ Return which filesystem is in a selected device """
294
295         devices = self.get_devices()
296
297         fs_devices = {}
298
299         if self.gpt:
300             if self.bootloader == "grub2":
301                 fs_devices[devices['efi']] = "vfat"
302                 fs_devices[devices['boot']] = "ext4"
303             elif self.bootloader in ["systemd-boot", "refind"]:
304                 fs_devices[devices['boot']] = "vfat"
305         else:
306             if self.uefi:
307                 fs_devices[devices['boot']] = "vfat"
308             else:
309                 fs_devices[devices['boot']] = "ext4"
310
311         fs_devices[devices['swap']] = "swap"
312         fs_devices[devices['root']] = "ext4"
313
314         if self.home:
315             fs_devices[devices['home']] = "ext4"
316
317         if self.luks:
318             fs_devices[devices['luks_root']] = "ext4"
319             if self.home:
320                 if self.lvm:
321                     # luks, lvm, home
322                     fs_devices[devices['home']] = "ext4"
323                 else:
324                     # luks, home
325                     fs_devices[devices['luks_home']] = "ext4"
326
327         for device in fs_devices:
328             logging.debug("Device %s will have a %s filesystem",
329                           device, fs_devices[device])
330
331         return fs_devices
332
333     def get_part_sizes(self, disk_size, start_part_sizes=1):
334         """ Returns a dict with all partition sizes """
335         part_sizes = {'disk': disk_size, 'boot': 512, 'efi': 0}
336
337         if self.gpt and self.bootloader == "grub2":
338             part_sizes['efi'] = 512
339
340         cmd = ["grep", "MemTotal", "/proc/meminfo"]
341         mem_total = call(cmd)
342         mem_total = int(mem_total.split()[1])
343         mem = mem_total / 1024
344
345         # Suggested swap sizes from Anaconda installer
346         if mem < 2048:
347             part_sizes['swap'] = 2 * mem
348         elif 2048 <= mem < 8192:
349             part_sizes['swap'] = mem
350         elif 8192 <= mem < 65536:
351             part_sizes['swap'] = mem // 2
352         else:
353             part_sizes['swap'] = 4096
354
355         # Max swap size is 10% of all available disk size
356         max_swap = disk_size * 0.1
357         if part_sizes['swap'] > max_swap:
358             part_sizes['swap'] = max_swap
359
360         part_sizes['swap'] = math.ceil(part_sizes['swap'])
361
362         other_than_root_size = start_part_sizes + \
363             part_sizes['efi'] + part_sizes['boot'] + part_sizes['swap']
364         part_sizes['root'] = disk_size - other_than_root_size
365
366         if self.home:
367             # Decide how much we leave to root and how much we leave to /home
368             # Question: why 5?
369             new_root_part_size = part_sizes['root'] // 5
370             if new_root_part_size > MAX_ROOT_SIZE:
371                 new_root_part_size = MAX_ROOT_SIZE
372             elif new_root_part_size < MIN_ROOT_SIZE:
373                 new_root_part_size = MIN_ROOT_SIZE
374
375             if new_root_part_size >= part_sizes['root']:
376                 # new_root_part_size can't be bigger than part_sizes['root'] !
377                 # this could happen if new_root_part_size == MIN_ROOT_SIZE but
378                 # our harddisk is smaller (detected using vbox)
379                 # Should we fail here or install without a separated /home partition?
380                 logging.warning(
381                     "There's not enough free space to have a separate /home partition")
382                 self.home = False
383                 part_sizes['home'] = 0
384             else:
385                 part_sizes['home'] = part_sizes['root'] - new_root_part_size
386                 part_sizes['root'] = new_root_part_size
387         else:
388             part_sizes['home'] = 0
389
390         part_sizes['lvm_pv'] = part_sizes['swap'] + \
391             part_sizes['root'] + part_sizes['home']
392
393         for part in part_sizes:
394             part_sizes[part] = int(part_sizes[part])
395
396         return part_sizes
397
398     def log_part_sizes(self, part_sizes):
399         """ Log partition sizes for debugging purposes """
400         logging.debug("Total disk size: %dMiB", part_sizes['disk'])
401         if self.gpt and self.bootloader == "grub2":
402             logging.debug(
403                 "EFI System Partition (ESP) size: %dMiB", part_sizes['efi'])
404         logging.debug("Boot partition size: %dMiB", part_sizes['boot'])
405
406         if self.lvm:
407             logging.debug("LVM physical volume size: %dMiB",
408                           part_sizes['lvm_pv'])
409
410         logging.debug("Swap partition size: %dMiB", part_sizes['swap'])
411         logging.debug("Root partition size: %dMiB", part_sizes['root'])
412
413         if self.home:
414             logging.debug("Home partition size: %dMiB", part_sizes['home'])
415
416     def get_disk_size(self):
417         """ Gets disk size in MiB """
418         # Partition sizes are expressed in MiB
419         # Get just the disk size in MiB
420         device = self.auto_device
421         device_name = os.path.split(device)[1]
422         size_path = os.path.join("/sys/block", device_name, 'size')
423         base_path = os.path.split(size_path)[0]
424         disk_size = 0
425         if os.path.exists(size_path):
426             logical_path = os.path.join(base_path, "queue/logical_block_size")
427             with open(logical_path, 'r') as logical_file:
428                 logical_block_size = int(logical_file.read())
429             with open(size_path, 'r') as size_file:
430                 size = int(size_file.read())
431             disk_size = ((logical_block_size * (size - 68)) / 1024) / 1024
432         else:
433             logging.error("Cannot detect %s device size", device)
434             txt = _("Setup cannot detect size of your device, please use advanced "
435                     "installation routine for partitioning and mounting devices.")
436             raise InstallError(txt)
437         return disk_size
438
439     def run_gpt(self, part_sizes):
440         """ Auto partition using a GPT table """
441         # Our computed sizes are all in mebibytes (MiB) i.e. powers of 1024, not metric megabytes.
442         # These are 'M' in sgdisk and 'MiB' in parted.
443         # If you use 'M' in parted you'll get MB instead of MiB, and you're gonna have a bad time.
444         device = self.auto_device
445
446         # Clean partition table to avoid issues!
447         wrapper.sgdisk("zap-all", device)
448
449         # Clear all magic strings/signatures - mdadm, lvm, partition tables etc.
450         wrapper.run_dd("/dev/zero", device)
451         wrapper.wipefs(device)
452
453         # Create fresh GPT
454         wrapper.sgdisk("clear", device)
455         wrapper.parted_mklabel(device, "gpt")
456
457         # Inform the kernel of the partition change.
458         # Needed if the hard disk had a MBR partition table.
459         err_msg = "Error informing the kernel of the partition change."
460         call(["partprobe", device], msg=err_msg, fatal=True)
461
462         part_num = 1
463
464         if not self.uefi:
465             # We don't allow BIOS+GPT right now, so this code will be never executed
466             # We leave here just for future reference
467             # Create BIOS Boot Partition
468             # GPT GUID: 21686148-6449-6E6F-744E-656564454649
469             # This partition is not required if the system is UEFI based,
470             # as there is no such embedding of the second-stage code in that case
471             wrapper.sgdisk_new(device, part_num, "BIOS_BOOT", 2, "EF02")
472             part_num += 1
473
474         if self.bootloader == "grub2":
475             # Create EFI System Partition (ESP)
476             # GPT GUID: C12A7328-F81F-11D2-BA4B-00A0C93EC93B
477             wrapper.sgdisk_new(
478                 device, part_num, "UEFI_SYSTEM", part_sizes['efi'], "EF00")
479             part_num += 1
480
481         # Create Boot partition
482         if self.bootloader in ["systemd-boot", "refind"]:
483             wrapper.sgdisk_new(
484                 device, part_num, "REBORNOS_BOOT", part_sizes['boot'], "EF00")
485         else:
486             wrapper.sgdisk_new(
487                 device, part_num, "REBORNOS_BOOT", part_sizes['boot'], "8300")
488         part_num += 1
489
490         if self.lvm:
491             # Create partition for lvm
492             # (will store root, swap and home (if desired) logical volumes)
493             wrapper.sgdisk_new(
494                 device, part_num, "REBORNOS_LVM", part_sizes['lvm_pv'], "8E00")
495             part_num += 1
496         else:
497             wrapper.sgdisk_new(
498                 device, part_num, "REBORNOS_ROOT", part_sizes['root'], "8300")
499             part_num += 1
500             if self.home:
501                 wrapper.sgdisk_new(
502                     device, part_num, "REBORNOS_HOME", part_sizes['home'], "8302")
503                 part_num += 1
504             wrapper.sgdisk_new(
505                 device, part_num, "REBORNOS_SWAP", 0, "8200")
506
507         output = call(["sgdisk", "--print", device])
508         logging.debug(output)
509
510     def run_mbr(self, part_sizes):
511         """ DOS MBR partition table """
512         # Our computed sizes are all in mebibytes (MiB) i.e. powers of 1024, not metric megabytes.
513         # These are 'M' in sgdisk and 'MiB' in parted.
514         # If you use 'M' in parted you'll get MB instead of MiB, and you're gonna have a bad time.
515         device = self.auto_device
516
517         # Start at sector 1 for 4k drive compatibility and correct alignment
518         # Clean partitiontable to avoid issues!
519         wrapper.run_dd("/dev/zero", device)
520         wrapper.wipefs(device)
521
522         # Create DOS MBR
523         wrapper.parted_mklabel(device, "msdos")
524
525         # Create boot partition (all sizes are in MiB)
526         # if start is -1 wrapper.parted_mkpart assumes that our partition
527         # starts at 1 (first partition in disk)
528         start = -1
529         end = part_sizes['boot']
530         wrapper.parted_mkpart(device, "primary", start, end)
531
532         # Set boot partition as bootable
533         wrapper.parted_set(device, "1", "boot", "on")
534
535         if self.lvm:
536             # Create partition for lvm (will store root, home (if desired),
537             # and swap logical volumes)
538             start = end
539             # end = start + part_sizes['lvm_pv']
540             end = "-1s"
541             wrapper.parted_mkpart(device, "primary", start, end)
542
543             # Set lvm flag
544             wrapper.parted_set(device, "2", "lvm", "on")
545         else:
546             # Create root partition
547             start = end
548             end = start + part_sizes['root']
549             wrapper.parted_mkpart(device, "primary", start, end)
550
551             if self.home:
552                 # Create home partition
553                 start = end
554                 end = start + part_sizes['home']
555                 wrapper.parted_mkpart(device, "primary", start, end)
556
557             # Create an extended partition where we will put our swap partition
558             start = end
559             # end = start + part_sizes['swap']
560             end = "-1s"
561             wrapper.parted_mkpart(device, "extended", start, end)
562
563             # Now create a logical swap partition
564             start += 1
565             end = "-1s"
566             wrapper.parted_mkpart(
567                 device, "logical", start, end, "linux-swap")
568
569     def run_lvm(self, devices, part_sizes, start_part_sizes, disk_size):
570         """ Create lvm volumes """
571         logging.debug("Cnchi will setup LVM on device %s", devices['lvm'])
572
573         err_msg = "Error creating LVM physical volume in device {0}"
574         err_msg = err_msg.format(devices['lvm'])
575         cmd = ["pvcreate", "-f", "-y", devices['lvm']]
576         call(cmd, msg=err_msg, fatal=True)
577
578         err_msg = "Error creating LVM volume group in device {0}"
579         err_msg = err_msg.format(devices['lvm'])
580         cmd = ["vgcreate", "-f", "-y", "RebornOSVG", devices['lvm']]
581         call(cmd, msg=err_msg, fatal=True)
582
583         # Fix issue 180
584         # Check space we have now for creating logical volumes
585         cmd = ["vgdisplay", "-c", "RebornOSVG"]
586         vg_info = call(cmd, fatal=True)
587         # Get column number 12: Size of volume group in kilobytes
588         vg_size = int(vg_info.split(":")[11]) / 1024
589         if part_sizes['lvm_pv'] > vg_size:
590             logging.debug(
591                 "Real RebornOSVG volume group size: %d MiB", vg_size)
592             logging.debug("Reajusting logical volume sizes")
593             diff_size = part_sizes['lvm_pv'] - vg_size
594             part_sizes = self.get_part_sizes(
595                 disk_size - diff_size, start_part_sizes)
596             self.log_part_sizes(part_sizes)
597
598         # Create LVM volumes
599         err_msg = "Error creating LVM logical volume"
600
601         size = str(int(part_sizes['root']))
602         cmd = ["lvcreate", "--name", "RebornOSRoot", "--size", size, "RebornOSVG"]
603         call(cmd, msg=err_msg, fatal=True)
604
605         if not self.home:
606             # Use the remainig space for our swap volume
607             cmd = ["lvcreate", "--name", "RebornOSSwap", "--extents", "100%FREE", "RebornOSVG"]
608             call(cmd, msg=err_msg, fatal=True)
609         else:
610             size = str(int(part_sizes['swap']))
611             cmd = ["lvcreate", "--name", "RebornOSSwap", "--size", size, "RebornOSVG"]
612             call(cmd, msg=err_msg, fatal=True)
613             # Use the remaining space for our home volume
614             cmd = ["lvcreate", "--name", "RebornOSHome", "--extents", "100%FREE", "RebornOSVG"]
615             call(cmd, msg=err_msg, fatal=True)
616
617     def create_filesystems(self, devices):
618         """ Create filesystems in newly created partitions """
619         mount_points = {
620             'efi': '/boot/efi', 'boot': '/boot', 'root': '/', 'home': '/home', 'swap': ''}
621
622         labels = {
623             'efi': 'UEFI_SYSTEM', 'boot': 'RebornOSBoot', 'root': 'RebornOSRoot',
624             'home': 'RebornOSHome', 'swap': 'RebornOSSwap'}
625
626         fs_devices = self.get_fs_devices()
627
628         # Note: Make sure the "root" partition is defined first!
629         self.mkfs(devices['root'], fs_devices[devices['root']],
630                   mount_points['root'], labels['root'])
631         self.mkfs(devices['swap'], fs_devices[devices['swap']],
632                   mount_points['swap'], labels['swap'])
633
634         # NOTE: This will be formated in ext4 (bios or gpt+grub2) or
635         # fat32 (gpt+systemd-boot or refind bootloaders)
636         self.mkfs(devices['boot'], fs_devices[devices['boot']],
637                   mount_points['boot'], labels['boot'])
638
639         # NOTE: Make sure the "boot" partition is defined before the "efi" one!
640         if self.gpt and self.bootloader == "grub2":
641             # Format EFI System Partition (ESP) with vfat (fat32)
642             self.mkfs(devices['efi'], fs_devices[devices['efi']],
643                       mount_points['efi'], labels['efi'])
644
645         if self.home:
646             self.mkfs(devices['home'], fs_devices[devices['home']],
647                       mount_points['home'], labels['home'])
648
649     def copy_luks_keyfiles(self):
650         """ Copy root keyfile to boot partition and home keyfile to root partition
651             user will choose what to do with it
652             THIS IS NONSENSE (BIG SECURITY HOLE), BUT WE TRUST THE USER TO FIX THIS
653             User shouldn't store the keyfiles unencrypted unless the medium itself
654             is reasonably safe (boot partition is not) """
655
656         key_files = AutoPartition.LUKS_KEY_FILES
657         key_file = os.path.join(self.temp, key_files[0])
658         os.chmod(key_file, 0o400)
659         boot_path = os.path.join(self.dest_dir, "boot")
660         cmd = ['mv', key_file, boot_path]
661         call(cmd, msg="Can't copy root LUKS keyfile to the installation device.")
662         if self.home and not self.lvm:
663             key_file = os.path.join(self.temp, key_files[1])
664             os.chmod(key_files, 0o400)
665             luks_dir = os.path.join(self.dest_dir, 'etc/luks-keys')
666             os.makedirs(luks_dir, mode=0o755, exist_ok=True)
667             cmd = ['mv', key_file, luks_dir]
668             call(cmd, msg="Can't copy home LUKS keyfile to the installation device.")
669
670     @staticmethod
671     def remove_lvm(device):
672         """ Remove all previous LVM volumes
673         (it may have been left created due to a previous failed installation) """
674
675         err_msg = "Can't delete existent LVM volumes in device {0}".format(device)
676
677         cmd = ["/usr/bin/lvs", "-o", "lv_name,vg_name,devices", "--noheadings"]
678         lvolumes = call(cmd, msg=err_msg)
679         if lvolumes:
680             lvolumes = lvolumes.split("\n")
681             for lvolume in lvolumes:
682                 if lvolume:
683                     (lvolume, vgroup, ldevice) = lvolume.split()
684                     if device in ldevice:
685                         lvdev = "/dev/" + vgroup + "/" + lvolume
686                         call(["/usr/bin/wipefs", "-a", lvdev], msg=err_msg)
687                         call(["/usr/bin/lvremove", "-f", lvdev], msg=err_msg)
688
689         cmd = ["/usr/bin/vgs", "-o", "vg_name,devices", "--noheadings"]
690         vgnames = call(cmd, msg=err_msg)
691         if vgnames:
692             vgnames = vgnames.split("\n")
693             for vgname in vgnames:
694                 (vgname, vgdevice) = vgname.split()
695                 if vgname and device in vgdevice:
696                     call(["/usr/bin/vgremove", "-f", vgname], msg=err_msg)
697
698         cmd = ["/usr/bin/pvs", "-o", "pv_name", "--noheadings"]
699         pvolumes = call(cmd, msg=err_msg)
700         if pvolumes:
701             pvolumes = pvolumes.split("\n")
702             for pvolume in pvolumes:
703                 pvolume = pvolume.strip()
704                 if device in pvolume:
705                     cmd = ["/usr/bin/pvremove", "-ff", "-y", pvolume]
706                     call(cmd, msg=err_msg)
707     @staticmethod
708     def printk(enable):
709         """ Enables / disables printing kernel messages to console """
710         with open("/proc/sys/kernel/printk", "w") as fpk:
711             if enable:
712                 fpk.write("4")
713             else:
714                 fpk.write("0")
715
716     def log_devices(self, devices):
717         """ Log all devices for debugging purposes """
718         if self.gpt and self.bootloader == "grub2":
719             logging.debug("EFI: %s", devices['efi'])
720         logging.debug("Boot: %s", devices['boot'])
721         logging.debug("Root: %s", devices['root'])
722         if self.home:
723             logging.debug("Home: %s", devices['home'])
724         logging.debug("Swap: %s", devices['swap'])
725
726     def run(self):
727         """ Main method. Runs auto partition sequence """
728         disk_size = self.get_disk_size()
729         device = self.auto_device
730         start_part_sizes = 1
731
732         part_sizes = self.get_part_sizes(disk_size, start_part_sizes)
733         self.log_part_sizes(part_sizes)
734
735         # Disable swap and unmount all partitions inside dest_dir
736         mount.unmount_all_in_directory(self.dest_dir)
737         # Disable swap and unmount all partitions of device
738         mount.unmount_all_in_device(device)
739         # Remove lvm in destination device
740         self.remove_lvm(device)
741         # Close luks devices in destination device
742         luks.close_antergos_devices()
743
744         self.printk(False)
745         if self.gpt:
746             self.run_gpt(part_sizes)
747         else:
748             self.run_mbr(part_sizes)
749         self.printk(True)
750
751         # Wait until /dev initialized correct devices
752         call(["udevadm", "settle"])
753
754         devices = self.get_devices()
755
756         self.log_devices(devices)
757
758         if self.luks:
759             luks_options = {'password': self.luks_password,
760                             'key': AutoPartition.LUKS_KEY_FILES[0]}
761             luks.setup(devices['luks_root'], 'cryptRebornOS', luks_options)
762             if self.home and not self.lvm:
763                 luks_options = {'password': self.luks_password,
764                                 'key': AutoPartition.LUKS_KEY_FILES[1]}
765                 luks.setup(devices['luks_home'], 'cryptRebornOSHome', luks_options)
766
767         if self.lvm:
768             self.run_lvm(devices, part_sizes, start_part_sizes, disk_size)
769
770         # We have all partitions and volumes created. Let's create its filesystems with mkfs.
771         self.create_filesystems(devices)
772
773         # NOTE: encrypted and/or lvm2 hooks will be added to mkinitcpio.conf
774         #       in mkinitcpio.py, if necessary.
775         # NOTE: /etc/default/grub, /etc/stab and /etc/crypttab will be modified
776         #       in post_install.py, if necessary.
777
778         if self.luks and self.luks_password == "":
779             self.copy_luks_keyfiles()
780
781
782 def test_module():
783     """ Test autopartition module """
784     import gettext
785
786     _ = gettext.gettext
787
788     os.makedirs("/var/log/cnchi")
789     logging.basicConfig(
790         filename="/var/log/cnchi/cnchi-autopartition.log",
791         level=logging.DEBUG)
792
793     settings = {
794         'use_luks': True,
795         'luks_password': "luks",
796         'use_lvm': True,
797         'use_home': True,
798         'bootloader': "grub2"}
799
800     AutoPartition(
801         dest_dir="/install",
802         auto_device="/dev/sdb",
803         settings=settings,
804         callback_queue=None).run()
805
806 if __name__ == '__main__':
807     test_module()